@datametria/vue-components 2.3.1 → 2.4.1

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 (126) hide show
  1. package/README.md +625 -594
  2. package/dist/index.es.js +1962 -1887
  3. package/dist/index.umd.js +6 -6
  4. package/dist/src/components/DatametriaForm.vue.d.ts +1 -1
  5. package/dist/src/components/DatametriaInput.vue.d.ts +9 -1
  6. package/dist/src/components/DatametriaSelect.vue.d.ts +10 -1
  7. package/dist/vue-components.css +1 -1
  8. package/package.json +103 -102
  9. package/src/components/DatametriaAlert.vue +151 -133
  10. package/src/components/DatametriaAutocomplete.vue +250 -229
  11. package/src/components/DatametriaAvatar.vue +256 -238
  12. package/src/components/DatametriaBadge.vue +101 -96
  13. package/src/components/DatametriaBreadcrumb.vue +132 -128
  14. package/src/components/DatametriaButton.vue +191 -173
  15. package/src/components/DatametriaCard.vue +84 -66
  16. package/src/components/DatametriaCheckbox.vue +197 -193
  17. package/src/components/DatametriaChip.vue +159 -141
  18. package/src/components/DatametriaContainer.vue +70 -52
  19. package/src/components/DatametriaDataTable.vue +318 -300
  20. package/src/components/DatametriaDatePicker.vue +396 -378
  21. package/src/components/DatametriaDialog.vue +297 -293
  22. package/src/components/DatametriaDivider.vue +105 -98
  23. package/src/components/DatametriaDropdown.vue +356 -350
  24. package/src/components/DatametriaEmpty.vue +155 -151
  25. package/src/components/DatametriaFileUpload.vue +413 -395
  26. package/src/components/DatametriaFloatingBar.vue +144 -126
  27. package/src/components/DatametriaForm.vue +174 -156
  28. package/src/components/DatametriaFormItem.vue +183 -179
  29. package/src/components/DatametriaGrid.vue +55 -37
  30. package/src/components/DatametriaInput.vue +314 -263
  31. package/src/components/DatametriaMenu.vue +618 -600
  32. package/src/components/DatametriaModal.vue +147 -129
  33. package/src/components/DatametriaNavbar.vue +277 -223
  34. package/src/components/DatametriaPagination.vue +375 -371
  35. package/src/components/DatametriaPasswordInput.vue +446 -426
  36. package/src/components/DatametriaPopconfirm.vue +240 -234
  37. package/src/components/DatametriaProgress.vue +228 -224
  38. package/src/components/DatametriaRadio.vue +151 -147
  39. package/src/components/DatametriaResult.vue +135 -131
  40. package/src/components/DatametriaSelect.vue +311 -211
  41. package/src/components/DatametriaSidebar.vue +294 -222
  42. package/src/components/DatametriaSkeleton.vue +257 -234
  43. package/src/components/DatametriaSlider.vue +409 -391
  44. package/src/components/DatametriaSortableTable.vue +826 -802
  45. package/src/components/DatametriaSpinner.vue +114 -110
  46. package/src/components/DatametriaSteps.vue +318 -312
  47. package/src/components/DatametriaSwitch.vue +146 -142
  48. package/src/components/DatametriaTabPane.vue +94 -76
  49. package/src/components/DatametriaTable.vue +118 -100
  50. package/src/components/DatametriaTabs.vue +315 -297
  51. package/src/components/DatametriaTextarea.vue +213 -195
  52. package/src/components/DatametriaTimePicker.vue +317 -299
  53. package/src/components/DatametriaToast.vue +176 -176
  54. package/src/components/DatametriaTooltip.vue +421 -400
  55. package/src/components/DatametriaTree.vue +126 -122
  56. package/src/components/DatametriaTreeNode.vue +176 -172
  57. package/src/components/DatametriaUpload.vue +379 -361
  58. package/src/components/__tests__/DatametriaAlert.test.js +35 -35
  59. package/src/components/__tests__/DatametriaAlert.test.ts +190 -190
  60. package/src/components/__tests__/DatametriaAvatar.test.ts +151 -151
  61. package/src/components/__tests__/DatametriaBadge.test.js +29 -29
  62. package/src/components/__tests__/DatametriaBadge.test.ts +167 -167
  63. package/src/components/__tests__/DatametriaBreadcrumb.test.ts +187 -0
  64. package/src/components/__tests__/DatametriaButton.test.js +30 -30
  65. package/src/components/__tests__/DatametriaButton.test.ts +283 -283
  66. package/src/components/__tests__/DatametriaCard.test.ts +201 -201
  67. package/src/components/__tests__/DatametriaCheckbox.test.ts +204 -0
  68. package/src/components/__tests__/DatametriaChip.test.js +38 -38
  69. package/src/components/__tests__/DatametriaContainer.test.ts +52 -52
  70. package/src/components/__tests__/DatametriaDialog.test.ts +338 -0
  71. package/src/components/__tests__/DatametriaDivider.test.ts +54 -54
  72. package/src/components/__tests__/DatametriaDropdown.test.ts +357 -0
  73. package/src/components/__tests__/DatametriaEmpty.test.ts +261 -0
  74. package/src/components/__tests__/DatametriaFileUpload.test.ts +290 -290
  75. package/src/components/__tests__/DatametriaFloatingBar.test.ts +137 -137
  76. package/src/components/__tests__/DatametriaForm.test.ts +96 -0
  77. package/src/components/__tests__/DatametriaFormItem.test.ts +58 -0
  78. package/src/components/__tests__/DatametriaGrid.test.ts +31 -31
  79. package/src/components/__tests__/DatametriaInput.test.ts +72 -72
  80. package/src/components/__tests__/DatametriaMenu.test.ts +366 -366
  81. package/src/components/__tests__/DatametriaModal.test.ts +86 -86
  82. package/src/components/__tests__/DatametriaNavbar.test.js +48 -48
  83. package/src/components/__tests__/DatametriaNavbar.test.ts +203 -203
  84. package/src/components/__tests__/DatametriaPasswordInput.test.js +305 -305
  85. package/src/components/__tests__/DatametriaRadio.test.ts +195 -0
  86. package/src/components/__tests__/DatametriaSelect.test.ts +77 -77
  87. package/src/components/__tests__/DatametriaSidebar.test.ts +169 -169
  88. package/src/components/__tests__/DatametriaSlider.test.ts +261 -261
  89. package/src/components/__tests__/DatametriaSortableTable.test.js +168 -168
  90. package/src/components/__tests__/DatametriaSpinner.test.ts +156 -156
  91. package/src/components/__tests__/DatametriaSteps.test.ts +211 -0
  92. package/src/components/__tests__/DatametriaSwitch.test.ts +129 -0
  93. package/src/components/__tests__/DatametriaTabPane.test.ts +205 -0
  94. package/src/components/__tests__/DatametriaTable.test.ts +97 -97
  95. package/src/components/__tests__/DatametriaTabs.test.ts +232 -232
  96. package/src/components/__tests__/DatametriaToast.test.js +48 -48
  97. package/src/components/__tests__/DatametriaToast.test.ts +99 -99
  98. package/src/components/__tests__/DatametriaTree.test.ts +376 -0
  99. package/src/components/__tests__/index.test.ts +48 -0
  100. package/src/composables/useAccessibilityScale.ts +94 -94
  101. package/src/composables/useBreakpoints.ts +82 -82
  102. package/src/composables/useHapticFeedback.ts +439 -439
  103. package/src/composables/useRipple.ts +218 -218
  104. package/src/composables/useTheme.ts +5 -1
  105. package/src/index.ts +84 -84
  106. package/src/stories/Variants.stories.js +95 -95
  107. package/src/styles/design-tokens.css +623 -623
  108. package/src/theme/ThemeProvider.vue +96 -96
  109. package/src/theme/__tests__/ThemeProvider.test.ts +208 -208
  110. package/src/theme/__tests__/constants.test.ts +31 -31
  111. package/src/theme/__tests__/presets.test.ts +166 -166
  112. package/src/theme/__tests__/tokens.test.ts +155 -155
  113. package/src/theme/__tests__/types.test.ts +153 -153
  114. package/src/theme/__tests__/useTheme.test.ts +146 -146
  115. package/src/theme/constants.ts +14 -14
  116. package/src/theme/index.ts +12 -12
  117. package/src/theme/presets/datametria.ts +94 -94
  118. package/src/theme/presets/default.ts +94 -94
  119. package/src/theme/presets/index.ts +8 -8
  120. package/src/theme/tokens/colors.ts +28 -28
  121. package/src/theme/tokens/index.ts +47 -47
  122. package/src/theme/tokens/spacing.ts +21 -21
  123. package/src/theme/tokens/typography.ts +35 -35
  124. package/src/theme/types.ts +111 -111
  125. package/src/theme/useTheme.ts +28 -28
  126. package/src/types/index.ts +55 -55
@@ -1,430 +1,450 @@
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
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);
196
441
  }
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-neutral-700, #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-neutral-300, #d1d5db);
264
- border-radius: var(--dm-radius-md, 0.375rem);
265
- font-size: var(--dm-font-size-base, 1rem);
266
- transition: all var(--dm-transition-base, 0.2s);
267
- }
268
-
269
- .datametria-password-input__field:focus {
270
- outline: none;
271
- border-color: var(--dm-primary, #0072CE);
272
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--dm-primary, #0072CE) 10%, transparent);
273
- }
274
-
275
- .datametria-password-input__field--error {
276
- border-color: var(--dm-error, #ef4444);
277
- }
278
-
279
- .datametria-password-input__field--error:focus {
280
- border-color: var(--dm-error, #ef4444);
281
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--dm-error, #ef4444) 10%, transparent);
282
- }
283
-
284
- .datametria-password-input__field--disabled {
285
- background: var(--dm-neutral-100, #f3f4f6);
286
- cursor: not-allowed;
287
- }
288
-
289
- .datametria-password-input__toggle {
290
- position: absolute;
291
- right: var(--dm-spacing-3, 0.75rem);
292
- background: none;
293
- border: none;
294
- cursor: pointer;
295
- padding: var(--dm-spacing-1, 0.25rem);
296
- color: var(--dm-neutral-500, #6b7280);
297
- transition: color var(--dm-transition-base, 0.2s);
298
- }
299
-
300
- .datametria-password-input__toggle:hover {
301
- color: var(--dm-neutral-700, #374151);
302
- }
303
-
304
- .datametria-password-input__toggle:focus {
305
- outline: 2px solid var(--dm-primary, #0072CE);
306
- outline-offset: 2px;
307
- border-radius: var(--dm-radius-sm, 0.25rem);
308
- }
309
-
310
- .datametria-password-input__icon {
311
- width: 1.25rem;
312
- height: 1.25rem;
313
- }
314
-
315
- .datametria-password-input__warning {
316
- font-size: var(--dm-font-size-sm, 0.875rem);
317
- color: var(--dm-warning, #f59e0b);
318
- margin: 0;
319
- }
320
-
321
- .datametria-password-input__strength {
322
- display: flex;
323
- align-items: center;
324
- gap: var(--dm-spacing-3, 0.75rem);
325
- }
326
-
327
- .datametria-password-input__strength-bar {
328
- flex: 1;
329
- height: var(--dm-spacing-2, 0.5rem);
330
- background: var(--dm-neutral-200, #e5e7eb);
331
- border-radius: var(--dm-radius-sm, 0.25rem);
332
- overflow: hidden;
333
- }
334
-
335
- .datametria-password-input__strength-fill {
336
- height: 100%;
337
- transition: width var(--dm-transition-slow, 0.3s) ease, background-color var(--dm-transition-slow, 0.3s) ease;
338
- border-radius: var(--dm-radius-sm, 0.25rem);
339
- }
340
-
341
- .datametria-password-input__strength-fill--weak {
342
- background: var(--dm-error, #ef4444);
343
- }
344
-
345
- .datametria-password-input__strength-fill--medium {
346
- background: var(--dm-warning, #f59e0b);
347
- }
348
-
349
- .datametria-password-input__strength-fill--good {
350
- background: var(--dm-info, #3b82f6);
351
- }
352
-
353
- .datametria-password-input__strength-fill--strong {
354
- background: var(--dm-success, #10b981);
355
- }
356
-
357
- .datametria-password-input__strength-text {
358
- font-size: var(--dm-font-size-sm, 0.875rem);
359
- font-weight: var(--dm-font-weight-medium, 500);
360
- min-width: 4rem;
361
- }
362
-
363
- .datametria-password-input__strength-text--weak {
364
- color: var(--dm-error, #ef4444);
365
- }
366
-
367
- .datametria-password-input__strength-text--medium {
368
- color: var(--dm-warning, #f59e0b);
369
- }
370
-
371
- .datametria-password-input__strength-text--good {
372
- color: var(--dm-info, #3b82f6);
373
- }
374
-
375
- .datametria-password-input__strength-text--strong {
376
- color: var(--dm-success, #10b981);
377
- }
378
-
379
- .datametria-password-input__requirements {
380
- padding: var(--dm-spacing-3, 0.75rem);
381
- background: var(--dm-neutral-50, #f9fafb);
382
- border: 1px solid var(--dm-neutral-200, #e5e7eb);
383
- border-radius: var(--dm-radius-md, 0.375rem);
384
- }
385
-
386
- .datametria-password-input__requirements-title {
387
- font-size: var(--dm-font-size-sm, 0.875rem);
388
- font-weight: var(--dm-font-weight-medium, 500);
389
- color: var(--dm-neutral-700, #374151);
390
- margin: 0 0 var(--dm-spacing-2, 0.5rem) 0;
391
- }
392
-
393
- .datametria-password-input__requirements-list {
394
- list-style: none;
395
- padding: 0;
396
- margin: 0;
397
- display: flex;
398
- flex-direction: column;
399
- gap: var(--dm-spacing-1, 0.25rem);
400
- }
401
-
402
- .datametria-password-input__requirements-list li {
403
- font-size: var(--dm-font-size-sm, 0.875rem);
404
- color: var(--dm-neutral-500, #6b7280);
405
- display: flex;
406
- align-items: center;
407
- gap: var(--dm-spacing-2, 0.5rem);
408
- }
409
-
410
- .datametria-password-input__requirements-list li.valid {
411
- color: var(--dm-success, #10b981);
412
- }
413
-
414
- .datametria-password-input__check {
415
- font-weight: var(--dm-font-weight-semibold, 600);
416
- min-width: 1rem;
417
- }
418
-
419
- .datametria-password-input__error {
420
- font-size: var(--dm-font-size-sm, 0.875rem);
421
- color: var(--dm-error, #ef4444);
422
- margin: 0;
423
442
  }
424
443
 
425
- .datametria-password-input__help {
426
- font-size: var(--dm-font-size-sm, 0.875rem);
427
- color: var(--dm-neutral-500, #6b7280);
428
- margin: 0;
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);
429
449
  }
430
- </style>
450
+ </style>