]> danix's work - danix.xyz-2.git/commitdiff
feat: add form component templates, i18n strings, and Alpine.js utilities
authorDanilo M. <redacted>
Thu, 16 Apr 2026 14:54:19 +0000 (16:54 +0200)
committerDanilo M. <redacted>
Thu, 16 Apr 2026 14:54:19 +0000 (16:54 +0200)
HANDOFF.md
i18n/en.yaml
i18n/it.yaml
themes/danix-xyz-hacker/assets/js/form-components.js [new file with mode: 0644]
themes/danix-xyz-hacker/layouts/partials/form-components.html [new file with mode: 0644]
themes/danix-xyz-hacker/layouts/partials/toast-container.html [new file with mode: 0644]

index 4e0522bffa1b4dec9c609dd8310b4ffa9ac41e31..668d13c9d084d7c93f29418b0bf5f1663d82e708 100644 (file)
@@ -1,15 +1,14 @@
 Who this is for:
-  You are Danilo, building a bilingual (Italian/English) hacker-themed Hugo theme for danix.xyz. You've completed Weeks 1-3 of a 6-week implementation roadmap and are now fixing bugs and regressions discovered during testing.
+  You are Danilo, building a bilingual (Italian/English) hacker-themed Hugo theme for danix.xyz. You've completed Weeks 1-3 of a 6-week implementation roadmap (50% complete) and have just fixed a regression and prepared comprehensive documentation for Week 4.
 
 What we covered:
-  This session focused on debugging and fixing the breadcrumb navigation issue that was left pending from Week 3. The breadcrumb partial existed but produced zero HTML output. Investigation revealed Hugo was using layouts/\_default/single.html instead of layouts/articles/single.html.
-  The fix involved adding the breadcrumb partial call to the actual layout file and making it fully multilingual with language-aware links (IT uses /it/ subdir, EN uses root /). After the breadcrumb was fixed and deployed, a regression was discovered: the header logo link was hardcoded to / which broke Italian navigation. This was also fixed to respect the current language. Both fixes have been committed and pushed to master.
+  This session focused on concluding Week 3 and preparing Week 4. You identified a regression where the header was scrolling with the page instead of staying fixed. Investigation revealed the header was using sticky positioning, which wasn't working properly. The fix involved changing the header to fixed positioning with left-0 right-0 classes and adding mt-20 margin to the main element to prevent content overlap. Separately, you created comprehensive transition documentation: WEEK3-COMPLETION.md (audit of all Week 3 work), WEEK4-PLAN.md (detailed technical roadmap for forms and interactions), WEEK3-4-TRANSITION.md (git workflow and quick start), PROGRESS-STATUS-WEEK4.txt (cumulative progress tracking), and updated HANDOFF.md. You also confirmed that the z-10 class on main wasn't related to the sticky issue and restored it after fixing the header. All changes were committed and pushed to master.
 
 What was confirmed:
-  The breadcrumb issue was caused by Hugo's layout lookup preferring \_default/single.html over articles/single.html. The breadcrumb now renders correctly on all article pages. Language-aware routing works properly for both breadcrumb links and the header logo. Italian articles correctly link to /it/ paths, English articles link to / paths. All navigation elements maintain language context when clicked.
+  The header regression was caused by sticky positioning not working reliably. Fixed positioning with proper margin offset (mt-20) on main element resolves it completely. The z-10 class has no effect on opacity or positioning behavior. Week 3 is fully complete with 14 commits merged to master covering cards, navigation header, hamburger menu, and breadcrumb navigation. Both breadcrumb rendering and logo language-context bugs were successfully fixed in Week 3. All components tested at 320px, 768px, 1060px+ breakpoints with dark/light mode support and WCAG AA accessibility verified.
 
 Still in progress:
-  Nothing is left open from this session. Both the breadcrumb rendering issue and the logo link regression have been resolved and pushed to master.
+  Nothing from previous sessions. Week 3 is fully merged to master with all objectives achieved. Week 4 is ready to start but not yet initiated.
 
 Next steps:
-  Create a new week-4-* feature branch following the established branching policy. Continue with Week 4 implementation while running npm run watch during development. Test all changes at multiple breakpoints (320px, 768px, 1060px) and in both dark and light modes before merging to master at week end.
\ No newline at end of file
+  Create the week-4-forms feature branch and begin Week 4 implementation: Forms & Interactions. This involves building form component system (inputs, textareas, selects, checkboxes, radio buttons, form groups), modal system (alert, confirm, content modals with focus trap), and interactive patterns (loading spinners, toast notifications, tooltips, validation feedback). Estimated 6-8 hours. Follow WEEK4-PLAN.md for the detailed technical roadmap. Remember to run npm run watch during development and npm run build before committing. Test at all responsive breakpoints and both dark/light modes before merging to master at week end.
\ No newline at end of file
index d543e6fb548e99b0327d079c33f51417a903a029..55338499442e25103ab26108c50a1f3184285057 100644 (file)
@@ -53,6 +53,35 @@ sending: "Sending..."
 success: "Message sent successfully!"
 error: "An error occurred. Please try again."
 
+# Form Components
+form_invalid_email: "Please enter a valid email address"
+form_password_help: "Must be at least 8 characters"
+form_agree_terms: "I agree to the terms and conditions"
+form_select_interests: "Select your interests"
+form_interest_tech: "Technology"
+form_interest_design: "Design"
+form_select_preference: "Select a preference"
+form_option_a: "Option A"
+form_option_b: "Option B"
+form_first_name: "First Name"
+form_last_name: "Last Name"
+form_search: "Search"
+form_search_btn: "Search"
+form_open_alert: "Open Alert Modal"
+form_open_confirm: "Open Confirm Modal"
+form_open_content: "Open Content Modal"
+form_alert_title: "Alert"
+form_alert_message: "This is an alert modal. Click OK to dismiss."
+form_ok: "OK"
+form_confirm_title: "Confirm Action"
+form_confirm_message: "Are you sure you want to continue?"
+form_cancel: "Cancel"
+form_confirm: "Confirm"
+form_content_title: "Modal with Content"
+form_content_message: "This modal contains detailed content. You can add forms, lists, or any HTML here."
+form_close: "Close"
+form_save: "Save"
+
 # Social
 follow: "Follow me"
 contactMe: "Contact me"
index 2f8d8e1fe135db153179de67f115f58bd13a5af1..2d11f543540dd6670f04fc1df5bad829331b7dd0 100644 (file)
@@ -53,6 +53,35 @@ sending: "Invio in corso..."
 success: "Messaggio inviato con successo!"
 error: "Si è verificato un errore. Riprova."
 
+# Form Components
+form_invalid_email: "Inserire un indirizzo email valido"
+form_password_help: "Deve avere almeno 8 caratteri"
+form_agree_terms: "Accetto i termini e le condizioni"
+form_select_interests: "Seleziona i tuoi interessi"
+form_interest_tech: "Tecnologia"
+form_interest_design: "Design"
+form_select_preference: "Seleziona una preferenza"
+form_option_a: "Opzione A"
+form_option_b: "Opzione B"
+form_first_name: "Nome"
+form_last_name: "Cognome"
+form_search: "Cerca"
+form_search_btn: "Cerca"
+form_open_alert: "Apri modale di avviso"
+form_open_confirm: "Apri modale di conferma"
+form_open_content: "Apri modale contenuto"
+form_alert_title: "Avviso"
+form_alert_message: "Questo è un modale di avviso. Fai clic su OK per chiudere."
+form_ok: "OK"
+form_confirm_title: "Conferma azione"
+form_confirm_message: "Sei sicuro di voler continuare?"
+form_cancel: "Annulla"
+form_confirm: "Conferma"
+form_content_title: "Modale con contenuto"
+form_content_message: "Questo modale contiene contenuti dettagliati. Puoi aggiungere moduli, elenchi o qualsiasi HTML qui."
+form_close: "Chiudi"
+form_save: "Salva"
+
 # Social
 follow: "Seguimi"
 contactMe: "Contattami"
diff --git a/themes/danix-xyz-hacker/assets/js/form-components.js b/themes/danix-xyz-hacker/assets/js/form-components.js
new file mode 100644 (file)
index 0000000..35a5f27
--- /dev/null
@@ -0,0 +1,91 @@
+// Form component utilities and Alpine.js data
+
+export function formComponentsData() {
+  return {
+    // Modal states
+    showAlertModal: false,
+    showConfirmModal: false,
+    showContentModal: false,
+
+    // Toast notification state
+    toasts: [],
+
+    // Handle confirm modal action
+    handleConfirm() {
+      this.showConfirmModal = false;
+      this.showToast('success', 'Action confirmed!');
+    },
+
+    // Show toast notification
+    showToast(type = 'success', message = null) {
+      const messages = {
+        success: 'Operation completed successfully!',
+        error: 'An error occurred. Please try again.',
+        info: 'Here is some information.',
+        warning: 'Please be careful with this action.'
+      };
+
+      const toastMessage = message || messages[type] || messages.success;
+      const toastId = Date.now();
+
+      // Add toast to list
+      this.toasts.push({
+        id: toastId,
+        type: type,
+        message: toastMessage
+      });
+
+      // Auto-remove after 5 seconds
+      setTimeout(() => {
+        this.toasts = this.toasts.filter(t => t.id !== toastId);
+      }, 5000);
+    },
+
+    // Remove toast manually
+    removeToast(id) {
+      this.toasts = this.toasts.filter(t => t.id !== id);
+    },
+
+    // Form validation utilities
+    validateEmail(email) {
+      const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+      return regex.test(email);
+    },
+
+    validatePassword(password) {
+      return password.length >= 8;
+    },
+
+    // Auto-expand textarea
+    autoExpandTextarea(event) {
+      const textarea = event.target;
+      textarea.style.height = 'auto';
+      textarea.style.height = (textarea.scrollHeight) + 'px';
+    }
+  };
+}
+
+// Toast container component for Alpine.js
+export function renderToastContainer(Alpine) {
+  if (!Alpine) return;
+
+  // This can be used in templates via Alpine
+  window.formUtils = {
+    formatCharCount(current, max) {
+      if (max) {
+        return `${current}/${max}`;
+      }
+      return current;
+    },
+
+    isCharCountWarning(current, max) {
+      if (!max) return false;
+      return current > (max * 0.8);
+    },
+
+    isCharCountError(current, max) {
+      if (!max) return false;
+      return current >= max;
+    }
+  };
+}
diff --git a/themes/danix-xyz-hacker/layouts/partials/form-components.html b/themes/danix-xyz-hacker/layouts/partials/form-components.html
new file mode 100644 (file)
index 0000000..9a69d43
--- /dev/null
@@ -0,0 +1,219 @@
+{{ define "form-components" }}
+
+<!-- ============================================
+     FORM INPUT EXAMPLES
+     ============================================ -->
+
+<section class="my-12 space-y-8">
+  <h2>Form Components</h2>
+
+  <!-- Basic Form Group -->
+  <div class="form-group">
+    <label for="input-text">Text Input</label>
+    <input type="text" id="input-text" class="form-input" placeholder="Enter text...">
+  </div>
+
+  <!-- Form Group with Error -->
+  <div class="form-group error">
+    <label for="input-email">Email (with error)</label>
+    <input type="email" id="input-email" class="form-input error" value="invalid-email">
+    <div class="form-error">{{ i18n "form_invalid_email" }}</div>
+  </div>
+
+  <!-- Form Group with Help Text -->
+  <div class="form-group">
+    <label for="input-password" class="">Password</label>
+    <input type="password" id="input-password" class="form-input" placeholder="••••••">
+    <div class="form-group-help">{{ i18n "form_password_help" | default "Must be at least 8 characters" }}</div>
+  </div>
+
+  <!-- Disabled Input -->
+  <div class="form-group">
+    <label for="input-disabled">Disabled Input</label>
+    <input type="text" id="input-disabled" class="form-input" value="Cannot edit" disabled>
+  </div>
+
+  <!-- ============================================
+       TEXTAREA EXAMPLES
+       ============================================ -->
+
+  <div class="form-group">
+    <label for="textarea-message">Message</label>
+    <textarea id="textarea-message" class="form-textarea" placeholder="Enter your message..."></textarea>
+  </div>
+
+  <!-- ============================================
+       SELECT EXAMPLES
+       ============================================ -->
+
+  <div class="form-group">
+    <label for="select-option">Select an option</label>
+    <select id="select-option" class="form-select">
+      <option>Choose...</option>
+      <option>Option 1</option>
+      <option>Option 2</option>
+      <option>Option 3</option>
+    </select>
+  </div>
+
+  <!-- ============================================
+       CHECKBOX EXAMPLES
+       ============================================ -->
+
+  <div class="form-group">
+    <label class="flex items-center gap-3 cursor-pointer">
+      <input type="checkbox" class="form-checkbox">
+      <span>{{ i18n "form_agree_terms" | default "I agree to the terms" }}</span>
+    </label>
+  </div>
+
+  <!-- Multiple Checkboxes -->
+  <div class="form-group space-y-2">
+    <p class="font-semibold">{{ i18n "form_select_interests" | default "Select your interests" }}</p>
+    <label class="flex items-center gap-3 cursor-pointer">
+      <input type="checkbox" class="form-checkbox" name="interests">
+      <span>{{ i18n "form_interest_tech" | default "Technology" }}</span>
+    </label>
+    <label class="flex items-center gap-3 cursor-pointer">
+      <input type="checkbox" class="form-checkbox" name="interests">
+      <span>{{ i18n "form_interest_design" | default "Design" }}</span>
+    </label>
+  </div>
+
+  <!-- ============================================
+       RADIO BUTTON EXAMPLES
+       ============================================ -->
+
+  <div class="form-group space-y-2">
+    <p class="font-semibold">{{ i18n "form_select_preference" | default "Select a preference" }}</p>
+    <label class="flex items-center gap-3 cursor-pointer">
+      <input type="radio" name="preference" class="form-radio">
+      <span>{{ i18n "form_option_a" | default "Option A" }}</span>
+    </label>
+    <label class="flex items-center gap-3 cursor-pointer">
+      <input type="radio" name="preference" class="form-radio">
+      <span>{{ i18n "form_option_b" | default "Option B" }}</span>
+    </label>
+  </div>
+
+  <!-- ============================================
+       FORM ROWS (MULTI-COLUMN)
+       ============================================ -->
+
+  <div class="form-row">
+    <div class="form-group">
+      <label for="first-name">{{ i18n "form_first_name" | default "First Name" }}</label>
+      <input type="text" id="first-name" class="form-input" placeholder="John">
+    </div>
+    <div class="form-group">
+      <label for="last-name">{{ i18n "form_last_name" | default "Last Name" }}</label>
+      <input type="text" id="last-name" class="form-input" placeholder="Doe">
+    </div>
+  </div>
+
+  <!-- ============================================
+       INLINE FORMS
+       ============================================ -->
+
+  <div class="form-inline">
+    <div class="form-group">
+      <label for="search-input">{{ i18n "form_search" | default "Search" }}</label>
+      <input type="text" id="search-input" class="form-input" placeholder="Type to search...">
+    </div>
+    <button class="btn btn-primary">{{ i18n "form_search_btn" | default "Search" }}</button>
+  </div>
+
+  <!-- ============================================
+       MODALS (DEMO BUTTONS)
+       ============================================ -->
+
+  <div class="space-y-3 mt-8">
+    <h3>Modal Examples</h3>
+    <button class="btn btn-primary" @click="showAlertModal = true">{{ i18n "form_open_alert" | default "Open Alert Modal" }}</button>
+    <button class="btn btn-secondary" @click="showConfirmModal = true">{{ i18n "form_open_confirm" | default "Open Confirm Modal" }}</button>
+    <button class="btn btn-outline" @click="showContentModal = true">{{ i18n "form_open_content" | default "Open Content Modal" }}</button>
+  </div>
+
+  <!-- ============================================
+       NOTIFICATIONS (DEMO BUTTONS)
+       ============================================ -->
+
+  <div class="space-y-3 mt-8">
+    <h3>Notifications</h3>
+    <button class="btn btn-primary btn-sm" @click="showToast('success')">Success Toast</button>
+    <button class="btn btn-secondary btn-sm" @click="showToast('error')">Error Toast</button>
+    <button class="btn btn-sm" style="background-color: #3b82f6; color: white;" @click="showToast('info')">Info Toast</button>
+    <button class="btn btn-sm" style="background-color: #f59e0b; color: white;" @click="showToast('warning')">Warning Toast</button>
+  </div>
+
+</section>
+
+<!-- ============================================
+     ALERT MODAL
+     ============================================ -->
+
+<div class="modal" :class="{ active: showAlertModal }" x-show="showAlertModal">
+  <div class="modal-backdrop" @click="showAlertModal = false"></div>
+  <div class="modal-content modal-sm">
+    <div class="modal-header">
+      <h3 class="modal-title">{{ i18n "form_alert_title" | default "Alert" }}</h3>
+      <div class="modal-close" @click="showAlertModal = false"></div>
+    </div>
+    <div class="modal-body">
+      <p>{{ i18n "form_alert_message" | default "This is an alert modal. Click OK to dismiss." }}</p>
+    </div>
+    <div class="modal-footer">
+      <button class="btn btn-primary" @click="showAlertModal = false">{{ i18n "form_ok" | default "OK" }}</button>
+    </div>
+  </div>
+</div>
+
+<!-- ============================================
+     CONFIRM MODAL
+     ============================================ -->
+
+<div class="modal" :class="{ active: showConfirmModal }" x-show="showConfirmModal">
+  <div class="modal-backdrop" @click="showConfirmModal = false"></div>
+  <div class="modal-content modal-sm">
+    <div class="modal-header">
+      <h3 class="modal-title">{{ i18n "form_confirm_title" | default "Confirm Action" }}</h3>
+      <div class="modal-close" @click="showConfirmModal = false"></div>
+    </div>
+    <div class="modal-body">
+      <p>{{ i18n "form_confirm_message" | default "Are you sure you want to continue?" }}</p>
+    </div>
+    <div class="modal-footer">
+      <button class="btn btn-outline" @click="showConfirmModal = false">{{ i18n "form_cancel" | default "Cancel" }}</button>
+      <button class="btn btn-primary" @click="handleConfirm()">{{ i18n "form_confirm" | default "Confirm" }}</button>
+    </div>
+  </div>
+</div>
+
+<!-- ============================================
+     CONTENT MODAL
+     ============================================ -->
+
+<div class="modal" :class="{ active: showContentModal }" x-show="showContentModal">
+  <div class="modal-backdrop" @click="showContentModal = false"></div>
+  <div class="modal-content modal-md">
+    <div class="modal-header">
+      <h3 class="modal-title">{{ i18n "form_content_title" | default "Modal with Content" }}</h3>
+      <div class="modal-close" @click="showContentModal = false"></div>
+    </div>
+    <div class="modal-body">
+      <p>{{ i18n "form_content_message" | default "This modal contains detailed content. You can add forms, lists, or any HTML here." }}</p>
+      <div class="mt-4">
+        <div class="form-group">
+          <label for="modal-input">Input in Modal</label>
+          <input type="text" id="modal-input" class="form-input" placeholder="Type here...">
+        </div>
+      </div>
+    </div>
+    <div class="modal-footer">
+      <button class="btn btn-outline" @click="showContentModal = false">{{ i18n "form_close" | default "Close" }}</button>
+      <button class="btn btn-primary">{{ i18n "form_save" | default "Save" }}</button>
+    </div>
+  </div>
+</div>
+
+{{ end }}
diff --git a/themes/danix-xyz-hacker/layouts/partials/toast-container.html b/themes/danix-xyz-hacker/layouts/partials/toast-container.html
new file mode 100644 (file)
index 0000000..1c5fbf2
--- /dev/null
@@ -0,0 +1,13 @@
+{{ define "toast-container" }}
+
+<!-- Toast notification container with Alpine.js integration -->
+<div class="toast-container" x-data="formComponentsData()">
+  <template x-for="toast in toasts" :key="toast.id">
+    <div class="toast" :class="`toast-${toast.type}`" x-show="toasts.length > 0">
+      <span x-text="toast.message"></span>
+      <div class="toast-close" @click="removeToast(toast.id)"></div>
+    </div>
+  </template>
+</div>
+
+{{ end }}