summaryrefslogtreecommitdiffstats
path: root/assets/js/form-components.js
blob: ffa42605ba62a24698da2462eec93b83348bd2e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// 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;
    }
  };
}

// Focus Trap for Modals - Week 5
function createFocusTrap(modalElement) {
  const focusableElements = modalElement.querySelectorAll(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );

  if (focusableElements.length === 0) return;

  const firstElement = focusableElements[0];
  const lastElement = focusableElements[focusableElements.length - 1];

  modalElement.addEventListener('keydown', (e) => {
    if (e.key !== 'Tab') return;

    if (e.shiftKey) {
      // Shift + Tab
      if (document.activeElement === firstElement) {
        e.preventDefault();
        lastElement.focus();
      }
    } else {
      // Tab
      if (document.activeElement === lastElement) {
        e.preventDefault();
        firstElement.focus();
      }
    }
  });

  // Set initial focus
  firstElement.focus();
}

// Export for use in Alpine.js
window.createFocusTrap = createFocusTrap;