@datametria/vue-components 2.3.1 → 2.4.0

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 (128) 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/DatametriaCheckboxGroup.vue +56 -38
  18. package/src/components/DatametriaChip.vue +159 -141
  19. package/src/components/DatametriaContainer.vue +70 -52
  20. package/src/components/DatametriaDataTable.vue +318 -300
  21. package/src/components/DatametriaDatePicker.vue +396 -378
  22. package/src/components/DatametriaDialog.vue +297 -293
  23. package/src/components/DatametriaDivider.vue +105 -98
  24. package/src/components/DatametriaDropdown.vue +356 -350
  25. package/src/components/DatametriaEmpty.vue +155 -151
  26. package/src/components/DatametriaFileUpload.vue +413 -395
  27. package/src/components/DatametriaFloatingBar.vue +144 -126
  28. package/src/components/DatametriaForm.vue +174 -156
  29. package/src/components/DatametriaFormItem.vue +183 -179
  30. package/src/components/DatametriaGrid.vue +55 -37
  31. package/src/components/DatametriaInput.vue +314 -263
  32. package/src/components/DatametriaMenu.vue +618 -600
  33. package/src/components/DatametriaModal.vue +147 -129
  34. package/src/components/DatametriaNavbar.vue +277 -223
  35. package/src/components/DatametriaPagination.vue +375 -371
  36. package/src/components/DatametriaPasswordInput.vue +444 -426
  37. package/src/components/DatametriaPopconfirm.vue +240 -234
  38. package/src/components/DatametriaProgress.vue +228 -224
  39. package/src/components/DatametriaRadio.vue +151 -147
  40. package/src/components/DatametriaRadioGroup.vue +55 -37
  41. package/src/components/DatametriaResult.vue +135 -131
  42. package/src/components/DatametriaSelect.vue +311 -211
  43. package/src/components/DatametriaSidebar.vue +294 -222
  44. package/src/components/DatametriaSkeleton.vue +257 -234
  45. package/src/components/DatametriaSlider.vue +409 -391
  46. package/src/components/DatametriaSortableTable.vue +820 -802
  47. package/src/components/DatametriaSpinner.vue +114 -110
  48. package/src/components/DatametriaSteps.vue +318 -312
  49. package/src/components/DatametriaSwitch.vue +146 -142
  50. package/src/components/DatametriaTabPane.vue +94 -76
  51. package/src/components/DatametriaTable.vue +118 -100
  52. package/src/components/DatametriaTabs.vue +315 -297
  53. package/src/components/DatametriaTextarea.vue +213 -195
  54. package/src/components/DatametriaTimePicker.vue +317 -299
  55. package/src/components/DatametriaToast.vue +176 -176
  56. package/src/components/DatametriaTooltip.vue +421 -400
  57. package/src/components/DatametriaTree.vue +126 -122
  58. package/src/components/DatametriaTreeNode.vue +176 -172
  59. package/src/components/DatametriaUpload.vue +379 -361
  60. package/src/components/__tests__/DatametriaAlert.test.js +35 -35
  61. package/src/components/__tests__/DatametriaAlert.test.ts +190 -190
  62. package/src/components/__tests__/DatametriaAvatar.test.ts +151 -151
  63. package/src/components/__tests__/DatametriaBadge.test.js +29 -29
  64. package/src/components/__tests__/DatametriaBadge.test.ts +167 -167
  65. package/src/components/__tests__/DatametriaBreadcrumb.test.ts +187 -0
  66. package/src/components/__tests__/DatametriaButton.test.js +30 -30
  67. package/src/components/__tests__/DatametriaButton.test.ts +283 -283
  68. package/src/components/__tests__/DatametriaCard.test.ts +201 -201
  69. package/src/components/__tests__/DatametriaCheckbox.test.ts +204 -0
  70. package/src/components/__tests__/DatametriaChip.test.js +38 -38
  71. package/src/components/__tests__/DatametriaContainer.test.ts +52 -52
  72. package/src/components/__tests__/DatametriaDialog.test.ts +338 -0
  73. package/src/components/__tests__/DatametriaDivider.test.ts +54 -54
  74. package/src/components/__tests__/DatametriaDropdown.test.ts +357 -0
  75. package/src/components/__tests__/DatametriaEmpty.test.ts +261 -0
  76. package/src/components/__tests__/DatametriaFileUpload.test.ts +290 -290
  77. package/src/components/__tests__/DatametriaFloatingBar.test.ts +137 -137
  78. package/src/components/__tests__/DatametriaForm.test.ts +96 -0
  79. package/src/components/__tests__/DatametriaFormItem.test.ts +58 -0
  80. package/src/components/__tests__/DatametriaGrid.test.ts +31 -31
  81. package/src/components/__tests__/DatametriaInput.test.ts +72 -72
  82. package/src/components/__tests__/DatametriaMenu.test.ts +366 -366
  83. package/src/components/__tests__/DatametriaModal.test.ts +86 -86
  84. package/src/components/__tests__/DatametriaNavbar.test.js +48 -48
  85. package/src/components/__tests__/DatametriaNavbar.test.ts +203 -203
  86. package/src/components/__tests__/DatametriaPasswordInput.test.js +305 -305
  87. package/src/components/__tests__/DatametriaRadio.test.ts +195 -0
  88. package/src/components/__tests__/DatametriaSelect.test.ts +77 -77
  89. package/src/components/__tests__/DatametriaSidebar.test.ts +169 -169
  90. package/src/components/__tests__/DatametriaSlider.test.ts +261 -261
  91. package/src/components/__tests__/DatametriaSortableTable.test.js +168 -168
  92. package/src/components/__tests__/DatametriaSpinner.test.ts +156 -156
  93. package/src/components/__tests__/DatametriaSteps.test.ts +211 -0
  94. package/src/components/__tests__/DatametriaSwitch.test.ts +129 -0
  95. package/src/components/__tests__/DatametriaTabPane.test.ts +205 -0
  96. package/src/components/__tests__/DatametriaTable.test.ts +97 -97
  97. package/src/components/__tests__/DatametriaTabs.test.ts +232 -232
  98. package/src/components/__tests__/DatametriaToast.test.js +48 -48
  99. package/src/components/__tests__/DatametriaToast.test.ts +99 -99
  100. package/src/components/__tests__/DatametriaTree.test.ts +376 -0
  101. package/src/components/__tests__/index.test.ts +48 -0
  102. package/src/composables/useAccessibilityScale.ts +94 -94
  103. package/src/composables/useBreakpoints.ts +82 -82
  104. package/src/composables/useHapticFeedback.ts +439 -439
  105. package/src/composables/useRipple.ts +218 -218
  106. package/src/composables/useTheme.ts +5 -1
  107. package/src/index.ts +84 -84
  108. package/src/stories/Variants.stories.js +95 -95
  109. package/src/styles/design-tokens.css +623 -623
  110. package/src/theme/ThemeProvider.vue +96 -96
  111. package/src/theme/__tests__/ThemeProvider.test.ts +208 -208
  112. package/src/theme/__tests__/constants.test.ts +31 -31
  113. package/src/theme/__tests__/presets.test.ts +166 -166
  114. package/src/theme/__tests__/tokens.test.ts +155 -155
  115. package/src/theme/__tests__/types.test.ts +153 -153
  116. package/src/theme/__tests__/useTheme.test.ts +146 -146
  117. package/src/theme/constants.ts +14 -14
  118. package/src/theme/index.ts +12 -12
  119. package/src/theme/presets/datametria.ts +94 -94
  120. package/src/theme/presets/default.ts +94 -94
  121. package/src/theme/presets/index.ts +8 -8
  122. package/src/theme/tokens/colors.ts +28 -28
  123. package/src/theme/tokens/index.ts +47 -47
  124. package/src/theme/tokens/spacing.ts +21 -21
  125. package/src/theme/tokens/typography.ts +35 -35
  126. package/src/theme/types.ts +111 -111
  127. package/src/theme/useTheme.ts +28 -28
  128. package/src/types/index.ts +55 -55
@@ -1,430 +1,448 @@
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-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
+ }
424
+
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;
429
+ }
430
+
431
+ /* Dark Mode Support - Hybrid Approach */
432
+
433
+ /* Fallback automático (sem JS) */
434
+ @media (prefers-color-scheme: dark) {
435
+ .datametria-password-input {
436
+ background: var(--dm-bg-color-dark, #1e1e1e);
437
+ color: var(--dm-text-primary-dark, #e0e0e0);
438
+ border-color: var(--dm-border-color-dark, #404040);
196
439
  }
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
440
  }
424
441
 
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;
442
+ /* Controle manual via useTheme() */
443
+ [data-theme="dark"] .datametria-password-input {
444
+ background: var(--dm-bg-color-dark, #1e1e1e);
445
+ color: var(--dm-text-primary-dark, #e0e0e0);
446
+ border-color: var(--dm-border-color-dark, #404040);
429
447
  }
430
- </style>
448
+ </style>