@datametria/vue-components 1.1.2 → 1.2.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.
- package/README.md +657 -472
- package/dist/index.es.js +1916 -675
- package/dist/index.umd.js +74 -1
- package/dist/vue-components.css +1 -1
- package/package.json +98 -98
- package/src/components/DatametriaAlert.vue +137 -123
- package/src/components/DatametriaBadge.vue +98 -90
- package/src/components/DatametriaButton.vue +165 -157
- package/src/components/DatametriaChip.vue +149 -149
- package/src/components/DatametriaMenu.vue +620 -0
- package/src/components/DatametriaNavbar.vue +252 -227
- package/src/components/DatametriaSkeleton.vue +240 -0
- package/src/components/DatametriaSlider.vue +408 -0
- package/src/components/DatametriaTimePicker.vue +286 -0
- package/src/components/DatametriaToast.vue +176 -163
- package/src/components/DatametriaTooltip.vue +409 -0
- package/src/components/__tests__/DatametriaAlert.test.js +36 -0
- package/src/components/__tests__/DatametriaBadge.test.js +30 -0
- package/src/components/__tests__/DatametriaButton.test.js +31 -0
- package/src/components/__tests__/DatametriaChip.test.js +39 -0
- package/src/components/__tests__/DatametriaNavbar.test.js +49 -0
- package/src/components/__tests__/DatametriaToast.test.js +49 -0
- package/src/composables/useAccessibilityScale.ts +95 -0
- package/src/composables/useBreakpoints.ts +83 -0
- package/src/composables/useHapticFeedback.ts +440 -0
- package/src/composables/useRipple.ts +219 -0
- package/src/index.ts +61 -52
- package/src/stories/Variants.stories.js +96 -0
- package/src/styles/design-tokens.css +623 -31
- package/ACCESSIBILITY.md +0 -78
- package/DESIGN-SYSTEM.md +0 -70
- package/PROGRESS.md +0 -327
|
@@ -0,0 +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
|
+
}
|
|
409
|
+
</style>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import DatametriaAlert from '../DatametriaAlert.vue'
|
|
4
|
+
|
|
5
|
+
describe('DatametriaAlert', () => {
|
|
6
|
+
it('renders with primary variant', () => {
|
|
7
|
+
const wrapper = mount(DatametriaAlert, {
|
|
8
|
+
props: {
|
|
9
|
+
variant: 'primary',
|
|
10
|
+
message: 'Test message'
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
expect(wrapper.classes()).toContain('datametria-alert--primary')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('validates invalid variant in development', () => {
|
|
18
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
19
|
+
const originalEnv = process.env.NODE_ENV
|
|
20
|
+
process.env.NODE_ENV = 'development'
|
|
21
|
+
|
|
22
|
+
mount(DatametriaAlert, {
|
|
23
|
+
props: {
|
|
24
|
+
variant: 'invalid',
|
|
25
|
+
message: 'Test'
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
30
|
+
expect.stringContaining('[DatametriaAlert] Invalid variant "invalid"')
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
process.env.NODE_ENV = originalEnv
|
|
34
|
+
consoleSpy.mockRestore()
|
|
35
|
+
})
|
|
36
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import DatametriaBadge from '../DatametriaBadge.vue'
|
|
4
|
+
|
|
5
|
+
describe('DatametriaBadge', () => {
|
|
6
|
+
it('renders with primary variant', () => {
|
|
7
|
+
const wrapper = mount(DatametriaBadge, {
|
|
8
|
+
props: { label: 'Test', variant: 'primary' }
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
expect(wrapper.classes()).toContain('dm-badge--primary')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('validates invalid variant in development', () => {
|
|
15
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
16
|
+
const originalEnv = process.env.NODE_ENV
|
|
17
|
+
process.env.NODE_ENV = 'development'
|
|
18
|
+
|
|
19
|
+
mount(DatametriaBadge, {
|
|
20
|
+
props: { label: 'Test', variant: 'invalid' }
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
24
|
+
expect.stringContaining('[DatametriaBadge] Invalid variant "invalid"')
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
process.env.NODE_ENV = originalEnv
|
|
28
|
+
consoleSpy.mockRestore()
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import DatametriaButton from '../DatametriaButton.vue'
|
|
4
|
+
import { ButtonVariant } from '../../types'
|
|
5
|
+
|
|
6
|
+
describe('DatametriaButton', () => {
|
|
7
|
+
it('validates invalid variant in development', () => {
|
|
8
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
9
|
+
const originalEnv = process.env.NODE_ENV
|
|
10
|
+
process.env.NODE_ENV = 'development'
|
|
11
|
+
|
|
12
|
+
mount(DatametriaButton, {
|
|
13
|
+
props: { variant: 'invalid' }
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
17
|
+
expect.stringContaining('[DatametriaButton] Invalid variant "invalid"')
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
process.env.NODE_ENV = originalEnv
|
|
21
|
+
consoleSpy.mockRestore()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('renders with primary variant', () => {
|
|
25
|
+
const wrapper = mount(DatametriaButton, {
|
|
26
|
+
props: { variant: ButtonVariant.PRIMARY }
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
expect(wrapper.classes()).toContain('datametria-button--primary')
|
|
30
|
+
})
|
|
31
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import DatametriaChip from '../DatametriaChip.vue'
|
|
4
|
+
|
|
5
|
+
describe('DatametriaChip', () => {
|
|
6
|
+
it('renders with primary variant by default', () => {
|
|
7
|
+
const wrapper = mount(DatametriaChip, {
|
|
8
|
+
props: { label: 'Test' }
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
expect(wrapper.classes()).toContain('dm-chip--primary')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('validates invalid variant in development', () => {
|
|
15
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
16
|
+
const originalEnv = process.env.NODE_ENV
|
|
17
|
+
process.env.NODE_ENV = 'development'
|
|
18
|
+
|
|
19
|
+
mount(DatametriaChip, {
|
|
20
|
+
props: { label: 'Test', variant: 'invalid' }
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
24
|
+
expect.stringContaining('[DatametriaChip] Invalid variant "invalid"')
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
process.env.NODE_ENV = originalEnv
|
|
28
|
+
consoleSpy.mockRestore()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('emits click event when clickable', async () => {
|
|
32
|
+
const wrapper = mount(DatametriaChip, {
|
|
33
|
+
props: { label: 'Test', clickable: true }
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
await wrapper.trigger('click')
|
|
37
|
+
expect(wrapper.emitted('click')).toBeTruthy()
|
|
38
|
+
})
|
|
39
|
+
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import DatametriaNavbar from '../DatametriaNavbar.vue'
|
|
4
|
+
|
|
5
|
+
describe('DatametriaNavbar', () => {
|
|
6
|
+
it('renders with primary variant', () => {
|
|
7
|
+
const wrapper = mount(DatametriaNavbar, {
|
|
8
|
+
props: { variant: 'primary' }
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
expect(wrapper.classes()).toContain('dm-navbar--primary')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('validates invalid variant in development', () => {
|
|
15
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
16
|
+
const originalEnv = process.env.NODE_ENV
|
|
17
|
+
process.env.NODE_ENV = 'development'
|
|
18
|
+
|
|
19
|
+
mount(DatametriaNavbar, {
|
|
20
|
+
props: { variant: 'invalid' }
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
24
|
+
expect.stringContaining('[DatametriaNavbar] Invalid variant "invalid"')
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
process.env.NODE_ENV = originalEnv
|
|
28
|
+
consoleSpy.mockRestore()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('renders brand text', () => {
|
|
32
|
+
const wrapper = mount(DatametriaNavbar, {
|
|
33
|
+
props: { brand: 'DATAMETRIA' }
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
expect(wrapper.text()).toContain('DATAMETRIA')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('toggles mobile menu', async () => {
|
|
40
|
+
const wrapper = mount(DatametriaNavbar, {
|
|
41
|
+
slots: { menu: '<div>Menu items</div>' }
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const toggle = wrapper.find('.dm-navbar__toggle')
|
|
45
|
+
await toggle.trigger('click')
|
|
46
|
+
|
|
47
|
+
expect(wrapper.find('.dm-navbar__menu').classes()).toContain('dm-navbar__menu--open')
|
|
48
|
+
})
|
|
49
|
+
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import DatametriaToast from '../DatametriaToast.vue'
|
|
4
|
+
|
|
5
|
+
describe('DatametriaToast', () => {
|
|
6
|
+
it('renders with primary variant', () => {
|
|
7
|
+
// Create body element for Teleport
|
|
8
|
+
const body = document.createElement('div')
|
|
9
|
+
document.body.appendChild(body)
|
|
10
|
+
|
|
11
|
+
const wrapper = mount(DatametriaToast, {
|
|
12
|
+
props: {
|
|
13
|
+
message: 'Test message',
|
|
14
|
+
variant: 'primary',
|
|
15
|
+
modelValue: true
|
|
16
|
+
},
|
|
17
|
+
attachTo: body
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// Check if toast exists in document body
|
|
21
|
+
const toast = document.querySelector('.dm-toast')
|
|
22
|
+
expect(toast).toBeTruthy()
|
|
23
|
+
expect(toast.classList.contains('dm-toast--primary')).toBe(true)
|
|
24
|
+
|
|
25
|
+
wrapper.unmount()
|
|
26
|
+
document.body.removeChild(body)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('validates invalid variant in development', () => {
|
|
30
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
31
|
+
const originalEnv = process.env.NODE_ENV
|
|
32
|
+
process.env.NODE_ENV = 'development'
|
|
33
|
+
|
|
34
|
+
mount(DatametriaToast, {
|
|
35
|
+
props: {
|
|
36
|
+
message: 'Test',
|
|
37
|
+
variant: 'invalid',
|
|
38
|
+
modelValue: true
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
43
|
+
expect.stringContaining('[DatametriaToast] Invalid variant "invalid"')
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
process.env.NODE_ENV = originalEnv
|
|
47
|
+
consoleSpy.mockRestore()
|
|
48
|
+
})
|
|
49
|
+
})
|