@datametria/vue-components 1.2.0 → 2.0.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 (97) hide show
  1. package/README.md +548 -657
  2. package/dist/index.es.js +2353 -1364
  3. package/dist/index.umd.js +10 -10
  4. package/dist/vue-components.css +1 -1
  5. package/package.json +102 -98
  6. package/src/components/DatametriaAlert.vue +137 -137
  7. package/src/components/DatametriaAutocomplete.vue +184 -138
  8. package/src/components/DatametriaAvatar.vue +177 -33
  9. package/src/components/DatametriaBadge.vue +98 -98
  10. package/src/components/DatametriaBreadcrumb.vue +21 -21
  11. package/src/components/DatametriaButton.vue +177 -165
  12. package/src/components/DatametriaCard.vue +12 -12
  13. package/src/components/DatametriaCheckbox.vue +8 -8
  14. package/src/components/DatametriaChip.vue +145 -149
  15. package/src/components/DatametriaContainer.vue +4 -4
  16. package/src/components/DatametriaDatePicker.vue +686 -68
  17. package/src/components/DatametriaDivider.vue +13 -13
  18. package/src/components/DatametriaFileUpload.vue +272 -140
  19. package/src/components/DatametriaGrid.vue +3 -3
  20. package/src/components/DatametriaInput.vue +15 -15
  21. package/src/components/DatametriaMenu.vue +604 -619
  22. package/src/components/DatametriaModal.vue +16 -16
  23. package/src/components/DatametriaNavbar.vue +230 -252
  24. package/src/components/DatametriaPasswordInput.vue +430 -0
  25. package/src/components/DatametriaProgress.vue +18 -18
  26. package/src/components/DatametriaRadio.vue +20 -20
  27. package/src/components/DatametriaSelect.vue +15 -15
  28. package/src/components/DatametriaSkeleton.vue +243 -239
  29. package/src/components/DatametriaSlider.vue +395 -407
  30. package/src/components/DatametriaSortableTable.vue +585 -0
  31. package/src/components/DatametriaSpinner.vue +7 -7
  32. package/src/components/DatametriaSwitch.vue +16 -16
  33. package/src/components/DatametriaTable.vue +14 -14
  34. package/src/components/DatametriaTextarea.vue +28 -28
  35. package/src/components/DatametriaTimePicker.vue +285 -285
  36. package/src/components/DatametriaToast.vue +176 -176
  37. package/src/components/DatametriaTooltip.vue +408 -408
  38. package/src/components/__tests__/DatametriaAlert.test.js +35 -35
  39. package/src/components/__tests__/DatametriaAlert.test.ts +190 -0
  40. package/src/components/__tests__/DatametriaAutocomplete.test.ts +180 -0
  41. package/src/components/__tests__/DatametriaAvatar.test.ts +152 -0
  42. package/src/components/__tests__/DatametriaBadge.test.js +29 -29
  43. package/src/components/__tests__/DatametriaBadge.test.ts +167 -0
  44. package/src/components/__tests__/DatametriaBreadcrumb.test.ts +75 -0
  45. package/src/components/__tests__/DatametriaButton.test.js +30 -30
  46. package/src/components/__tests__/DatametriaButton.test.ts +283 -0
  47. package/src/components/__tests__/DatametriaCard.test.ts +201 -0
  48. package/src/components/__tests__/DatametriaCheckbox.test.ts +47 -0
  49. package/src/components/__tests__/DatametriaChip.test.js +38 -38
  50. package/src/components/__tests__/DatametriaContainer.test.ts +52 -0
  51. package/src/components/__tests__/DatametriaDatePicker.test.ts +234 -0
  52. package/src/components/__tests__/DatametriaDivider.test.ts +54 -0
  53. package/src/components/__tests__/DatametriaFileUpload.test.ts +291 -0
  54. package/src/components/__tests__/DatametriaGrid.test.ts +31 -0
  55. package/src/components/__tests__/DatametriaInput.test.ts +72 -0
  56. package/src/components/__tests__/DatametriaMenu.test.ts +366 -0
  57. package/src/components/__tests__/DatametriaModal.test.ts +86 -0
  58. package/src/components/__tests__/DatametriaNavbar.test.js +48 -48
  59. package/src/components/__tests__/DatametriaNavbar.test.ts +203 -0
  60. package/src/components/__tests__/DatametriaPasswordInput.test.js +305 -0
  61. package/src/components/__tests__/DatametriaProgress.test.ts +90 -0
  62. package/src/components/__tests__/DatametriaRadio.test.ts +77 -0
  63. package/src/components/__tests__/DatametriaSelect.test.ts +77 -0
  64. package/src/components/__tests__/DatametriaSlider.test.ts +261 -0
  65. package/src/components/__tests__/DatametriaSortableTable.test.js +168 -0
  66. package/src/components/__tests__/DatametriaSpinner.test.ts +156 -0
  67. package/src/components/__tests__/DatametriaSwitch.test.ts +64 -0
  68. package/src/components/__tests__/DatametriaTable.test.ts +97 -0
  69. package/src/components/__tests__/DatametriaTextarea.test.ts +66 -0
  70. package/src/components/__tests__/DatametriaToast.test.js +48 -48
  71. package/src/components/__tests__/DatametriaToast.test.ts +99 -0
  72. package/src/composables/useAccessibilityScale.ts +94 -94
  73. package/src/composables/useBreakpoints.ts +82 -82
  74. package/src/composables/useHapticFeedback.ts +439 -439
  75. package/src/composables/useRipple.ts +218 -218
  76. package/src/index.ts +68 -61
  77. package/src/stories/Variants.stories.js +95 -95
  78. package/src/styles/design-tokens.css +623 -623
  79. package/src/theme/ThemeProvider.vue +96 -0
  80. package/src/theme/__tests__/ThemeProvider.test.ts +208 -0
  81. package/src/theme/__tests__/constants.test.ts +31 -0
  82. package/src/theme/__tests__/presets.test.ts +166 -0
  83. package/src/theme/__tests__/tokens.test.ts +155 -0
  84. package/src/theme/__tests__/types.test.ts +153 -0
  85. package/src/theme/__tests__/useTheme.test.ts +146 -0
  86. package/src/theme/constants.ts +14 -0
  87. package/src/theme/index.ts +12 -0
  88. package/src/theme/presets/datametria.ts +94 -0
  89. package/src/theme/presets/default.ts +94 -0
  90. package/src/theme/presets/index.ts +8 -0
  91. package/src/theme/tokens/colors.ts +28 -0
  92. package/src/theme/tokens/index.ts +47 -0
  93. package/src/theme/tokens/spacing.ts +21 -0
  94. package/src/theme/tokens/typography.ts +35 -0
  95. package/src/theme/types.ts +111 -0
  96. package/src/theme/useTheme.ts +28 -0
  97. package/src/types/index.ts +19 -0
@@ -1,409 +1,409 @@
1
- <template>
2
- <div
3
- ref="triggerRef"
4
- class="dm-tooltip-trigger"
5
- @mouseenter="handleMouseEnter"
6
- @mouseleave="handleMouseLeave"
7
- @focus="handleFocus"
8
- @blur="handleBlur"
9
- @click="handleClick"
10
- >
11
- <slot />
12
-
13
- <Teleport to="body">
14
- <Transition
15
- name="tooltip"
16
- @enter="onEnter"
17
- @leave="onLeave"
18
- >
19
- <div
20
- v-if="isVisible"
21
- ref="tooltipRef"
22
- :id="tooltipId"
23
- class="dm-tooltip"
24
- :class="[
25
- `dm-tooltip--${placement}`,
26
- `dm-tooltip--${variant}`,
27
- { 'dm-tooltip--arrow': showArrow }
28
- ]"
29
- :style="tooltipStyle"
30
- role="tooltip"
31
- :aria-hidden="!isVisible"
32
- >
33
- <div class="dm-tooltip__content">
34
- <slot name="content">
35
- {{ content }}
36
- </slot>
37
- </div>
38
- <div v-if="showArrow" class="dm-tooltip__arrow"></div>
39
- </div>
40
- </Transition>
41
- </Teleport>
42
- </div>
43
- </template>
44
-
45
- <script setup lang="ts">
46
- import { ref, computed, nextTick, onMounted, onUnmounted } from 'vue'
47
-
48
- type Placement = 'top' | 'bottom' | 'left' | 'right' | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end'
49
- type Variant = 'dark' | 'light' | 'primary' | 'error' | 'warning' | 'success'
50
- type Trigger = 'hover' | 'click' | 'focus' | 'manual'
51
-
52
- interface Props {
53
- content?: string
54
- placement?: Placement
55
- variant?: Variant
56
- trigger?: Trigger
57
- disabled?: boolean
58
- showArrow?: boolean
59
- delay?: number
60
- hideDelay?: number
61
- offset?: number
62
- maxWidth?: string
63
- }
64
-
65
- interface Emits {
66
- (e: 'show'): void
67
- (e: 'hide'): void
68
- }
69
-
70
- const props = withDefaults(defineProps<Props>(), {
71
- placement: 'top',
72
- variant: 'dark',
73
- trigger: 'hover',
74
- showArrow: true,
75
- delay: 100,
76
- hideDelay: 100,
77
- offset: 8,
78
- maxWidth: '200px'
79
- })
80
-
81
- const emit = defineEmits<Emits>()
82
-
83
- // Refs
84
- const triggerRef = ref<HTMLElement>()
85
- const tooltipRef = ref<HTMLElement>()
86
- const isVisible = ref(false)
87
- const showTimeout = ref<number>()
88
- const hideTimeout = ref<number>()
89
-
90
- // Computed
91
- const tooltipId = computed(() => `dm-tooltip-${Math.random().toString(36).substr(2, 9)}`)
92
-
93
- const tooltipStyle = ref<Record<string, string>>({})
94
-
95
- // Methods
96
- const calculatePosition = async () => {
97
- if (!triggerRef.value || !tooltipRef.value) return
98
-
99
- await nextTick()
100
-
101
- const trigger = triggerRef.value.getBoundingClientRect()
102
- const tooltip = tooltipRef.value.getBoundingClientRect()
103
- const viewport = {
104
- width: window.innerWidth,
105
- height: window.innerHeight
106
- }
107
-
108
- let top = 0
109
- let left = 0
110
-
111
- // Calculate base position
112
- switch (props.placement) {
113
- case 'top':
114
- case 'top-start':
115
- case 'top-end':
116
- top = trigger.top - tooltip.height - props.offset
117
- break
118
- case 'bottom':
119
- case 'bottom-start':
120
- case 'bottom-end':
121
- top = trigger.bottom + props.offset
122
- break
123
- case 'left':
124
- top = trigger.top + (trigger.height - tooltip.height) / 2
125
- left = trigger.left - tooltip.width - props.offset
126
- break
127
- case 'right':
128
- top = trigger.top + (trigger.height - tooltip.height) / 2
129
- left = trigger.right + props.offset
130
- break
131
- }
132
-
133
- // Calculate horizontal position for top/bottom placements
134
- if (props.placement.startsWith('top') || props.placement.startsWith('bottom')) {
135
- switch (props.placement) {
136
- case 'top':
137
- case 'bottom':
138
- left = trigger.left + (trigger.width - tooltip.width) / 2
139
- break
140
- case 'top-start':
141
- case 'bottom-start':
142
- left = trigger.left
143
- break
144
- case 'top-end':
145
- case 'bottom-end':
146
- left = trigger.right - tooltip.width
147
- break
148
- }
149
- }
150
-
151
- // Viewport boundary checks
152
- if (left < 0) {
153
- left = 8
154
- } else if (left + tooltip.width > viewport.width) {
155
- left = viewport.width - tooltip.width - 8
156
- }
157
-
158
- if (top < 0) {
159
- top = 8
160
- } else if (top + tooltip.height > viewport.height) {
161
- top = viewport.height - tooltip.height - 8
162
- }
163
-
164
- tooltipStyle.value = {
165
- position: 'fixed',
166
- top: `${top}px`,
167
- left: `${left}px`,
168
- maxWidth: props.maxWidth,
169
- zIndex: '9999'
170
- }
171
- }
172
-
173
- const show = () => {
174
- if (props.disabled || isVisible.value) return
175
-
176
- clearTimeout(hideTimeout.value)
177
-
178
- showTimeout.value = window.setTimeout(async () => {
179
- isVisible.value = true
180
- emit('show')
181
- await calculatePosition()
182
- }, props.delay)
183
- }
184
-
185
- const hide = () => {
186
- clearTimeout(showTimeout.value)
187
-
188
- hideTimeout.value = window.setTimeout(() => {
189
- isVisible.value = false
190
- emit('hide')
191
- }, props.hideDelay)
192
- }
193
-
194
- const handleMouseEnter = () => {
195
- if (props.trigger === 'hover') {
196
- show()
197
- }
198
- }
199
-
200
- const handleMouseLeave = () => {
201
- if (props.trigger === 'hover') {
202
- hide()
203
- }
204
- }
205
-
206
- const handleFocus = () => {
207
- if (props.trigger === 'focus') {
208
- show()
209
- }
210
- }
211
-
212
- const handleBlur = () => {
213
- if (props.trigger === 'focus') {
214
- hide()
215
- }
216
- }
217
-
218
- const handleClick = () => {
219
- if (props.trigger === 'click') {
220
- if (isVisible.value) {
221
- hide()
222
- } else {
223
- show()
224
- }
225
- }
226
- }
227
-
228
- const onEnter = () => {
229
- calculatePosition()
230
- }
231
-
232
- const onLeave = () => {
233
- tooltipStyle.value = {}
234
- }
235
-
236
- // Handle window resize
237
- const handleResize = () => {
238
- if (isVisible.value) {
239
- calculatePosition()
240
- }
241
- }
242
-
243
- // Handle scroll
244
- const handleScroll = () => {
245
- if (isVisible.value) {
246
- calculatePosition()
247
- }
248
- }
249
-
250
- // Lifecycle
251
- onMounted(() => {
252
- window.addEventListener('resize', handleResize)
253
- window.addEventListener('scroll', handleScroll, true)
254
- })
255
-
256
- onUnmounted(() => {
257
- clearTimeout(showTimeout.value)
258
- clearTimeout(hideTimeout.value)
259
- window.removeEventListener('resize', handleResize)
260
- window.removeEventListener('scroll', handleScroll, true)
261
- })
262
-
263
- // Expose methods
264
- defineExpose({
265
- show,
266
- hide,
267
- isVisible: computed(() => isVisible.value)
268
- })
269
- </script>
270
-
271
- <style scoped>
272
- .dm-tooltip-trigger {
273
- @apply inline-block;
274
- }
275
-
276
- .dm-tooltip {
277
- @apply absolute z-50 px-2 py-1 text-sm rounded shadow-lg pointer-events-none;
278
- border-radius: var(--dm-radius);
279
- font-size: var(--dm-text-sm);
280
- }
281
-
282
- /* Variants */
283
- .dm-tooltip--dark {
284
- @apply bg-gray-900 text-white;
285
- background-color: var(--dm-gray-900);
286
- color: var(--dm-bg-primary, #ffffff);
287
- }
288
-
289
- .dm-tooltip--light {
290
- @apply bg-white text-gray-900 border border-gray-200;
291
- background-color: var(--dm-bg-primary, #ffffff);
292
- color: var(--dm-text-primary);
293
- border-color: var(--dm-gray-200, #e5e7eb);
294
- }
295
-
296
- .dm-tooltip--primary {
297
- @apply text-white;
298
- background: var(--gradient-primary);
299
- color: var(--dm-bg-primary, #ffffff);
300
- }
301
-
302
- .dm-tooltip--error {
303
- @apply text-white;
304
- background-color: var(--dm-error);
305
- color: var(--dm-bg-primary, #ffffff);
306
- }
307
-
308
- .dm-tooltip--warning {
309
- @apply text-white;
310
- background-color: var(--dm-warning);
311
- color: var(--dm-bg-primary, #ffffff);
312
- }
313
-
314
- .dm-tooltip--success {
315
- @apply text-white;
316
- background-color: var(--dm-success);
317
- color: var(--dm-bg-primary, #ffffff);
318
- }
319
-
320
- /* Dark mode adjustments */
321
- [data-theme="dark"] .dm-tooltip--dark {
322
- background-color: var(--dm-gray-800, #1f2937);
323
- color: var(--dm-text-primary);
324
- }
325
-
326
- [data-theme="dark"] .dm-tooltip--light {
327
- background-color: var(--dm-bg-secondary);
328
- color: var(--dm-text-primary);
329
- border-color: var(--dm-gray-600, #4b5563);
330
- }
331
-
332
- .dm-tooltip__content {
333
- @apply relative z-10;
334
- }
335
-
336
- /* Arrow styles */
337
- .dm-tooltip--arrow .dm-tooltip__arrow {
338
- @apply absolute w-2 h-2 transform rotate-45;
339
- }
340
-
341
- .dm-tooltip--top .dm-tooltip__arrow {
342
- @apply bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2;
343
- }
344
-
345
- .dm-tooltip--bottom .dm-tooltip__arrow {
346
- @apply top-0 left-1/2 -translate-x-1/2 -translate-y-1/2;
347
- }
348
-
349
- .dm-tooltip--left .dm-tooltip__arrow {
350
- @apply right-0 top-1/2 translate-x-1/2 -translate-y-1/2;
351
- }
352
-
353
- .dm-tooltip--right .dm-tooltip__arrow {
354
- @apply left-0 top-1/2 -translate-x-1/2 -translate-y-1/2;
355
- }
356
-
357
- /* Arrow colors */
358
- .dm-tooltip--dark .dm-tooltip__arrow {
359
- background-color: var(--dm-gray-900);
360
- }
361
-
362
- .dm-tooltip--light .dm-tooltip__arrow {
363
- background-color: var(--dm-bg-primary, #ffffff);
364
- border: 1px solid var(--dm-gray-200, #e5e7eb);
365
- }
366
-
367
- .dm-tooltip--primary .dm-tooltip__arrow {
368
- background-color: var(--dm-primary);
369
- }
370
-
371
- .dm-tooltip--error .dm-tooltip__arrow {
372
- background-color: var(--dm-error);
373
- }
374
-
375
- .dm-tooltip--warning .dm-tooltip__arrow {
376
- background-color: var(--dm-warning);
377
- }
378
-
379
- .dm-tooltip--success .dm-tooltip__arrow {
380
- background-color: var(--dm-success);
381
- }
382
-
383
- /* Transitions */
384
- .tooltip-enter-active,
385
- .tooltip-leave-active {
386
- transition: opacity var(--transition-fast), transform var(--transition-fast);
387
- }
388
-
389
- .tooltip-enter-from,
390
- .tooltip-leave-to {
391
- opacity: 0;
392
- transform: scale(0.95);
393
- }
394
-
395
- /* Reduced motion support */
396
- @media (prefers-reduced-motion: reduce) {
397
- .tooltip-enter-active,
398
- .tooltip-leave-active {
399
- transition: none;
400
- }
401
- }
402
-
403
- /* High contrast mode support */
404
- @media (prefers-contrast: high) {
405
- .dm-tooltip {
406
- @apply border-2 border-current;
407
- }
408
- }
1
+ <template>
2
+ <div
3
+ ref="triggerRef"
4
+ class="dm-tooltip-trigger"
5
+ @mouseenter="handleMouseEnter"
6
+ @mouseleave="handleMouseLeave"
7
+ @focus="handleFocus"
8
+ @blur="handleBlur"
9
+ @click="handleClick"
10
+ >
11
+ <slot />
12
+
13
+ <Teleport to="body">
14
+ <Transition
15
+ name="tooltip"
16
+ @enter="onEnter"
17
+ @leave="onLeave"
18
+ >
19
+ <div
20
+ v-if="isVisible"
21
+ ref="tooltipRef"
22
+ :id="tooltipId"
23
+ class="dm-tooltip"
24
+ :class="[
25
+ `dm-tooltip--${placement}`,
26
+ `dm-tooltip--${variant}`,
27
+ { 'dm-tooltip--arrow': showArrow }
28
+ ]"
29
+ :style="tooltipStyle"
30
+ role="tooltip"
31
+ :aria-hidden="!isVisible"
32
+ >
33
+ <div class="dm-tooltip__content">
34
+ <slot name="content">
35
+ {{ content }}
36
+ </slot>
37
+ </div>
38
+ <div v-if="showArrow" class="dm-tooltip__arrow"></div>
39
+ </div>
40
+ </Transition>
41
+ </Teleport>
42
+ </div>
43
+ </template>
44
+
45
+ <script setup lang="ts">
46
+ import { ref, computed, nextTick, onMounted, onUnmounted } from 'vue'
47
+
48
+ type Placement = 'top' | 'bottom' | 'left' | 'right' | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end'
49
+ type Variant = 'dark' | 'light' | 'primary' | 'error' | 'warning' | 'success'
50
+ type Trigger = 'hover' | 'click' | 'focus' | 'manual'
51
+
52
+ interface Props {
53
+ content?: string
54
+ placement?: Placement
55
+ variant?: Variant
56
+ trigger?: Trigger
57
+ disabled?: boolean
58
+ showArrow?: boolean
59
+ delay?: number
60
+ hideDelay?: number
61
+ offset?: number
62
+ maxWidth?: string
63
+ }
64
+
65
+ interface Emits {
66
+ (e: 'show'): void
67
+ (e: 'hide'): void
68
+ }
69
+
70
+ const props = withDefaults(defineProps<Props>(), {
71
+ placement: 'top',
72
+ variant: 'dark',
73
+ trigger: 'hover',
74
+ showArrow: true,
75
+ delay: 100,
76
+ hideDelay: 100,
77
+ offset: 8,
78
+ maxWidth: '200px'
79
+ })
80
+
81
+ const emit = defineEmits<Emits>()
82
+
83
+ // Refs
84
+ const triggerRef = ref<HTMLElement>()
85
+ const tooltipRef = ref<HTMLElement>()
86
+ const isVisible = ref(false)
87
+ const showTimeout = ref<number>()
88
+ const hideTimeout = ref<number>()
89
+
90
+ // Computed
91
+ const tooltipId = computed(() => `dm-tooltip-${Math.random().toString(36).substr(2, 9)}`)
92
+
93
+ const tooltipStyle = ref<Record<string, string>>({})
94
+
95
+ // Methods
96
+ const calculatePosition = async () => {
97
+ if (!triggerRef.value || !tooltipRef.value) return
98
+
99
+ await nextTick()
100
+
101
+ const trigger = triggerRef.value.getBoundingClientRect()
102
+ const tooltip = tooltipRef.value.getBoundingClientRect()
103
+ const viewport = {
104
+ width: window.innerWidth,
105
+ height: window.innerHeight
106
+ }
107
+
108
+ let top = 0
109
+ let left = 0
110
+
111
+ // Calculate base position
112
+ switch (props.placement) {
113
+ case 'top':
114
+ case 'top-start':
115
+ case 'top-end':
116
+ top = trigger.top - tooltip.height - props.offset
117
+ break
118
+ case 'bottom':
119
+ case 'bottom-start':
120
+ case 'bottom-end':
121
+ top = trigger.bottom + props.offset
122
+ break
123
+ case 'left':
124
+ top = trigger.top + (trigger.height - tooltip.height) / 2
125
+ left = trigger.left - tooltip.width - props.offset
126
+ break
127
+ case 'right':
128
+ top = trigger.top + (trigger.height - tooltip.height) / 2
129
+ left = trigger.right + props.offset
130
+ break
131
+ }
132
+
133
+ // Calculate horizontal position for top/bottom placements
134
+ if (props.placement.startsWith('top') || props.placement.startsWith('bottom')) {
135
+ switch (props.placement) {
136
+ case 'top':
137
+ case 'bottom':
138
+ left = trigger.left + (trigger.width - tooltip.width) / 2
139
+ break
140
+ case 'top-start':
141
+ case 'bottom-start':
142
+ left = trigger.left
143
+ break
144
+ case 'top-end':
145
+ case 'bottom-end':
146
+ left = trigger.right - tooltip.width
147
+ break
148
+ }
149
+ }
150
+
151
+ // Viewport boundary checks
152
+ if (left < 0) {
153
+ left = 8
154
+ } else if (left + tooltip.width > viewport.width) {
155
+ left = viewport.width - tooltip.width - 8
156
+ }
157
+
158
+ if (top < 0) {
159
+ top = 8
160
+ } else if (top + tooltip.height > viewport.height) {
161
+ top = viewport.height - tooltip.height - 8
162
+ }
163
+
164
+ tooltipStyle.value = {
165
+ position: 'fixed',
166
+ top: `${top}px`,
167
+ left: `${left}px`,
168
+ maxWidth: props.maxWidth,
169
+ zIndex: '9999'
170
+ }
171
+ }
172
+
173
+ const show = () => {
174
+ if (props.disabled || isVisible.value) return
175
+
176
+ clearTimeout(hideTimeout.value)
177
+
178
+ showTimeout.value = window.setTimeout(async () => {
179
+ isVisible.value = true
180
+ emit('show')
181
+ await calculatePosition()
182
+ }, props.delay)
183
+ }
184
+
185
+ const hide = () => {
186
+ clearTimeout(showTimeout.value)
187
+
188
+ hideTimeout.value = window.setTimeout(() => {
189
+ isVisible.value = false
190
+ emit('hide')
191
+ }, props.hideDelay)
192
+ }
193
+
194
+ const handleMouseEnter = () => {
195
+ if (props.trigger === 'hover') {
196
+ show()
197
+ }
198
+ }
199
+
200
+ const handleMouseLeave = () => {
201
+ if (props.trigger === 'hover') {
202
+ hide()
203
+ }
204
+ }
205
+
206
+ const handleFocus = () => {
207
+ if (props.trigger === 'focus') {
208
+ show()
209
+ }
210
+ }
211
+
212
+ const handleBlur = () => {
213
+ if (props.trigger === 'focus') {
214
+ hide()
215
+ }
216
+ }
217
+
218
+ const handleClick = () => {
219
+ if (props.trigger === 'click') {
220
+ if (isVisible.value) {
221
+ hide()
222
+ } else {
223
+ show()
224
+ }
225
+ }
226
+ }
227
+
228
+ const onEnter = () => {
229
+ calculatePosition()
230
+ }
231
+
232
+ const onLeave = () => {
233
+ tooltipStyle.value = {}
234
+ }
235
+
236
+ // Handle window resize
237
+ const handleResize = () => {
238
+ if (isVisible.value) {
239
+ calculatePosition()
240
+ }
241
+ }
242
+
243
+ // Handle scroll
244
+ const handleScroll = () => {
245
+ if (isVisible.value) {
246
+ calculatePosition()
247
+ }
248
+ }
249
+
250
+ // Lifecycle
251
+ onMounted(() => {
252
+ window.addEventListener('resize', handleResize)
253
+ window.addEventListener('scroll', handleScroll, true)
254
+ })
255
+
256
+ onUnmounted(() => {
257
+ clearTimeout(showTimeout.value)
258
+ clearTimeout(hideTimeout.value)
259
+ window.removeEventListener('resize', handleResize)
260
+ window.removeEventListener('scroll', handleScroll, true)
261
+ })
262
+
263
+ // Expose methods
264
+ defineExpose({
265
+ show,
266
+ hide,
267
+ isVisible: computed(() => isVisible.value)
268
+ })
269
+ </script>
270
+
271
+ <style scoped>
272
+ .dm-tooltip-trigger {
273
+ @apply inline-block;
274
+ }
275
+
276
+ .dm-tooltip {
277
+ @apply absolute z-50 px-2 py-1 text-sm rounded shadow-lg pointer-events-none;
278
+ border-radius: var(--dm-radius);
279
+ font-size: var(--dm-text-sm);
280
+ }
281
+
282
+ /* Variants */
283
+ .dm-tooltip--dark {
284
+ @apply bg-gray-900 text-white;
285
+ background-color: var(--dm-gray-900);
286
+ color: var(--dm-bg-primary, #ffffff);
287
+ }
288
+
289
+ .dm-tooltip--light {
290
+ @apply bg-white text-gray-900 border border-gray-200;
291
+ background-color: var(--dm-bg-primary, #ffffff);
292
+ color: var(--dm-text-primary);
293
+ border-color: var(--dm-gray-200, #e5e7eb);
294
+ }
295
+
296
+ .dm-tooltip--primary {
297
+ @apply text-white;
298
+ background: var(--gradient-primary);
299
+ color: var(--dm-bg-primary, #ffffff);
300
+ }
301
+
302
+ .dm-tooltip--error {
303
+ @apply text-white;
304
+ background-color: var(--dm-error);
305
+ color: var(--dm-bg-primary, #ffffff);
306
+ }
307
+
308
+ .dm-tooltip--warning {
309
+ @apply text-white;
310
+ background-color: var(--dm-warning);
311
+ color: var(--dm-bg-primary, #ffffff);
312
+ }
313
+
314
+ .dm-tooltip--success {
315
+ @apply text-white;
316
+ background-color: var(--dm-success);
317
+ color: var(--dm-bg-primary, #ffffff);
318
+ }
319
+
320
+ /* Dark mode adjustments */
321
+ [data-theme="dark"] .dm-tooltip--dark {
322
+ background-color: var(--dm-gray-800, #1f2937);
323
+ color: var(--dm-text-primary);
324
+ }
325
+
326
+ [data-theme="dark"] .dm-tooltip--light {
327
+ background-color: var(--dm-bg-secondary);
328
+ color: var(--dm-text-primary);
329
+ border-color: var(--dm-gray-600, #4b5563);
330
+ }
331
+
332
+ .dm-tooltip__content {
333
+ @apply relative z-10;
334
+ }
335
+
336
+ /* Arrow styles */
337
+ .dm-tooltip--arrow .dm-tooltip__arrow {
338
+ @apply absolute w-2 h-2 transform rotate-45;
339
+ }
340
+
341
+ .dm-tooltip--top .dm-tooltip__arrow {
342
+ @apply bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2;
343
+ }
344
+
345
+ .dm-tooltip--bottom .dm-tooltip__arrow {
346
+ @apply top-0 left-1/2 -translate-x-1/2 -translate-y-1/2;
347
+ }
348
+
349
+ .dm-tooltip--left .dm-tooltip__arrow {
350
+ @apply right-0 top-1/2 translate-x-1/2 -translate-y-1/2;
351
+ }
352
+
353
+ .dm-tooltip--right .dm-tooltip__arrow {
354
+ @apply left-0 top-1/2 -translate-x-1/2 -translate-y-1/2;
355
+ }
356
+
357
+ /* Arrow colors */
358
+ .dm-tooltip--dark .dm-tooltip__arrow {
359
+ background-color: var(--dm-gray-900);
360
+ }
361
+
362
+ .dm-tooltip--light .dm-tooltip__arrow {
363
+ background-color: var(--dm-bg-primary, #ffffff);
364
+ border: 1px solid var(--dm-gray-200, #e5e7eb);
365
+ }
366
+
367
+ .dm-tooltip--primary .dm-tooltip__arrow {
368
+ background-color: var(--dm-primary);
369
+ }
370
+
371
+ .dm-tooltip--error .dm-tooltip__arrow {
372
+ background-color: var(--dm-error);
373
+ }
374
+
375
+ .dm-tooltip--warning .dm-tooltip__arrow {
376
+ background-color: var(--dm-warning);
377
+ }
378
+
379
+ .dm-tooltip--success .dm-tooltip__arrow {
380
+ background-color: var(--dm-success);
381
+ }
382
+
383
+ /* Transitions */
384
+ .tooltip-enter-active,
385
+ .tooltip-leave-active {
386
+ transition: opacity var(--transition-fast), transform var(--transition-fast);
387
+ }
388
+
389
+ .tooltip-enter-from,
390
+ .tooltip-leave-to {
391
+ opacity: 0;
392
+ transform: scale(0.95);
393
+ }
394
+
395
+ /* Reduced motion support */
396
+ @media (prefers-reduced-motion: reduce) {
397
+ .tooltip-enter-active,
398
+ .tooltip-leave-active {
399
+ transition: none;
400
+ }
401
+ }
402
+
403
+ /* High contrast mode support */
404
+ @media (prefers-contrast: high) {
405
+ .dm-tooltip {
406
+ @apply border-2 border-current;
407
+ }
408
+ }
409
409
  </style>