@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,396 +1,414 @@
1
- <template>
2
- <div class="dm-slider" :class="{ 'dm-slider--disabled': disabled }">
3
- <div v-if="label || showValue" class="dm-slider__header">
4
- <label v-if="label" :for="inputId" class="dm-slider__label">
5
- {{ label }}
6
- <span v-if="required" class="dm-slider__required" aria-label="obrigatório">*</span>
7
- </label>
8
- <span v-if="showValue" class="dm-slider__value">{{ displayValue }}</span>
9
- </div>
10
-
11
- <div class="dm-slider__wrapper">
12
- <div class="dm-slider__track" @click="handleTrackClick">
13
- <div
14
- class="dm-slider__progress"
15
- :style="{ width: `${progressPercentage}%` }"
16
- ></div>
17
- <div
18
- class="dm-slider__thumb"
19
- :style="{ left: `${progressPercentage}%` }"
20
- @mousedown="handleMouseDown"
21
- @touchstart="handleTouchStart"
22
- ></div>
23
- </div>
24
-
25
- <input
26
- :id="inputId"
27
- ref="inputRef"
28
- type="range"
29
- class="dm-slider__input"
30
- :value="modelValue"
31
- :min="min"
32
- :max="max"
33
- :step="step"
34
- :disabled="disabled"
35
- :required="required"
36
- :aria-label="ariaLabel"
37
- :aria-describedby="ariaDescribedBy"
38
- :aria-valuemin="min"
39
- :aria-valuemax="max"
40
- :aria-valuenow="modelValue"
41
- :aria-valuetext="ariaValueText"
42
- @input="handleInput"
43
- @change="handleChange"
44
- @focus="handleFocus"
45
- @blur="handleBlur"
46
- />
47
- </div>
48
-
49
- <div v-if="showMinMax" class="dm-slider__range">
50
- <span class="dm-slider__min">{{ formatValue(min) }}</span>
51
- <span class="dm-slider__max">{{ formatValue(max) }}</span>
52
- </div>
53
-
54
- <div v-if="errorMessage || helperText" class="dm-slider__messages">
55
- <p v-if="errorMessage" :id="`${inputId}-error`" class="dm-slider__error" role="alert">
56
- {{ errorMessage }}
57
- </p>
58
- <p v-else-if="helperText" :id="`${inputId}-helper`" class="dm-slider__helper">
59
- {{ helperText }}
60
- </p>
61
- </div>
62
- </div>
63
- </template>
64
-
65
- <script setup lang="ts">
66
- import { ref, computed, nextTick } from 'vue'
67
-
68
- interface Props {
69
- modelValue: number
70
- min?: number
71
- max?: number
72
- step?: number
73
- label?: string
74
- disabled?: boolean
75
- required?: boolean
76
- showValue?: boolean
77
- showMinMax?: boolean
78
- errorMessage?: string
79
- helperText?: string
80
- ariaLabel?: string
81
- formatter?: (value: number) => string
82
- }
83
-
84
- interface Emits {
85
- (e: 'update:modelValue', value: number): void
86
- (e: 'change', value: number): void
87
- (e: 'input', value: number): void
88
- (e: 'focus', event: FocusEvent): void
89
- (e: 'blur', event: FocusEvent): void
90
- }
91
-
92
- const props = withDefaults(defineProps<Props>(), {
93
- min: 0,
94
- max: 100,
95
- step: 1,
96
- showValue: true,
97
- showMinMax: false
98
- })
99
-
100
- const emit = defineEmits<Emits>()
101
-
102
- // Refs
103
- const inputRef = ref<HTMLInputElement>()
104
- const isDragging = ref(false)
105
-
106
- // Computed
107
- const inputId = computed(() => `dm-slider-${Math.random().toString(36).substr(2, 9)}`)
108
-
109
- const progressPercentage = computed(() => {
110
- const range = props.max - props.min
111
- const value = props.modelValue - props.min
112
- return (value / range) * 100
113
- })
114
-
115
- const displayValue = computed(() => {
116
- return props.formatter ? props.formatter(props.modelValue) : props.modelValue.toString()
117
- })
118
-
119
- const ariaDescribedBy = computed(() => {
120
- const ids = []
121
- if (props.errorMessage) ids.push(`${inputId.value}-error`)
122
- else if (props.helperText) ids.push(`${inputId.value}-helper`)
123
- return ids.length > 0 ? ids.join(' ') : undefined
124
- })
125
-
126
- const ariaValueText = computed(() => {
127
- return props.formatter ? props.formatter(props.modelValue) : `${props.modelValue}`
128
- })
129
-
130
- // Methods
131
- const formatValue = (value: number): string => {
132
- return props.formatter ? props.formatter(value) : value.toString()
133
- }
134
-
135
- const handleInput = (event: Event) => {
136
- const target = event.target as HTMLInputElement
137
- const value = parseFloat(target.value)
138
- emit('update:modelValue', value)
139
- emit('input', value)
140
- }
141
-
142
- const handleChange = (event: Event) => {
143
- const target = event.target as HTMLInputElement
144
- const value = parseFloat(target.value)
145
- emit('change', value)
146
- }
147
-
148
- const handleFocus = (event: FocusEvent) => {
149
- emit('focus', event)
150
- }
151
-
152
- const handleBlur = (event: FocusEvent) => {
153
- emit('blur', event)
154
- }
155
-
156
- const handleTrackClick = (event: MouseEvent) => {
157
- if (props.disabled) return
158
-
159
- const track = event.currentTarget as HTMLElement
160
- const rect = track.getBoundingClientRect()
161
- const percentage = (event.clientX - rect.left) / rect.width
162
- const range = props.max - props.min
163
- const newValue = props.min + (percentage * range)
164
- const steppedValue = Math.round(newValue / props.step) * props.step
165
- const clampedValue = Math.max(props.min, Math.min(props.max, steppedValue))
166
-
167
- emit('update:modelValue', clampedValue)
168
- emit('change', clampedValue)
169
- }
170
-
171
- const handleMouseDown = (event: MouseEvent) => {
172
- if (props.disabled) return
173
-
174
- isDragging.value = true
175
- event.preventDefault()
176
-
177
- const handleMouseMove = (e: MouseEvent) => {
178
- if (!isDragging.value) return
179
-
180
- const track = (event.target as HTMLElement).parentElement
181
- if (!track) return
182
-
183
- const rect = track.getBoundingClientRect()
184
- const percentage = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))
185
- const range = props.max - props.min
186
- const newValue = props.min + (percentage * range)
187
- const steppedValue = Math.round(newValue / props.step) * props.step
188
- const clampedValue = Math.max(props.min, Math.min(props.max, steppedValue))
189
-
190
- emit('update:modelValue', clampedValue)
191
- emit('input', clampedValue)
192
- }
193
-
194
- const handleMouseUp = () => {
195
- if (isDragging.value) {
196
- isDragging.value = false
197
- emit('change', props.modelValue)
198
- }
199
- document.removeEventListener('mousemove', handleMouseMove)
200
- document.removeEventListener('mouseup', handleMouseUp)
1
+ <template>
2
+ <div class="dm-slider" :class="{ 'dm-slider--disabled': disabled }">
3
+ <div v-if="label || showValue" class="dm-slider__header">
4
+ <label v-if="label" :for="inputId" class="dm-slider__label">
5
+ {{ label }}
6
+ <span v-if="required" class="dm-slider__required" aria-label="obrigatório">*</span>
7
+ </label>
8
+ <span v-if="showValue" class="dm-slider__value">{{ displayValue }}</span>
9
+ </div>
10
+
11
+ <div class="dm-slider__wrapper">
12
+ <div class="dm-slider__track" @click="handleTrackClick">
13
+ <div
14
+ class="dm-slider__progress"
15
+ :style="{ width: `${progressPercentage}%` }"
16
+ ></div>
17
+ <div
18
+ class="dm-slider__thumb"
19
+ :style="{ left: `${progressPercentage}%` }"
20
+ @mousedown="handleMouseDown"
21
+ @touchstart="handleTouchStart"
22
+ ></div>
23
+ </div>
24
+
25
+ <input
26
+ :id="inputId"
27
+ ref="inputRef"
28
+ type="range"
29
+ class="dm-slider__input"
30
+ :value="modelValue"
31
+ :min="min"
32
+ :max="max"
33
+ :step="step"
34
+ :disabled="disabled"
35
+ :required="required"
36
+ :aria-label="ariaLabel"
37
+ :aria-describedby="ariaDescribedBy"
38
+ :aria-valuemin="min"
39
+ :aria-valuemax="max"
40
+ :aria-valuenow="modelValue"
41
+ :aria-valuetext="ariaValueText"
42
+ @input="handleInput"
43
+ @change="handleChange"
44
+ @focus="handleFocus"
45
+ @blur="handleBlur"
46
+ />
47
+ </div>
48
+
49
+ <div v-if="showMinMax" class="dm-slider__range">
50
+ <span class="dm-slider__min">{{ formatValue(min) }}</span>
51
+ <span class="dm-slider__max">{{ formatValue(max) }}</span>
52
+ </div>
53
+
54
+ <div v-if="errorMessage || helperText" class="dm-slider__messages">
55
+ <p v-if="errorMessage" :id="`${inputId}-error`" class="dm-slider__error" role="alert">
56
+ {{ errorMessage }}
57
+ </p>
58
+ <p v-else-if="helperText" :id="`${inputId}-helper`" class="dm-slider__helper">
59
+ {{ helperText }}
60
+ </p>
61
+ </div>
62
+ </div>
63
+ </template>
64
+
65
+ <script setup lang="ts">
66
+ import { ref, computed, nextTick } from 'vue'
67
+
68
+ interface Props {
69
+ modelValue: number
70
+ min?: number
71
+ max?: number
72
+ step?: number
73
+ label?: string
74
+ disabled?: boolean
75
+ required?: boolean
76
+ showValue?: boolean
77
+ showMinMax?: boolean
78
+ errorMessage?: string
79
+ helperText?: string
80
+ ariaLabel?: string
81
+ formatter?: (value: number) => string
82
+ }
83
+
84
+ interface Emits {
85
+ (e: 'update:modelValue', value: number): void
86
+ (e: 'change', value: number): void
87
+ (e: 'input', value: number): void
88
+ (e: 'focus', event: FocusEvent): void
89
+ (e: 'blur', event: FocusEvent): void
90
+ }
91
+
92
+ const props = withDefaults(defineProps<Props>(), {
93
+ min: 0,
94
+ max: 100,
95
+ step: 1,
96
+ showValue: true,
97
+ showMinMax: false
98
+ })
99
+
100
+ const emit = defineEmits<Emits>()
101
+
102
+ // Refs
103
+ const inputRef = ref<HTMLInputElement>()
104
+ const isDragging = ref(false)
105
+
106
+ // Computed
107
+ const inputId = computed(() => `dm-slider-${Math.random().toString(36).substr(2, 9)}`)
108
+
109
+ const progressPercentage = computed(() => {
110
+ const range = props.max - props.min
111
+ const value = props.modelValue - props.min
112
+ return (value / range) * 100
113
+ })
114
+
115
+ const displayValue = computed(() => {
116
+ return props.formatter ? props.formatter(props.modelValue) : props.modelValue.toString()
117
+ })
118
+
119
+ const ariaDescribedBy = computed(() => {
120
+ const ids = []
121
+ if (props.errorMessage) ids.push(`${inputId.value}-error`)
122
+ else if (props.helperText) ids.push(`${inputId.value}-helper`)
123
+ return ids.length > 0 ? ids.join(' ') : undefined
124
+ })
125
+
126
+ const ariaValueText = computed(() => {
127
+ return props.formatter ? props.formatter(props.modelValue) : `${props.modelValue}`
128
+ })
129
+
130
+ // Methods
131
+ const formatValue = (value: number): string => {
132
+ return props.formatter ? props.formatter(value) : value.toString()
133
+ }
134
+
135
+ const handleInput = (event: Event) => {
136
+ const target = event.target as HTMLInputElement
137
+ const value = parseFloat(target.value)
138
+ emit('update:modelValue', value)
139
+ emit('input', value)
140
+ }
141
+
142
+ const handleChange = (event: Event) => {
143
+ const target = event.target as HTMLInputElement
144
+ const value = parseFloat(target.value)
145
+ emit('change', value)
146
+ }
147
+
148
+ const handleFocus = (event: FocusEvent) => {
149
+ emit('focus', event)
150
+ }
151
+
152
+ const handleBlur = (event: FocusEvent) => {
153
+ emit('blur', event)
154
+ }
155
+
156
+ const handleTrackClick = (event: MouseEvent) => {
157
+ if (props.disabled) return
158
+
159
+ const track = event.currentTarget as HTMLElement
160
+ const rect = track.getBoundingClientRect()
161
+ const percentage = (event.clientX - rect.left) / rect.width
162
+ const range = props.max - props.min
163
+ const newValue = props.min + (percentage * range)
164
+ const steppedValue = Math.round(newValue / props.step) * props.step
165
+ const clampedValue = Math.max(props.min, Math.min(props.max, steppedValue))
166
+
167
+ emit('update:modelValue', clampedValue)
168
+ emit('change', clampedValue)
169
+ }
170
+
171
+ const handleMouseDown = (event: MouseEvent) => {
172
+ if (props.disabled) return
173
+
174
+ isDragging.value = true
175
+ event.preventDefault()
176
+
177
+ const handleMouseMove = (e: MouseEvent) => {
178
+ if (!isDragging.value) return
179
+
180
+ const track = (event.target as HTMLElement).parentElement
181
+ if (!track) return
182
+
183
+ const rect = track.getBoundingClientRect()
184
+ const percentage = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))
185
+ const range = props.max - props.min
186
+ const newValue = props.min + (percentage * range)
187
+ const steppedValue = Math.round(newValue / props.step) * props.step
188
+ const clampedValue = Math.max(props.min, Math.min(props.max, steppedValue))
189
+
190
+ emit('update:modelValue', clampedValue)
191
+ emit('input', clampedValue)
192
+ }
193
+
194
+ const handleMouseUp = () => {
195
+ if (isDragging.value) {
196
+ isDragging.value = false
197
+ emit('change', props.modelValue)
198
+ }
199
+ document.removeEventListener('mousemove', handleMouseMove)
200
+ document.removeEventListener('mouseup', handleMouseUp)
201
+ }
202
+
203
+ document.addEventListener('mousemove', handleMouseMove)
204
+ document.addEventListener('mouseup', handleMouseUp)
205
+ }
206
+
207
+ const handleTouchStart = (event: TouchEvent) => {
208
+ if (props.disabled) return
209
+
210
+ isDragging.value = true
211
+ event.preventDefault()
212
+
213
+ const handleTouchMove = (e: TouchEvent) => {
214
+ if (!isDragging.value) return
215
+
216
+ const track = (event.target as HTMLElement).parentElement
217
+ if (!track) return
218
+
219
+ const rect = track.getBoundingClientRect()
220
+ const touch = e.touches[0]
221
+ const percentage = Math.max(0, Math.min(1, (touch.clientX - rect.left) / rect.width))
222
+ const range = props.max - props.min
223
+ const newValue = props.min + (percentage * range)
224
+ const steppedValue = Math.round(newValue / props.step) * props.step
225
+ const clampedValue = Math.max(props.min, Math.min(props.max, steppedValue))
226
+
227
+ emit('update:modelValue', clampedValue)
228
+ emit('input', clampedValue)
229
+ }
230
+
231
+ const handleTouchEnd = () => {
232
+ if (isDragging.value) {
233
+ isDragging.value = false
234
+ emit('change', props.modelValue)
235
+ }
236
+ document.removeEventListener('touchmove', handleTouchMove)
237
+ document.removeEventListener('touchend', handleTouchEnd)
238
+ }
239
+
240
+ document.addEventListener('touchmove', handleTouchMove)
241
+ document.addEventListener('touchend', handleTouchEnd)
242
+ }
243
+
244
+ const focus = () => {
245
+ nextTick(() => {
246
+ inputRef.value?.focus()
247
+ })
248
+ }
249
+
250
+ const blur = () => {
251
+ inputRef.value?.blur()
252
+ }
253
+
254
+ // Expose methods
255
+ defineExpose({
256
+ focus,
257
+ blur,
258
+ inputRef
259
+ })
260
+ </script>
261
+
262
+ <style scoped>
263
+ .dm-slider {
264
+ @apply w-full;
265
+ }
266
+
267
+ .dm-slider--disabled {
268
+ @apply opacity-60 cursor-not-allowed;
269
+ }
270
+
271
+ .dm-slider__header {
272
+ @apply flex justify-between items-center;
273
+ margin-bottom: var(--dm-spacing-2, 0.5rem);
274
+ }
275
+
276
+ .dm-slider__label {
277
+ font-size: var(--dm-font-size-sm, 0.875rem);
278
+ font-weight: var(--dm-font-weight-medium, 500);
279
+ color: var(--dm-neutral-700, #374151);
280
+ }
281
+
282
+ .dm-slider__required {
283
+ color: var(--dm-error, #ef4444);
284
+ margin-left: var(--dm-spacing-1, 0.25rem);
285
+ }
286
+
287
+ .dm-slider__value {
288
+ font-size: var(--dm-font-size-sm, 0.875rem);
289
+ font-weight: var(--dm-font-weight-medium, 500);
290
+ color: var(--dm-neutral-900, #111827);
291
+ }
292
+
293
+ .dm-slider__wrapper {
294
+ @apply relative;
295
+ margin-bottom: var(--dm-spacing-2, 0.5rem);
296
+ }
297
+
298
+ .dm-slider__track {
299
+ @apply relative cursor-pointer;
300
+ height: 0.5rem;
301
+ background-color: var(--dm-neutral-200, #e5e7eb);
302
+ border-radius: var(--dm-radius-full, 9999px);
303
+ }
304
+
305
+ .dm-slider__progress {
306
+ @apply absolute top-0 left-0 h-full;
307
+ background: var(--dm-primary, #0072CE);
308
+ border-radius: var(--dm-radius-full, 9999px);
309
+ transition: width 0.2s ease;
310
+ }
311
+
312
+ .dm-slider__thumb {
313
+ @apply absolute top-1/2 cursor-grab;
314
+ @apply transform -translate-x-1/2 -translate-y-1/2;
315
+ width: 1.25rem;
316
+ height: 1.25rem;
317
+ background-color: var(--dm-neutral-50, #ffffff);
318
+ border: 2px solid var(--dm-primary, #0072CE);
319
+ border-radius: var(--dm-radius-full, 9999px);
320
+ box-shadow: var(--dm-shadow-md, 0 4px 6px -1px rgba(0, 0, 0, 0.1));
321
+ transition: transform 0.2s ease;
322
+ }
323
+
324
+ .dm-slider__thumb:hover {
325
+ transform: translate(-50%, -50%) scale(1.1);
326
+ }
327
+
328
+ .dm-slider__thumb:active {
329
+ @apply cursor-grabbing;
330
+ transform: translate(-50%, -50%) scale(1.1);
331
+ }
332
+
333
+ .dm-slider__input {
334
+ @apply absolute inset-0 w-full h-full opacity-0 cursor-pointer;
335
+ @apply focus:outline-none;
336
+ }
337
+
338
+ .dm-slider__input:focus ~ .dm-slider__track .dm-slider__thumb {
339
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--dm-primary, #0072CE) 20%, transparent);
340
+ }
341
+
342
+ .dm-slider__range {
343
+ @apply flex justify-between;
344
+ font-size: var(--dm-font-size-xs, 0.75rem);
345
+ color: var(--dm-neutral-500, #6b7280);
346
+ }
347
+
348
+ .dm-slider__min,
349
+ .dm-slider__max {
350
+ @apply select-none;
351
+ }
352
+
353
+ .dm-slider__messages {
354
+ margin-top: var(--dm-spacing-1, 0.25rem);
355
+ }
356
+
357
+ .dm-slider__error {
358
+ font-size: var(--dm-font-size-sm, 0.875rem);
359
+ color: var(--dm-error, #ef4444);
360
+ }
361
+
362
+ .dm-slider__helper {
363
+ font-size: var(--dm-font-size-sm, 0.875rem);
364
+ color: var(--dm-neutral-500, #6b7280);
365
+ }
366
+
367
+ /* High contrast mode support */
368
+ @media (prefers-contrast: high) {
369
+ .dm-slider__track {
370
+ @apply border border-gray-400;
371
+ }
372
+
373
+ .dm-slider__thumb {
374
+ @apply border-4;
375
+ }
376
+ }
377
+
378
+ /* Reduced motion support */
379
+ @media (prefers-reduced-motion: reduce) {
380
+ .dm-slider__progress,
381
+ .dm-slider__thumb {
382
+ @apply transition-none;
383
+ }
384
+ }
385
+
386
+ /* Touch device optimizations */
387
+ @media (pointer: coarse) {
388
+ .dm-slider__thumb {
389
+ @apply w-6 h-6;
390
+ }
391
+
392
+ .dm-slider__track {
393
+ @apply h-3;
394
+ }
395
+ }
396
+
397
+ /* Dark Mode Support - Hybrid Approach */
398
+
399
+ /* Fallback automático (sem JS) */
400
+ @media (prefers-color-scheme: dark) {
401
+ .dm-slider {
402
+ background: var(--dm-bg-color-dark, #1e1e1e);
403
+ color: var(--dm-text-primary-dark, #e0e0e0);
404
+ border-color: var(--dm-border-color-dark, #404040);
201
405
  }
202
-
203
- document.addEventListener('mousemove', handleMouseMove)
204
- document.addEventListener('mouseup', handleMouseUp)
205
- }
206
-
207
- const handleTouchStart = (event: TouchEvent) => {
208
- if (props.disabled) return
209
-
210
- isDragging.value = true
211
- event.preventDefault()
212
-
213
- const handleTouchMove = (e: TouchEvent) => {
214
- if (!isDragging.value) return
215
-
216
- const track = (event.target as HTMLElement).parentElement
217
- if (!track) return
218
-
219
- const rect = track.getBoundingClientRect()
220
- const touch = e.touches[0]
221
- const percentage = Math.max(0, Math.min(1, (touch.clientX - rect.left) / rect.width))
222
- const range = props.max - props.min
223
- const newValue = props.min + (percentage * range)
224
- const steppedValue = Math.round(newValue / props.step) * props.step
225
- const clampedValue = Math.max(props.min, Math.min(props.max, steppedValue))
226
-
227
- emit('update:modelValue', clampedValue)
228
- emit('input', clampedValue)
229
- }
230
-
231
- const handleTouchEnd = () => {
232
- if (isDragging.value) {
233
- isDragging.value = false
234
- emit('change', props.modelValue)
235
- }
236
- document.removeEventListener('touchmove', handleTouchMove)
237
- document.removeEventListener('touchend', handleTouchEnd)
238
- }
239
-
240
- document.addEventListener('touchmove', handleTouchMove)
241
- document.addEventListener('touchend', handleTouchEnd)
242
- }
243
-
244
- const focus = () => {
245
- nextTick(() => {
246
- inputRef.value?.focus()
247
- })
248
- }
249
-
250
- const blur = () => {
251
- inputRef.value?.blur()
252
- }
253
-
254
- // Expose methods
255
- defineExpose({
256
- focus,
257
- blur,
258
- inputRef
259
- })
260
- </script>
261
-
262
- <style scoped>
263
- .dm-slider {
264
- @apply w-full;
265
- }
266
-
267
- .dm-slider--disabled {
268
- @apply opacity-60 cursor-not-allowed;
269
- }
270
-
271
- .dm-slider__header {
272
- @apply flex justify-between items-center;
273
- margin-bottom: var(--dm-spacing-2, 0.5rem);
274
406
  }
275
407
 
276
- .dm-slider__label {
277
- font-size: var(--dm-font-size-sm, 0.875rem);
278
- font-weight: var(--dm-font-weight-medium, 500);
279
- color: var(--dm-neutral-700, #374151);
280
- }
281
-
282
- .dm-slider__required {
283
- color: var(--dm-error, #ef4444);
284
- margin-left: var(--dm-spacing-1, 0.25rem);
285
- }
286
-
287
- .dm-slider__value {
288
- font-size: var(--dm-font-size-sm, 0.875rem);
289
- font-weight: var(--dm-font-weight-medium, 500);
290
- color: var(--dm-neutral-900, #111827);
291
- }
292
-
293
- .dm-slider__wrapper {
294
- @apply relative;
295
- margin-bottom: var(--dm-spacing-2, 0.5rem);
296
- }
297
-
298
- .dm-slider__track {
299
- @apply relative cursor-pointer;
300
- height: 0.5rem;
301
- background-color: var(--dm-neutral-200, #e5e7eb);
302
- border-radius: var(--dm-radius-full, 9999px);
303
- }
304
-
305
- .dm-slider__progress {
306
- @apply absolute top-0 left-0 h-full;
307
- background: var(--dm-primary, #0072CE);
308
- border-radius: var(--dm-radius-full, 9999px);
309
- transition: width 0.2s ease;
310
- }
311
-
312
- .dm-slider__thumb {
313
- @apply absolute top-1/2 cursor-grab;
314
- @apply transform -translate-x-1/2 -translate-y-1/2;
315
- width: 1.25rem;
316
- height: 1.25rem;
317
- background-color: var(--dm-neutral-50, #ffffff);
318
- border: 2px solid var(--dm-primary, #0072CE);
319
- border-radius: var(--dm-radius-full, 9999px);
320
- box-shadow: var(--dm-shadow-md, 0 4px 6px -1px rgba(0, 0, 0, 0.1));
321
- transition: transform 0.2s ease;
322
- }
323
-
324
- .dm-slider__thumb:hover {
325
- transform: translate(-50%, -50%) scale(1.1);
326
- }
327
-
328
- .dm-slider__thumb:active {
329
- @apply cursor-grabbing;
330
- transform: translate(-50%, -50%) scale(1.1);
331
- }
332
-
333
- .dm-slider__input {
334
- @apply absolute inset-0 w-full h-full opacity-0 cursor-pointer;
335
- @apply focus:outline-none;
336
- }
337
-
338
- .dm-slider__input:focus ~ .dm-slider__track .dm-slider__thumb {
339
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--dm-primary, #0072CE) 20%, transparent);
340
- }
341
-
342
- .dm-slider__range {
343
- @apply flex justify-between;
344
- font-size: var(--dm-font-size-xs, 0.75rem);
345
- color: var(--dm-neutral-500, #6b7280);
346
- }
347
-
348
- .dm-slider__min,
349
- .dm-slider__max {
350
- @apply select-none;
351
- }
352
-
353
- .dm-slider__messages {
354
- margin-top: var(--dm-spacing-1, 0.25rem);
355
- }
356
-
357
- .dm-slider__error {
358
- font-size: var(--dm-font-size-sm, 0.875rem);
359
- color: var(--dm-error, #ef4444);
360
- }
361
-
362
- .dm-slider__helper {
363
- font-size: var(--dm-font-size-sm, 0.875rem);
364
- color: var(--dm-neutral-500, #6b7280);
365
- }
366
-
367
- /* High contrast mode support */
368
- @media (prefers-contrast: high) {
369
- .dm-slider__track {
370
- @apply border border-gray-400;
371
- }
372
-
373
- .dm-slider__thumb {
374
- @apply border-4;
375
- }
376
- }
377
-
378
- /* Reduced motion support */
379
- @media (prefers-reduced-motion: reduce) {
380
- .dm-slider__progress,
381
- .dm-slider__thumb {
382
- @apply transition-none;
383
- }
384
- }
385
-
386
- /* Touch device optimizations */
387
- @media (pointer: coarse) {
388
- .dm-slider__thumb {
389
- @apply w-6 h-6;
390
- }
391
-
392
- .dm-slider__track {
393
- @apply h-3;
394
- }
408
+ /* Controle manual via useTheme() */
409
+ [data-theme="dark"] .dm-slider {
410
+ background: var(--dm-bg-color-dark, #1e1e1e);
411
+ color: var(--dm-text-primary-dark, #e0e0e0);
412
+ border-color: var(--dm-border-color-dark, #404040);
395
413
  }
396
414
  </style>