@datametria/vue-components 2.4.1 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +57 -5
  2. package/dist/index.es.js +5812 -2837
  3. package/dist/index.umd.js +670 -10
  4. package/dist/src/components/DatametriaCheckbox.vue.d.ts +2 -0
  5. package/dist/src/components/DatametriaCheckboxGroup.vue.d.ts +6 -0
  6. package/dist/src/components/DatametriaFileUpload.vue.d.ts +5 -0
  7. package/dist/src/components/DatametriaFloatingBar.vue.d.ts +1 -1
  8. package/dist/src/components/DatametriaInput.vue.d.ts +2 -22
  9. package/dist/src/components/DatametriaNavbar.vue.d.ts +1 -1
  10. package/dist/src/components/DatametriaPasswordInput.vue.d.ts +2 -0
  11. package/dist/src/components/DatametriaRadio.vue.d.ts +2 -0
  12. package/dist/src/components/DatametriaRadioGroup.vue.d.ts +6 -0
  13. package/dist/src/components/DatametriaSelect.vue.d.ts +2 -3
  14. package/dist/src/components/DatametriaSidebar.vue.d.ts +1 -1
  15. package/dist/src/components/DatametriaSwitch.vue.d.ts +8 -1
  16. package/dist/src/components/DatametriaTabs.vue.d.ts +2 -2
  17. package/dist/src/components/DatametriaTextarea.vue.d.ts +6 -0
  18. package/dist/src/composables/useAnalytics.d.ts +8 -0
  19. package/dist/src/types/analytics.d.ts +50 -0
  20. package/dist/vue-components.css +1 -1
  21. package/package.json +3 -2
  22. package/src/components/DatametriaButton.vue +196 -195
  23. package/src/components/DatametriaCheckbox.vue +289 -197
  24. package/src/components/DatametriaCheckboxGroup.vue +124 -3
  25. package/src/components/DatametriaFileUpload.vue +493 -414
  26. package/src/components/DatametriaInput.vue +342 -316
  27. package/src/components/DatametriaPasswordInput.vue +433 -446
  28. package/src/components/DatametriaRadio.vue +240 -151
  29. package/src/components/DatametriaRadioGroup.vue +124 -3
  30. package/src/components/DatametriaSelect.vue +409 -313
  31. package/src/components/DatametriaSwitch.vue +319 -146
  32. package/src/components/DatametriaTabs.vue +2 -2
  33. package/src/components/DatametriaTextarea.vue +285 -213
  34. package/src/composables/useAnalytics.ts +70 -0
  35. package/src/types/analytics.ts +59 -0
@@ -1,450 +1,437 @@
1
- <template>
2
- <div class="datametria-password-input">
3
- <label v-if="label" :for="inputId" class="datametria-password-input__label">
4
- {{ label }}
5
- <span v-if="required" class="datametria-password-input__required">*</span>
6
- </label>
7
-
8
- <div class="datametria-password-input__wrapper">
9
- <input
10
- :id="inputId"
11
- :value="modelValue"
12
- :type="showPassword ? 'text' : 'password'"
13
- :placeholder="placeholder"
14
- :disabled="disabled"
15
- :required="required"
16
- :autocomplete="autocomplete"
17
- :class="inputClasses"
18
- :aria-invalid="!!errorMessage"
19
- :aria-describedby="ariaDescribedby"
20
- @input="handleInput"
21
- @focus="handleFocus"
22
- @blur="handleBlur"
23
- @keyup="checkCapsLock"
24
- />
25
-
26
- <button
27
- type="button"
28
- class="datametria-password-input__toggle"
29
- :aria-label="showPassword ? 'Ocultar senha' : 'Mostrar senha'"
30
- @click="toggleVisibility"
31
- tabindex="-1"
32
- >
33
- <svg v-if="showPassword" class="datametria-password-input__icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
34
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
35
- </svg>
36
- <svg v-else class="datametria-password-input__icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
37
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
38
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
39
- </svg>
40
- </button>
41
- </div>
42
-
43
- <!-- Caps Lock Warning -->
44
- <p v-if="capsLockOn" class="datametria-password-input__warning">
45
- ⚠️ Caps Lock está ativado
46
- </p>
47
-
48
- <!-- Strength Indicator -->
49
- <div v-if="showStrength && modelValue" class="datametria-password-input__strength">
50
- <div class="datametria-password-input__strength-bar">
51
- <div
52
- :class="strengthBarClasses"
53
- :style="{ width: `${strengthPercentage}%` }"
54
- ></div>
55
- </div>
56
- <span :class="strengthTextClasses">{{ strengthText }}</span>
57
- </div>
58
-
59
- <!-- Requirements -->
60
- <div v-if="showRequirements && isFocused" :id="`${inputId}-requirements`" class="datametria-password-input__requirements">
61
- <p class="datametria-password-input__requirements-title">A senha deve conter:</p>
62
- <ul class="datametria-password-input__requirements-list">
63
- <li :class="{ 'valid': requirements.minLength }">
64
- <span class="datametria-password-input__check">{{ requirements.minLength ? '✓' : '○' }}</span>
65
- Mínimo {{ minLength }} caracteres
66
- </li>
67
- <li :class="{ 'valid': requirements.hasUppercase }">
68
- <span class="datametria-password-input__check">{{ requirements.hasUppercase ? '✓' : '○' }}</span>
69
- Pelo menos 1 letra maiúscula
70
- </li>
71
- <li :class="{ 'valid': requirements.hasLowercase }">
72
- <span class="datametria-password-input__check">{{ requirements.hasLowercase ? '✓' : '○' }}</span>
73
- Pelo menos 1 letra minúscula
74
- </li>
75
- <li :class="{ 'valid': requirements.hasNumber }">
76
- <span class="datametria-password-input__check">{{ requirements.hasNumber ? '✓' : '○' }}</span>
77
- Pelo menos 1 número
78
- </li>
79
- <li :class="{ 'valid': requirements.hasSpecial }">
80
- <span class="datametria-password-input__check">{{ requirements.hasSpecial ? '✓' : '○' }}</span>
81
- Pelo menos 1 caractere especial
82
- </li>
83
- </ul>
84
- </div>
85
-
86
- <!-- Error Message -->
87
- <p v-if="errorMessage" :id="`${inputId}-error`" class="datametria-password-input__error">
88
- {{ errorMessage }}
89
- </p>
90
-
91
- <!-- Help Text -->
92
- <p v-if="helpText && !errorMessage" :id="`${inputId}-help`" class="datametria-password-input__help">
93
- {{ helpText }}
94
- </p>
95
- </div>
96
- </template>
97
-
98
- <script setup lang="ts">
99
- import { computed, ref, watch } from 'vue'
100
-
101
- interface Props {
102
- modelValue?: string
103
- label?: string
104
- placeholder?: string
105
- errorMessage?: string
106
- helpText?: string
107
- disabled?: boolean
108
- required?: boolean
109
- minLength?: number
110
- showStrength?: boolean
111
- showRequirements?: boolean
112
- autocomplete?: 'current-password' | 'new-password'
113
- }
114
-
115
- const props = withDefaults(defineProps<Props>(), {
116
- modelValue: '',
117
- disabled: false,
118
- required: false,
119
- minLength: 8,
120
- showStrength: true,
121
- showRequirements: true,
122
- autocomplete: 'current-password'
123
- })
124
-
125
- const emit = defineEmits<{
126
- 'update:modelValue': [value: string]
127
- 'strength-change': [strength: number]
128
- }>()
129
-
130
- const inputId = computed(() => `password-${Math.random().toString(36).substr(2, 9)}`)
131
- const showPassword = ref(false)
132
- const isFocused = ref(false)
133
- const capsLockOn = ref(false)
134
-
135
- // Requirements validation
136
- const requirements = computed(() => ({
137
- minLength: props.modelValue.length >= props.minLength,
138
- hasUppercase: /[A-Z]/.test(props.modelValue),
139
- hasLowercase: /[a-z]/.test(props.modelValue),
140
- hasNumber: /\d/.test(props.modelValue),
141
- hasSpecial: /[!@#$%^&*(),.?":{}|<>]/.test(props.modelValue)
142
- }))
143
-
144
- // Password strength calculation
145
- const strength = computed(() => {
146
- if (!props.modelValue) return 0
147
-
148
- let score = 0
149
- const reqs = requirements.value
150
-
151
- if (reqs.minLength) score += 20
152
- if (reqs.hasUppercase) score += 20
153
- if (reqs.hasLowercase) score += 20
154
- if (reqs.hasNumber) score += 20
155
- if (reqs.hasSpecial) score += 20
156
-
157
- return score
158
- })
159
-
160
- const strengthPercentage = computed(() => strength.value)
161
-
162
- const strengthText = computed(() => {
163
- const s = strength.value
164
- if (s === 0) return ''
165
- if (s <= 40) return 'Fraca'
166
- if (s <= 60) return 'Média'
167
- if (s <= 80) return 'Boa'
168
- return 'Forte'
169
- })
170
-
171
- const strengthBarClasses = computed(() => [
172
- 'datametria-password-input__strength-fill',
173
- {
174
- 'datametria-password-input__strength-fill--weak': strength.value <= 40,
175
- 'datametria-password-input__strength-fill--medium': strength.value > 40 && strength.value <= 60,
176
- 'datametria-password-input__strength-fill--good': strength.value > 60 && strength.value <= 80,
177
- 'datametria-password-input__strength-fill--strong': strength.value > 80
178
- }
179
- ])
180
-
181
- const strengthTextClasses = computed(() => [
182
- 'datametria-password-input__strength-text',
183
- {
184
- 'datametria-password-input__strength-text--weak': strength.value <= 40,
185
- 'datametria-password-input__strength-text--medium': strength.value > 40 && strength.value <= 60,
186
- 'datametria-password-input__strength-text--good': strength.value > 60 && strength.value <= 80,
187
- 'datametria-password-input__strength-text--strong': strength.value > 80
188
- }
189
- ])
190
-
191
- const inputClasses = computed(() => [
192
- 'datametria-password-input__field',
193
- {
194
- 'datametria-password-input__field--error': props.errorMessage,
195
- 'datametria-password-input__field--disabled': props.disabled
196
- }
197
- ])
198
-
199
- const ariaDescribedby = computed(() => {
200
- const ids = []
201
- if (props.showRequirements && isFocused.value) ids.push(`${inputId.value}-requirements`)
202
- if (props.errorMessage) ids.push(`${inputId.value}-error`)
203
- if (props.helpText && !props.errorMessage) ids.push(`${inputId.value}-help`)
204
- return ids.length > 0 ? ids.join(' ') : undefined
205
- })
206
-
207
- const handleInput = (event: Event) => {
208
- const value = (event.target as HTMLInputElement).value
209
- emit('update:modelValue', value)
210
- }
211
-
212
- const handleFocus = () => {
213
- isFocused.value = true
214
- }
215
-
216
- const handleBlur = () => {
217
- isFocused.value = false
218
- }
219
-
220
- const toggleVisibility = () => {
221
- showPassword.value = !showPassword.value
222
- }
223
-
224
- const checkCapsLock = (event: KeyboardEvent) => {
225
- capsLockOn.value = event.getModifierState('CapsLock')
226
- }
227
-
228
- // Watch strength changes and emit
229
- watch(strength, (newStrength) => {
230
- if (props.modelValue) {
231
- emit('strength-change', newStrength)
232
- }
233
- })
234
- </script>
235
-
236
- <style scoped>
237
- .datametria-password-input {
238
- display: flex;
239
- flex-direction: column;
240
- gap: var(--dm-spacing-2, 0.5rem);
241
- }
242
-
243
- .datametria-password-input__label {
244
- font-size: var(--dm-font-size-sm, 0.875rem);
245
- font-weight: var(--dm-font-weight-medium, 500);
246
- color: var(--dm-text-primary, #374151);
247
- }
248
-
249
- .datametria-password-input__required {
250
- color: var(--dm-error, #ef4444);
251
- }
252
-
253
- .datametria-password-input__wrapper {
254
- position: relative;
255
- display: flex;
256
- align-items: center;
257
- }
258
-
259
- .datametria-password-input__field {
260
- width: 100%;
261
- padding: var(--dm-spacing-3, 0.75rem);
262
- padding-right: var(--dm-spacing-12, 3rem);
263
- border: 1px solid var(--dm-border-color, #d1d5db);
264
- border-radius: var(--dm-radius-md, 0.375rem);
265
- font-size: var(--dm-font-size-base, 1rem);
266
- background: var(--dm-bg-color, #ffffff);
267
- color: var(--dm-text-primary, #111827);
268
- transition: all var(--dm-transition-base, 0.2s);
269
- }
270
-
271
- .datametria-password-input__field:focus {
272
- outline: none;
273
- border-color: var(--dm-primary, #0072CE);
274
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--dm-primary, #0072CE) 10%, transparent);
275
- }
276
-
277
- .datametria-password-input__field--error {
278
- border-color: var(--dm-error, #ef4444);
279
- }
280
-
281
- .datametria-password-input__field--error:focus {
282
- border-color: var(--dm-error, #ef4444);
283
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--dm-error, #ef4444) 10%, transparent);
284
- }
285
-
286
- .datametria-password-input__field--disabled {
287
- background: var(--dm-bg-disabled, #f3f4f6);
288
- cursor: not-allowed;
289
- }
290
-
291
- .datametria-password-input__toggle {
292
- position: absolute;
293
- right: var(--dm-spacing-3, 0.75rem);
294
- background: none;
295
- border: none;
296
- cursor: pointer;
297
- padding: var(--dm-spacing-1, 0.25rem);
298
- color: var(--dm-text-secondary, #6b7280);
299
- transition: color var(--dm-transition-base, 0.2s);
300
- }
301
-
302
- .datametria-password-input__toggle:hover {
303
- color: var(--dm-text-primary, #374151);
304
- }
305
-
306
- .datametria-password-input__toggle:focus {
307
- outline: 2px solid var(--dm-primary, #0072CE);
308
- outline-offset: 2px;
309
- border-radius: var(--dm-radius-sm, 0.25rem);
310
- }
311
-
312
- .datametria-password-input__icon {
313
- width: 1.25rem;
314
- height: 1.25rem;
315
- }
316
-
317
- .datametria-password-input__warning {
318
- font-size: var(--dm-font-size-sm, 0.875rem);
319
- color: var(--dm-warning, #f59e0b);
320
- margin: 0;
321
- }
322
-
323
- .datametria-password-input__strength {
324
- display: flex;
325
- align-items: center;
326
- gap: var(--dm-spacing-3, 0.75rem);
327
- }
328
-
329
- .datametria-password-input__strength-bar {
330
- flex: 1;
331
- height: var(--dm-spacing-2, 0.5rem);
332
- background: var(--dm-bg-secondary, #e5e7eb);
333
- border-radius: var(--dm-radius-sm, 0.25rem);
334
- overflow: hidden;
335
- }
336
-
337
- .datametria-password-input__strength-fill {
338
- height: 100%;
339
- transition: width var(--dm-transition-slow, 0.3s) ease, background-color var(--dm-transition-slow, 0.3s) ease;
340
- border-radius: var(--dm-radius-sm, 0.25rem);
341
- }
342
-
343
- .datametria-password-input__strength-fill--weak {
344
- background: var(--dm-error, #ef4444);
345
- }
346
-
347
- .datametria-password-input__strength-fill--medium {
348
- background: var(--dm-warning, #f59e0b);
349
- }
350
-
351
- .datametria-password-input__strength-fill--good {
352
- background: var(--dm-info, #3b82f6);
353
- }
354
-
355
- .datametria-password-input__strength-fill--strong {
356
- background: var(--dm-success, #10b981);
357
- }
358
-
359
- .datametria-password-input__strength-text {
360
- font-size: var(--dm-font-size-sm, 0.875rem);
361
- font-weight: var(--dm-font-weight-medium, 500);
362
- min-width: 4rem;
363
- }
364
-
365
- .datametria-password-input__strength-text--weak {
366
- color: var(--dm-error, #ef4444);
367
- }
368
-
369
- .datametria-password-input__strength-text--medium {
370
- color: var(--dm-warning, #f59e0b);
371
- }
372
-
373
- .datametria-password-input__strength-text--good {
374
- color: var(--dm-info, #3b82f6);
375
- }
376
-
377
- .datametria-password-input__strength-text--strong {
378
- color: var(--dm-success, #10b981);
379
- }
380
-
381
- .datametria-password-input__requirements {
382
- padding: var(--dm-spacing-3, 0.75rem);
383
- background: var(--dm-bg-secondary, #f9fafb);
384
- border: 1px solid var(--dm-border-color, #e5e7eb);
385
- border-radius: var(--dm-radius-md, 0.375rem);
386
- }
387
-
388
- .datametria-password-input__requirements-title {
389
- font-size: var(--dm-font-size-sm, 0.875rem);
390
- font-weight: var(--dm-font-weight-medium, 500);
391
- color: var(--dm-text-primary, #374151);
392
- margin: 0 0 var(--dm-spacing-2, 0.5rem) 0;
393
- }
394
-
395
- .datametria-password-input__requirements-list {
396
- list-style: none;
397
- padding: 0;
398
- margin: 0;
399
- display: flex;
400
- flex-direction: column;
401
- gap: var(--dm-spacing-1, 0.25rem);
402
- }
403
-
404
- .datametria-password-input__requirements-list li {
405
- font-size: var(--dm-font-size-sm, 0.875rem);
406
- color: var(--dm-text-secondary, #6b7280);
407
- display: flex;
408
- align-items: center;
409
- gap: var(--dm-spacing-2, 0.5rem);
410
- }
411
-
412
- .datametria-password-input__requirements-list li.valid {
413
- color: var(--dm-success, #10b981);
414
- }
415
-
416
- .datametria-password-input__check {
417
- font-weight: var(--dm-font-weight-semibold, 600);
418
- min-width: 1rem;
419
- }
420
-
421
- .datametria-password-input__error {
422
- font-size: var(--dm-font-size-sm, 0.875rem);
423
- color: var(--dm-error, #ef4444);
424
- margin: 0;
425
- }
426
-
427
- .datametria-password-input__help {
428
- font-size: var(--dm-font-size-sm, 0.875rem);
429
- color: var(--dm-text-secondary, #6b7280);
430
- margin: 0;
431
- }
432
-
433
- /* Dark Mode Support - Hybrid Approach */
434
-
435
- /* Fallback automático (sem JS) */
436
- @media (prefers-color-scheme: dark) {
437
- .datametria-password-input {
438
- background: var(--dm-bg-color-dark, #1e1e1e);
439
- color: var(--dm-text-primary-dark, #e0e0e0);
440
- border-color: var(--dm-border-color-dark, #404040);
1
+ <template>
2
+ <div class="datametria-password-input" :class="wrapperClasses">
3
+ <label v-if="label" :for="inputId" class="datametria-password-input__label">
4
+ {{ label }}
5
+ <span v-if="required" class="datametria-password-input__required">*</span>
6
+ </label>
7
+
8
+ <div class="datametria-password-input__wrapper">
9
+ <input
10
+ :id="inputId"
11
+ :value="modelValue"
12
+ :type="showPassword ? 'text' : 'password'"
13
+ :placeholder="placeholder"
14
+ :disabled="disabled"
15
+ :required="required"
16
+ :autocomplete="autocomplete"
17
+ :class="inputClasses"
18
+ :aria-label="label || placeholder"
19
+ :aria-required="required"
20
+ :aria-invalid="!!errorMessage"
21
+ :aria-describedby="ariaDescribedby"
22
+ @input="handleInput"
23
+ @focus="handleFocus"
24
+ @blur="handleBlur"
25
+ @keyup="checkCapsLock"
26
+ />
27
+
28
+ <button
29
+ type="button"
30
+ class="datametria-password-input__toggle"
31
+ :aria-label="showPassword ? 'Ocultar senha' : 'Mostrar senha'"
32
+ @click="toggleVisibility"
33
+ >
34
+ <svg v-if="showPassword" class="datametria-password-input__icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
35
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
36
+ </svg>
37
+ <svg v-else class="datametria-password-input__icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
38
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
39
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
40
+ </svg>
41
+ </button>
42
+ </div>
43
+
44
+ <p v-if="capsLockOn" class="datametria-password-input__warning" role="alert">
45
+ ⚠️ Caps Lock está ativado
46
+ </p>
47
+
48
+ <div v-if="showStrength && modelValue" class="datametria-password-input__strength">
49
+ <div class="datametria-password-input__strength-bar">
50
+ <div :class="strengthBarClasses" :style="{ width: `${strengthPercentage}%` }"></div>
51
+ </div>
52
+ <span :class="strengthTextClasses">{{ strengthText }}</span>
53
+ </div>
54
+
55
+ <div v-if="showRequirements && isFocused" :id="`${inputId}-requirements`" class="datametria-password-input__requirements">
56
+ <p class="datametria-password-input__requirements-title">A senha deve conter:</p>
57
+ <ul class="datametria-password-input__requirements-list">
58
+ <li :class="{ 'valid': requirements.minLength }">
59
+ <span class="datametria-password-input__check">{{ requirements.minLength ? '✓' : '○' }}</span>
60
+ Mínimo {{ minLength }} caracteres
61
+ </li>
62
+ <li :class="{ 'valid': requirements.hasUppercase }">
63
+ <span class="datametria-password-input__check">{{ requirements.hasUppercase ? '' : '○' }}</span>
64
+ Pelo menos 1 letra maiúscula
65
+ </li>
66
+ <li :class="{ 'valid': requirements.hasLowercase }">
67
+ <span class="datametria-password-input__check">{{ requirements.hasLowercase ? '' : '○' }}</span>
68
+ Pelo menos 1 letra minúscula
69
+ </li>
70
+ <li :class="{ 'valid': requirements.hasNumber }">
71
+ <span class="datametria-password-input__check">{{ requirements.hasNumber ? '' : '○' }}</span>
72
+ Pelo menos 1 número
73
+ </li>
74
+ <li :class="{ 'valid': requirements.hasSpecial }">
75
+ <span class="datametria-password-input__check">{{ requirements.hasSpecial ? '' : '○' }}</span>
76
+ Pelo menos 1 caractere especial
77
+ </li>
78
+ </ul>
79
+ </div>
80
+
81
+ <p v-if="errorMessage" :id="`${inputId}-error`" role="alert" aria-live="polite" class="datametria-password-input__error">
82
+ {{ errorMessage }}
83
+ </p>
84
+
85
+ <p v-if="helpText && !errorMessage" :id="`${inputId}-help`" class="datametria-password-input__help">
86
+ {{ helpText }}
87
+ </p>
88
+ </div>
89
+ </template>
90
+
91
+ <script setup lang="ts">
92
+ import { computed, ref, watch } from 'vue'
93
+ import { useAnalytics } from '@/composables/useAnalytics'
94
+
95
+ interface Props {
96
+ modelValue?: string
97
+ label?: string
98
+ placeholder?: string
99
+ errorMessage?: string
100
+ helpText?: string
101
+ disabled?: boolean
102
+ required?: boolean
103
+ minLength?: number
104
+ showStrength?: boolean
105
+ showRequirements?: boolean
106
+ autocomplete?: 'current-password' | 'new-password'
107
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
108
+ }
109
+
110
+ const props = withDefaults(defineProps<Props>(), {
111
+ modelValue: '',
112
+ disabled: false,
113
+ required: false,
114
+ minLength: 8,
115
+ showStrength: true,
116
+ showRequirements: true,
117
+ autocomplete: 'current-password',
118
+ size: 'md'
119
+ })
120
+
121
+ const emit = defineEmits<{
122
+ 'update:modelValue': [value: string]
123
+ 'strength-change': [strength: number]
124
+ }>()
125
+
126
+ const { trackEvent } = useAnalytics()
127
+ const inputId = `password-${Math.random().toString(36).substr(2, 9)}`
128
+ const showPassword = ref(false)
129
+ const isFocused = ref(false)
130
+ const capsLockOn = ref(false)
131
+
132
+ const requirements = computed(() => ({
133
+ minLength: props.modelValue.length >= props.minLength,
134
+ hasUppercase: /[A-Z]/.test(props.modelValue),
135
+ hasLowercase: /[a-z]/.test(props.modelValue),
136
+ hasNumber: /\d/.test(props.modelValue),
137
+ hasSpecial: /[!@#$%^&*(),.?":{}|<>]/.test(props.modelValue)
138
+ }))
139
+
140
+ const strength = computed(() => {
141
+ if (!props.modelValue) return 0
142
+ let score = 0
143
+ const reqs = requirements.value
144
+ if (reqs.minLength) score += 20
145
+ if (reqs.hasUppercase) score += 20
146
+ if (reqs.hasLowercase) score += 20
147
+ if (reqs.hasNumber) score += 20
148
+ if (reqs.hasSpecial) score += 20
149
+ return score
150
+ })
151
+
152
+ const strengthPercentage = computed(() => strength.value)
153
+
154
+ const strengthText = computed(() => {
155
+ const s = strength.value
156
+ if (s === 0) return ''
157
+ if (s <= 40) return 'Fraca'
158
+ if (s <= 60) return 'Média'
159
+ if (s <= 80) return 'Boa'
160
+ return 'Forte'
161
+ })
162
+
163
+ const wrapperClasses = computed(() => ({
164
+ [`datametria-password-input--${props.size}`]: true
165
+ }))
166
+
167
+ const strengthBarClasses = computed(() => [
168
+ 'datametria-password-input__strength-fill',
169
+ {
170
+ 'datametria-password-input__strength-fill--weak': strength.value <= 40,
171
+ 'datametria-password-input__strength-fill--medium': strength.value > 40 && strength.value <= 60,
172
+ 'datametria-password-input__strength-fill--good': strength.value > 60 && strength.value <= 80,
173
+ 'datametria-password-input__strength-fill--strong': strength.value > 80
174
+ }
175
+ ])
176
+
177
+ const strengthTextClasses = computed(() => [
178
+ 'datametria-password-input__strength-text',
179
+ {
180
+ 'datametria-password-input__strength-text--weak': strength.value <= 40,
181
+ 'datametria-password-input__strength-text--medium': strength.value > 40 && strength.value <= 60,
182
+ 'datametria-password-input__strength-text--good': strength.value > 60 && strength.value <= 80,
183
+ 'datametria-password-input__strength-text--strong': strength.value > 80
184
+ }
185
+ ])
186
+
187
+ const inputClasses = computed(() => [
188
+ 'datametria-password-input__field',
189
+ {
190
+ 'datametria-password-input__field--error': props.errorMessage,
191
+ 'datametria-password-input__field--disabled': props.disabled
441
192
  }
193
+ ])
194
+
195
+ const ariaDescribedby = computed(() => {
196
+ const ids = []
197
+ if (props.showRequirements && isFocused.value) ids.push(`${inputId}-requirements`)
198
+ if (props.errorMessage) ids.push(`${inputId}-error`)
199
+ if (props.helpText && !props.errorMessage) ids.push(`${inputId}-help`)
200
+ return ids.length > 0 ? ids.join(' ') : undefined
201
+ })
202
+
203
+ const handleInput = (event: Event) => {
204
+ const value = (event.target as HTMLInputElement).value
205
+ emit('update:modelValue', value)
206
+ }
207
+
208
+ const handleFocus = () => {
209
+ isFocused.value = true
210
+ trackEvent('password_input_focus', {
211
+ component: 'DatametriaPasswordInput',
212
+ size: props.size
213
+ })
214
+ }
215
+
216
+ const handleBlur = () => {
217
+ isFocused.value = false
218
+ }
219
+
220
+ const toggleVisibility = () => {
221
+ showPassword.value = !showPassword.value
222
+ trackEvent('password_visibility_toggle', {
223
+ component: 'DatametriaPasswordInput',
224
+ visible: showPassword.value
225
+ })
226
+ }
227
+
228
+ const checkCapsLock = (event: KeyboardEvent) => {
229
+ capsLockOn.value = event.getModifierState('CapsLock')
230
+ }
231
+
232
+ watch(strength, (newStrength) => {
233
+ if (props.modelValue) {
234
+ emit('strength-change', newStrength)
235
+ }
236
+ })
237
+ </script>
238
+
239
+ <style scoped>
240
+ .datametria-password-input {
241
+ display: flex;
242
+ flex-direction: column;
243
+ gap: 8px;
244
+ width: 100%;
245
+ }
246
+
247
+ .datametria-password-input__label {
248
+ font-size: 14px;
249
+ font-weight: 500;
250
+ color: var(--color-text-primary, #1f2937);
251
+ }
252
+
253
+ .datametria-password-input__required {
254
+ color: var(--color-error, #ef4444);
255
+ }
256
+
257
+ .datametria-password-input__wrapper {
258
+ position: relative;
259
+ display: flex;
260
+ align-items: center;
261
+ }
262
+
263
+ .datametria-password-input__field {
264
+ width: 100%;
265
+ padding: 8px 40px 8px 12px;
266
+ border: 1px solid var(--color-border, #d1d5db);
267
+ border-radius: 6px;
268
+ background: var(--color-background, #fff);
269
+ color: var(--color-text-primary, #1f2937);
270
+ font-size: 14px;
271
+ transition: all 0.2s;
272
+ touch-action: manipulation;
273
+ -webkit-tap-highlight-color: transparent;
274
+ }
275
+
276
+ .datametria-password-input__field:hover:not(:disabled) {
277
+ border-color: var(--color-primary, #3b82f6);
278
+ }
279
+
280
+ .datametria-password-input__field:focus {
281
+ outline: none;
282
+ border-color: var(--color-primary, #3b82f6);
283
+ box-shadow: 0 0 0 3px var(--color-primary-alpha, rgba(59, 130, 246, 0.1));
284
+ }
285
+
286
+ .datametria-password-input__field--error {
287
+ border-color: var(--color-error, #ef4444);
288
+ }
289
+
290
+ .datametria-password-input__field--disabled {
291
+ background: var(--color-background-muted, #f3f4f6);
292
+ cursor: not-allowed;
293
+ opacity: 0.6;
294
+ }
295
+
296
+ .datametria-password-input__toggle {
297
+ position: absolute;
298
+ right: 8px;
299
+ background: none;
300
+ border: none;
301
+ cursor: pointer;
302
+ padding: 8px;
303
+ min-width: 44px;
304
+ min-height: 44px;
305
+ display: flex;
306
+ align-items: center;
307
+ justify-content: center;
308
+ }
309
+
310
+ .datametria-password-input__icon {
311
+ width: 20px;
312
+ height: 20px;
313
+ color: var(--color-text-secondary, #6b7280);
314
+ }
315
+
316
+ .datametria-password-input__warning {
317
+ font-size: 12px;
318
+ color: var(--color-warning, #f59e0b);
319
+ }
320
+
321
+ .datametria-password-input__strength {
322
+ display: flex;
323
+ align-items: center;
324
+ gap: 8px;
325
+ }
326
+
327
+ .datametria-password-input__strength-bar {
328
+ flex: 1;
329
+ height: 4px;
330
+ background: var(--color-background-muted, #f3f4f6);
331
+ border-radius: 2px;
332
+ overflow: hidden;
333
+ }
334
+
335
+ .datametria-password-input__strength-fill {
336
+ height: 100%;
337
+ transition: width 0.3s, background-color 0.3s;
338
+ }
339
+
340
+ .datametria-password-input__strength-fill--weak { background: var(--color-error, #ef4444); }
341
+ .datametria-password-input__strength-fill--medium { background: var(--color-warning, #f59e0b); }
342
+ .datametria-password-input__strength-fill--good { background: var(--color-info, #3b82f6); }
343
+ .datametria-password-input__strength-fill--strong { background: var(--color-success, #10b981); }
344
+
345
+ .datametria-password-input__strength-text {
346
+ font-size: 12px;
347
+ font-weight: 500;
348
+ }
349
+
350
+ .datametria-password-input__strength-text--weak { color: var(--color-error, #ef4444); }
351
+ .datametria-password-input__strength-text--medium { color: var(--color-warning, #f59e0b); }
352
+ .datametria-password-input__strength-text--good { color: var(--color-info, #3b82f6); }
353
+ .datametria-password-input__strength-text--strong { color: var(--color-success, #10b981); }
354
+
355
+ .datametria-password-input__requirements {
356
+ padding: 12px;
357
+ background: var(--color-background-alt, #f9fafb);
358
+ border-radius: 6px;
359
+ font-size: 12px;
360
+ }
361
+
362
+ .datametria-password-input__requirements-title {
363
+ font-weight: 500;
364
+ margin-bottom: 8px;
365
+ color: var(--color-text-primary, #1f2937);
366
+ }
367
+
368
+ .datametria-password-input__requirements-list {
369
+ list-style: none;
370
+ padding: 0;
371
+ margin: 0;
372
+ display: flex;
373
+ flex-direction: column;
374
+ gap: 4px;
375
+ }
376
+
377
+ .datametria-password-input__requirements-list li {
378
+ color: var(--color-text-secondary, #6b7280);
379
+ }
380
+
381
+ .datametria-password-input__requirements-list li.valid {
382
+ color: var(--color-success, #10b981);
383
+ }
384
+
385
+ .datametria-password-input__check {
386
+ margin-right: 4px;
387
+ }
388
+
389
+ .datametria-password-input__error {
390
+ font-size: 12px;
391
+ color: var(--color-error, #ef4444);
392
+ }
393
+
394
+ .datametria-password-input__help {
395
+ font-size: 12px;
396
+ color: var(--color-text-secondary, #6b7280);
397
+ }
398
+
399
+ /* Tamanhos */
400
+ .datametria-password-input--xs .datametria-password-input__field {
401
+ padding: 2px 36px 2px 6px;
402
+ font-size: 11px;
403
+ min-height: 1.75rem;
404
+ }
405
+
406
+ .datametria-password-input--sm .datametria-password-input__field {
407
+ padding: 4px 38px 4px 8px;
408
+ font-size: 12px;
409
+ min-height: 2rem;
410
+ }
411
+
412
+ .datametria-password-input--md .datametria-password-input__field {
413
+ padding: 8px 40px 8px 12px;
414
+ font-size: 14px;
415
+ min-height: 2.5rem;
416
+ }
417
+
418
+ .datametria-password-input--lg .datametria-password-input__field {
419
+ padding: 12px 44px 12px 16px;
420
+ font-size: 16px;
421
+ min-height: 3rem;
422
+ }
423
+
424
+ .datametria-password-input--xl .datametria-password-input__field {
425
+ padding: 16px 48px 16px 20px;
426
+ font-size: 18px;
427
+ min-height: 3.5rem;
442
428
  }
443
429
 
444
- /* Controle manual via useTheme() */
445
- [data-theme="dark"] .datametria-password-input {
446
- background: var(--dm-bg-color-dark, #1e1e1e);
447
- color: var(--dm-text-primary-dark, #e0e0e0);
448
- border-color: var(--dm-border-color-dark, #404040);
430
+ @media (max-width: 640px) {
431
+ .datametria-password-input--xs .datametria-password-input__field { min-height: 2rem; }
432
+ .datametria-password-input--sm .datametria-password-input__field { min-height: 2.25rem; }
433
+ .datametria-password-input--md .datametria-password-input__field { min-height: 2.75rem; }
434
+ .datametria-password-input--lg .datametria-password-input__field { min-height: 3.25rem; }
435
+ .datametria-password-input--xl .datametria-password-input__field { min-height: 3.75rem; }
449
436
  }
450
- </style>
437
+ </style>