daisy-ui-kit 5.0.0-pre.9 → 5.0.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/app/components/Accordion.vue +8 -5
- package/app/components/Alert.vue +2 -1
- package/app/components/Avatar.vue +10 -7
- package/app/components/AvatarGroup.vue +6 -2
- package/app/components/Badge.vue +19 -1
- package/app/components/Button.vue +66 -46
- package/app/components/Calendar.vue +151 -42
- package/app/components/CalendarInput.vue +229 -130
- package/app/components/CalendarSkeleton.vue +51 -10
- package/app/components/Card.vue +20 -2
- package/app/components/CardActions.vue +1 -1
- package/app/components/CardBody.vue +1 -1
- package/app/components/CardTitle.vue +1 -1
- package/app/components/Carousel.vue +2 -1
- package/app/components/Chat.vue +6 -1
- package/app/components/Checkbox.vue +1 -1
- package/app/components/Collapse.vue +38 -5
- package/app/components/CollapseTitle.vue +11 -1
- package/app/components/Countdown.vue +3 -3
- package/app/components/CountdownTimers.vue +4 -7
- package/app/components/Counter.vue +14 -3
- package/app/components/DaisyLink.vue +33 -15
- package/app/components/Dock.vue +5 -6
- package/app/components/DockItem.vue +5 -3
- package/app/components/Drawer.vue +15 -12
- package/app/components/DrawerContent.vue +9 -6
- package/app/components/DrawerSide.vue +9 -6
- package/app/components/Dropdown.vue +61 -50
- package/app/components/DropdownButton.vue +11 -4
- package/app/components/DropdownContent.vue +90 -20
- package/app/components/DropdownTarget.vue +10 -3
- package/app/components/Fab.vue +16 -0
- package/app/components/FabClose.vue +18 -0
- package/app/components/FabMainAction.vue +5 -0
- package/app/components/FabTrigger.vue +117 -0
- package/app/components/Fieldset.vue +5 -4
- package/app/components/FileInput.vue +1 -1
- package/app/components/Filter.vue +45 -38
- package/app/components/Flex.vue +8 -1
- package/app/components/FlexItem.vue +30 -27
- package/app/components/Footer.vue +16 -12
- package/app/components/FooterTitle.vue +8 -5
- package/app/components/Hero.vue +9 -6
- package/app/components/HeroContent.vue +9 -6
- package/app/components/Hover3D.vue +22 -0
- package/app/components/HoverGallery.vue +11 -0
- package/app/components/Indicator.vue +12 -5
- package/app/components/IndicatorItem.vue +21 -14
- package/app/components/Input.vue +44 -47
- package/app/components/Kbd.vue +2 -1
- package/app/components/Label.vue +32 -29
- package/app/components/MenuExpand.vue +5 -13
- package/app/components/MenuExpandToggle.vue +7 -1
- package/app/components/MenuItem.vue +6 -4
- package/app/components/Modal.vue +23 -17
- package/app/components/Progress.vue +13 -1
- package/app/components/Prose.vue +7 -2
- package/app/components/RadialProgress.vue +8 -8
- package/app/components/Radio.vue +1 -1
- package/app/components/RadioGroup.vue +2 -2
- package/app/components/Range.vue +186 -46
- package/app/components/RangeMeasure.vue +33 -30
- package/app/components/RangeMeasureTick.vue +4 -5
- package/app/components/Rating.vue +70 -53
- package/app/components/Select.vue +44 -47
- package/app/components/SkeletonText.vue +11 -0
- package/app/components/Stack.vue +5 -0
- package/app/components/Steps.vue +7 -2
- package/app/components/Swap.vue +4 -10
- package/app/components/Tab.vue +23 -5
- package/app/components/Text.vue +47 -23
- package/app/components/TextArea.vue +75 -30
- package/app/components/TextRotate.vue +24 -0
- package/app/components/ThemeController.vue +3 -4
- package/app/components/ThemeProvider.vue +47 -32
- package/app/components/TimelineLine.vue +1 -1
- package/app/components/TimelineStart.vue +2 -1
- package/app/components/Toast.vue +3 -8
- package/app/components/Toggle.vue +2 -2
- package/app/components/Tooltip.vue +111 -21
- package/app/components/TooltipContent.vue +279 -1
- package/app/components/TooltipTarget.vue +20 -0
- package/app/composables/__tests__/use-calendar.test.ts +239 -0
- package/app/composables/use-calendar.ts +288 -0
- package/app/composables/use-daisy-theme.ts +140 -0
- package/app/composables/use-toast.ts +345 -0
- package/app/composables/useSearch.ts +22 -0
- package/app/utils/drawer-utils.ts +15 -13
- package/app/utils/position-area.ts +40 -0
- package/nuxt.d.ts +13 -0
- package/nuxt.js +12 -9
- package/package.json +40 -29
- package/app/utils/random-string.ts +0 -19
|
@@ -1,30 +1,115 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
2
|
+
import { useId } from 'vue'
|
|
3
|
+
import { useElementHover } from '@vueuse/core'
|
|
4
|
+
import { computed, onMounted, provide, ref, watch } from 'vue'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(
|
|
7
|
+
defineProps<{
|
|
8
|
+
tip?: string | number
|
|
9
|
+
open?: boolean
|
|
10
|
+
|
|
11
|
+
color?: string
|
|
12
|
+
neutral?: boolean
|
|
13
|
+
primary?: boolean
|
|
14
|
+
secondary?: boolean
|
|
15
|
+
accent?: boolean
|
|
16
|
+
info?: boolean
|
|
17
|
+
success?: boolean
|
|
18
|
+
warning?: boolean
|
|
19
|
+
error?: boolean
|
|
20
|
+
|
|
21
|
+
position?: 'top' | 'right' | 'bottom' | 'left'
|
|
22
|
+
top?: boolean
|
|
23
|
+
right?: boolean
|
|
24
|
+
bottom?: boolean
|
|
25
|
+
left?: boolean
|
|
26
|
+
|
|
27
|
+
// Popover mode props
|
|
28
|
+
delayEnter?: number
|
|
29
|
+
delayLeave?: number
|
|
30
|
+
}>(),
|
|
31
|
+
{
|
|
32
|
+
delayEnter: 200,
|
|
33
|
+
delayLeave: 100,
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
// Detect if using popover mode (when TooltipTarget/TooltipContent are used)
|
|
38
|
+
const isPopoverMode = computed(() => {
|
|
39
|
+
return !props.tip
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Popover mode setup
|
|
43
|
+
const isOpen = defineModel('open', { default: false })
|
|
44
|
+
provide('isTooltipOpen', isOpen)
|
|
45
|
+
|
|
46
|
+
const uniqueId = useId()
|
|
47
|
+
const id = `tooltip-${uniqueId}`
|
|
48
|
+
provide('tooltipId', id)
|
|
49
|
+
|
|
50
|
+
// Compute placement for CSS anchor positioning
|
|
51
|
+
const placement = computed(() => {
|
|
52
|
+
if (props.top || props.position === 'top') return 'top'
|
|
53
|
+
if (props.right || props.position === 'right') return 'right'
|
|
54
|
+
if (props.left || props.position === 'left') return 'left'
|
|
55
|
+
return 'bottom' // default
|
|
56
|
+
})
|
|
57
|
+
provide('tooltipPlacement', placement)
|
|
58
|
+
|
|
59
|
+
// Provide color for TooltipContent
|
|
60
|
+
const color = computed(() => {
|
|
61
|
+
if (props.color) return props.color
|
|
62
|
+
if (props.neutral) return 'neutral'
|
|
63
|
+
if (props.primary) return 'primary'
|
|
64
|
+
if (props.secondary) return 'secondary'
|
|
65
|
+
if (props.accent) return 'accent'
|
|
66
|
+
if (props.info) return 'info'
|
|
67
|
+
if (props.success) return 'success'
|
|
68
|
+
if (props.warning) return 'warning'
|
|
69
|
+
if (props.error) return 'error'
|
|
70
|
+
return null
|
|
71
|
+
})
|
|
72
|
+
provide('tooltipColor', color)
|
|
73
|
+
|
|
74
|
+
// References
|
|
75
|
+
const targetEl = ref(null)
|
|
76
|
+
const contentEl = ref(null)
|
|
77
|
+
provide('targetEl', targetEl)
|
|
78
|
+
provide('contentEl', contentEl)
|
|
79
|
+
|
|
80
|
+
// Visibility utils
|
|
81
|
+
function open() {
|
|
82
|
+
isOpen.value = true
|
|
83
|
+
}
|
|
84
|
+
function close() {
|
|
85
|
+
isOpen.value = false
|
|
86
|
+
}
|
|
87
|
+
provide('openTooltip', open)
|
|
88
|
+
provide('closeTooltip', close)
|
|
89
|
+
|
|
90
|
+
const tooltipWrapper = ref(null)
|
|
91
|
+
|
|
92
|
+
onMounted(() => {
|
|
93
|
+
if (isPopoverMode.value) {
|
|
94
|
+
const hover = useElementHover(tooltipWrapper, {
|
|
95
|
+
delayLeave: props.delayLeave,
|
|
96
|
+
delayEnter: props.delayEnter,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
watch(hover, newValue => {
|
|
100
|
+
isOpen.value = newValue
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
})
|
|
22
104
|
</script>
|
|
23
105
|
|
|
24
106
|
<template>
|
|
107
|
+
<!-- CSS-only mode (when tip prop is provided) -->
|
|
25
108
|
<div
|
|
109
|
+
v-if="!isPopoverMode"
|
|
26
110
|
:data-tip="tip"
|
|
27
|
-
class="tooltip"
|
|
111
|
+
class="tooltip"
|
|
112
|
+
:class="{
|
|
28
113
|
'tooltip-open': props.open,
|
|
29
114
|
|
|
30
115
|
'tooltip-top': props.top || props.position === 'top',
|
|
@@ -44,4 +129,9 @@ const props = defineProps<{
|
|
|
44
129
|
>
|
|
45
130
|
<slot />
|
|
46
131
|
</div>
|
|
132
|
+
|
|
133
|
+
<!-- Popover API mode (when using TooltipTarget/TooltipContent) -->
|
|
134
|
+
<div v-else ref="tooltipWrapper" class="tooltip-wrapper inline-block">
|
|
135
|
+
<slot />
|
|
136
|
+
</div>
|
|
47
137
|
</template>
|
|
@@ -1,5 +1,283 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, inject, ref, watch } from 'vue'
|
|
3
|
+
import { getPositionArea, getPositionFallbacks } from '../utils/position-area'
|
|
4
|
+
|
|
5
|
+
const id = inject('tooltipId', null)
|
|
6
|
+
const isOpen = inject('isTooltipOpen', ref(false))
|
|
7
|
+
const contentEl = inject('contentEl', ref(null))
|
|
8
|
+
const placement = inject('tooltipPlacement', ref('bottom'))
|
|
9
|
+
const color = inject('tooltipColor', ref(null))
|
|
10
|
+
|
|
11
|
+
// Check if we're in popover mode (inside a Tooltip wrapper with context)
|
|
12
|
+
const isPopoverMode = computed(() => id !== null)
|
|
13
|
+
|
|
14
|
+
// Compute CSS position-area value based on placement
|
|
15
|
+
const positionArea = computed(() => getPositionArea(placement.value))
|
|
16
|
+
const positionFallbacks = computed(() => getPositionFallbacks(placement.value))
|
|
17
|
+
|
|
18
|
+
// Color classes for the tooltip
|
|
19
|
+
const colorClass = computed(() => {
|
|
20
|
+
if (!color.value) return ''
|
|
21
|
+
const colorMap = {
|
|
22
|
+
neutral: 'tooltip-neutral',
|
|
23
|
+
primary: 'tooltip-primary',
|
|
24
|
+
secondary: 'tooltip-secondary',
|
|
25
|
+
accent: 'tooltip-accent',
|
|
26
|
+
info: 'tooltip-info',
|
|
27
|
+
success: 'tooltip-success',
|
|
28
|
+
warning: 'tooltip-warning',
|
|
29
|
+
error: 'tooltip-error',
|
|
30
|
+
}
|
|
31
|
+
return colorMap[color.value] || ''
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Sync popover state with isOpen model
|
|
35
|
+
watch(
|
|
36
|
+
isOpen,
|
|
37
|
+
newValue => {
|
|
38
|
+
if (!isPopoverMode.value || !contentEl.value) return
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const isPopoverOpen = contentEl.value.matches(':popover-open')
|
|
42
|
+
|
|
43
|
+
if (newValue && !isPopoverOpen) {
|
|
44
|
+
contentEl.value.showPopover()
|
|
45
|
+
} else if (!newValue && isPopoverOpen) {
|
|
46
|
+
contentEl.value.hidePopover()
|
|
47
|
+
}
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.warn('Popover API not supported:', e)
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{ flush: 'post' },
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
// Listen to popover toggle events to sync back to isOpen model
|
|
56
|
+
function handleToggle(event) {
|
|
57
|
+
const newState = event.newState === 'open'
|
|
58
|
+
if (isOpen.value !== newState) {
|
|
59
|
+
isOpen.value = newState
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
</script>
|
|
63
|
+
|
|
1
64
|
<template>
|
|
2
|
-
|
|
65
|
+
<!-- Popover mode -->
|
|
66
|
+
<div
|
|
67
|
+
v-if="isPopoverMode"
|
|
68
|
+
:id="`${id}-content`"
|
|
69
|
+
ref="contentEl"
|
|
70
|
+
:anchor="id"
|
|
71
|
+
:data-placement="placement"
|
|
72
|
+
role="tooltip"
|
|
73
|
+
popover="manual"
|
|
74
|
+
class="tooltip-popover"
|
|
75
|
+
:class="colorClass"
|
|
76
|
+
:style="{
|
|
77
|
+
'position-anchor': `--${id}`,
|
|
78
|
+
'position-area': positionArea,
|
|
79
|
+
'position-try-fallbacks': positionFallbacks,
|
|
80
|
+
}"
|
|
81
|
+
@toggle="handleToggle"
|
|
82
|
+
>
|
|
83
|
+
<slot />
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<!-- Simple mode (no Tooltip wrapper context) -->
|
|
87
|
+
<div v-else class="tooltip-content">
|
|
3
88
|
<slot />
|
|
4
89
|
</div>
|
|
5
90
|
</template>
|
|
91
|
+
|
|
92
|
+
<style>
|
|
93
|
+
@layer components {
|
|
94
|
+
.tooltip-popover[popover] {
|
|
95
|
+
border: none;
|
|
96
|
+
overflow: visible;
|
|
97
|
+
inset: auto;
|
|
98
|
+
margin: 0;
|
|
99
|
+
padding: 0.25rem 0.5rem;
|
|
100
|
+
font-size: 0.875rem;
|
|
101
|
+
line-height: 1.25rem;
|
|
102
|
+
border-radius: var(--radius-selector, 0.25rem);
|
|
103
|
+
background-color: var(--color-neutral);
|
|
104
|
+
color: var(--color-neutral-content);
|
|
105
|
+
max-width: 20rem;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.tooltip-popover[popover]:popover-open {
|
|
109
|
+
position: fixed;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Arrow base styles */
|
|
113
|
+
.tooltip-popover[popover]::before {
|
|
114
|
+
content: '';
|
|
115
|
+
position: absolute;
|
|
116
|
+
border: 6px solid transparent;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Bottom placement - arrow points up */
|
|
120
|
+
.tooltip-popover[data-placement='bottom'] {
|
|
121
|
+
margin-top: 6px;
|
|
122
|
+
}
|
|
123
|
+
.tooltip-popover[data-placement='bottom']::before {
|
|
124
|
+
bottom: 100%;
|
|
125
|
+
left: 50%;
|
|
126
|
+
transform: translateX(-50%);
|
|
127
|
+
border-bottom-color: var(--color-neutral);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Top placement - arrow points down */
|
|
131
|
+
.tooltip-popover[data-placement='top'] {
|
|
132
|
+
margin-bottom: 6px;
|
|
133
|
+
}
|
|
134
|
+
.tooltip-popover[data-placement='top']::before {
|
|
135
|
+
top: 100%;
|
|
136
|
+
left: 50%;
|
|
137
|
+
transform: translateX(-50%);
|
|
138
|
+
border-top-color: var(--color-neutral);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Right placement - arrow points left */
|
|
142
|
+
.tooltip-popover[data-placement='right'] {
|
|
143
|
+
margin-left: 6px;
|
|
144
|
+
}
|
|
145
|
+
.tooltip-popover[data-placement='right']::before {
|
|
146
|
+
right: 100%;
|
|
147
|
+
top: 50%;
|
|
148
|
+
transform: translateY(-50%);
|
|
149
|
+
border-right-color: var(--color-neutral);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Left placement - arrow points right */
|
|
153
|
+
.tooltip-popover[data-placement='left'] {
|
|
154
|
+
margin-right: 6px;
|
|
155
|
+
}
|
|
156
|
+
.tooltip-popover[data-placement='left']::before {
|
|
157
|
+
left: 100%;
|
|
158
|
+
top: 50%;
|
|
159
|
+
transform: translateY(-50%);
|
|
160
|
+
border-left-color: var(--color-neutral);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* Color variants */
|
|
164
|
+
.tooltip-popover.tooltip-primary {
|
|
165
|
+
background-color: var(--color-primary);
|
|
166
|
+
color: var(--color-primary-content);
|
|
167
|
+
}
|
|
168
|
+
.tooltip-popover.tooltip-primary[data-placement='bottom']::before {
|
|
169
|
+
border-bottom-color: var(--color-primary);
|
|
170
|
+
}
|
|
171
|
+
.tooltip-popover.tooltip-primary[data-placement='top']::before {
|
|
172
|
+
border-top-color: var(--color-primary);
|
|
173
|
+
}
|
|
174
|
+
.tooltip-popover.tooltip-primary[data-placement='right']::before {
|
|
175
|
+
border-right-color: var(--color-primary);
|
|
176
|
+
}
|
|
177
|
+
.tooltip-popover.tooltip-primary[data-placement='left']::before {
|
|
178
|
+
border-left-color: var(--color-primary);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.tooltip-popover.tooltip-secondary {
|
|
182
|
+
background-color: var(--color-secondary);
|
|
183
|
+
color: var(--color-secondary-content);
|
|
184
|
+
}
|
|
185
|
+
.tooltip-popover.tooltip-secondary[data-placement='bottom']::before {
|
|
186
|
+
border-bottom-color: var(--color-secondary);
|
|
187
|
+
}
|
|
188
|
+
.tooltip-popover.tooltip-secondary[data-placement='top']::before {
|
|
189
|
+
border-top-color: var(--color-secondary);
|
|
190
|
+
}
|
|
191
|
+
.tooltip-popover.tooltip-secondary[data-placement='right']::before {
|
|
192
|
+
border-right-color: var(--color-secondary);
|
|
193
|
+
}
|
|
194
|
+
.tooltip-popover.tooltip-secondary[data-placement='left']::before {
|
|
195
|
+
border-left-color: var(--color-secondary);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.tooltip-popover.tooltip-accent {
|
|
199
|
+
background-color: var(--color-accent);
|
|
200
|
+
color: var(--color-accent-content);
|
|
201
|
+
}
|
|
202
|
+
.tooltip-popover.tooltip-accent[data-placement='bottom']::before {
|
|
203
|
+
border-bottom-color: var(--color-accent);
|
|
204
|
+
}
|
|
205
|
+
.tooltip-popover.tooltip-accent[data-placement='top']::before {
|
|
206
|
+
border-top-color: var(--color-accent);
|
|
207
|
+
}
|
|
208
|
+
.tooltip-popover.tooltip-accent[data-placement='right']::before {
|
|
209
|
+
border-right-color: var(--color-accent);
|
|
210
|
+
}
|
|
211
|
+
.tooltip-popover.tooltip-accent[data-placement='left']::before {
|
|
212
|
+
border-left-color: var(--color-accent);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.tooltip-popover.tooltip-info {
|
|
216
|
+
background-color: var(--color-info);
|
|
217
|
+
color: var(--color-info-content);
|
|
218
|
+
}
|
|
219
|
+
.tooltip-popover.tooltip-info[data-placement='bottom']::before {
|
|
220
|
+
border-bottom-color: var(--color-info);
|
|
221
|
+
}
|
|
222
|
+
.tooltip-popover.tooltip-info[data-placement='top']::before {
|
|
223
|
+
border-top-color: var(--color-info);
|
|
224
|
+
}
|
|
225
|
+
.tooltip-popover.tooltip-info[data-placement='right']::before {
|
|
226
|
+
border-right-color: var(--color-info);
|
|
227
|
+
}
|
|
228
|
+
.tooltip-popover.tooltip-info[data-placement='left']::before {
|
|
229
|
+
border-left-color: var(--color-info);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.tooltip-popover.tooltip-success {
|
|
233
|
+
background-color: var(--color-success);
|
|
234
|
+
color: var(--color-success-content);
|
|
235
|
+
}
|
|
236
|
+
.tooltip-popover.tooltip-success[data-placement='bottom']::before {
|
|
237
|
+
border-bottom-color: var(--color-success);
|
|
238
|
+
}
|
|
239
|
+
.tooltip-popover.tooltip-success[data-placement='top']::before {
|
|
240
|
+
border-top-color: var(--color-success);
|
|
241
|
+
}
|
|
242
|
+
.tooltip-popover.tooltip-success[data-placement='right']::before {
|
|
243
|
+
border-right-color: var(--color-success);
|
|
244
|
+
}
|
|
245
|
+
.tooltip-popover.tooltip-success[data-placement='left']::before {
|
|
246
|
+
border-left-color: var(--color-success);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.tooltip-popover.tooltip-warning {
|
|
250
|
+
background-color: var(--color-warning);
|
|
251
|
+
color: var(--color-warning-content);
|
|
252
|
+
}
|
|
253
|
+
.tooltip-popover.tooltip-warning[data-placement='bottom']::before {
|
|
254
|
+
border-bottom-color: var(--color-warning);
|
|
255
|
+
}
|
|
256
|
+
.tooltip-popover.tooltip-warning[data-placement='top']::before {
|
|
257
|
+
border-top-color: var(--color-warning);
|
|
258
|
+
}
|
|
259
|
+
.tooltip-popover.tooltip-warning[data-placement='right']::before {
|
|
260
|
+
border-right-color: var(--color-warning);
|
|
261
|
+
}
|
|
262
|
+
.tooltip-popover.tooltip-warning[data-placement='left']::before {
|
|
263
|
+
border-left-color: var(--color-warning);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.tooltip-popover.tooltip-error {
|
|
267
|
+
background-color: var(--color-error);
|
|
268
|
+
color: var(--color-error-content);
|
|
269
|
+
}
|
|
270
|
+
.tooltip-popover.tooltip-error[data-placement='bottom']::before {
|
|
271
|
+
border-bottom-color: var(--color-error);
|
|
272
|
+
}
|
|
273
|
+
.tooltip-popover.tooltip-error[data-placement='top']::before {
|
|
274
|
+
border-top-color: var(--color-error);
|
|
275
|
+
}
|
|
276
|
+
.tooltip-popover.tooltip-error[data-placement='right']::before {
|
|
277
|
+
border-right-color: var(--color-error);
|
|
278
|
+
}
|
|
279
|
+
.tooltip-popover.tooltip-error[data-placement='left']::before {
|
|
280
|
+
border-left-color: var(--color-error);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { inject } from 'vue'
|
|
3
|
+
|
|
4
|
+
const id = inject('tooltipId')
|
|
5
|
+
const targetEl = inject('targetEl')
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<div
|
|
10
|
+
:id="id"
|
|
11
|
+
ref="targetEl"
|
|
12
|
+
:aria-describedby="`${id}-content`"
|
|
13
|
+
:popovertarget="`${id}-content`"
|
|
14
|
+
popovertargetaction="toggle"
|
|
15
|
+
:style="{ 'anchor-name': `--${id}` }"
|
|
16
|
+
class="tooltip-target inline-block"
|
|
17
|
+
>
|
|
18
|
+
<slot />
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { useCalendar } from '../use-calendar'
|
|
3
|
+
|
|
4
|
+
describe('useCalendar', () => {
|
|
5
|
+
describe('initialization', () => {
|
|
6
|
+
it('initializes with null date by default', () => {
|
|
7
|
+
const { selectedDate } = useCalendar()
|
|
8
|
+
expect(selectedDate.value).toBeNull()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('initializes with provided date', () => {
|
|
12
|
+
const date = new Date(2025, 5, 15) // June 15, 2025
|
|
13
|
+
const { selectedDate } = useCalendar(date)
|
|
14
|
+
expect(selectedDate.value?.getFullYear()).toBe(2025)
|
|
15
|
+
expect(selectedDate.value?.getMonth()).toBe(5)
|
|
16
|
+
expect(selectedDate.value?.getDate()).toBe(15)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('sets viewDate to today when no initial date provided', () => {
|
|
20
|
+
const { viewDate } = useCalendar()
|
|
21
|
+
const today = new Date()
|
|
22
|
+
expect(viewDate.value.getFullYear()).toBe(today.getFullYear())
|
|
23
|
+
expect(viewDate.value.getMonth()).toBe(today.getMonth())
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('sets viewDate to initial date when provided', () => {
|
|
27
|
+
const date = new Date(2025, 5, 15)
|
|
28
|
+
const { viewDate } = useCalendar(date)
|
|
29
|
+
expect(viewDate.value.getFullYear()).toBe(2025)
|
|
30
|
+
expect(viewDate.value.getMonth()).toBe(5)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('weekday headers', () => {
|
|
35
|
+
it('returns weekday headers starting with Sunday by default', () => {
|
|
36
|
+
const { weekdayHeaders } = useCalendar()
|
|
37
|
+
expect(weekdayHeaders.value).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('returns weekday headers starting with Monday when firstDay is 1', () => {
|
|
41
|
+
const { weekdayHeaders } = useCalendar(null, { firstDay: 1 })
|
|
42
|
+
expect(weekdayHeaders.value).toEqual(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('returns full weekday names for accessibility', () => {
|
|
46
|
+
const { weekdayHeadersFull } = useCalendar()
|
|
47
|
+
expect(weekdayHeadersFull.value).toEqual([
|
|
48
|
+
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
|
|
49
|
+
])
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('month display', () => {
|
|
54
|
+
it('returns correct month name', () => {
|
|
55
|
+
const date = new Date(2025, 5, 15) // June
|
|
56
|
+
const { monthName } = useCalendar(date)
|
|
57
|
+
expect(monthName.value).toBe('June')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('returns correct short month name', () => {
|
|
61
|
+
const date = new Date(2025, 11, 25) // December
|
|
62
|
+
const { monthNameShort } = useCalendar(date)
|
|
63
|
+
expect(monthNameShort.value).toBe('Dec')
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('calendar days', () => {
|
|
68
|
+
it('generates 42 days (6 weeks)', () => {
|
|
69
|
+
const { calendarDays } = useCalendar(new Date(2025, 5, 15))
|
|
70
|
+
expect(calendarDays.value).toHaveLength(42)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('marks days outside current month', () => {
|
|
74
|
+
const { calendarDays } = useCalendar(new Date(2025, 5, 15)) // June 2025
|
|
75
|
+
const outsideDays = calendarDays.value.filter(d => d.isOutsideMonth)
|
|
76
|
+
expect(outsideDays.length).toBeGreaterThan(0)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('marks the selected date', () => {
|
|
80
|
+
const date = new Date(2025, 5, 15)
|
|
81
|
+
const { calendarDays } = useCalendar(date)
|
|
82
|
+
const selectedDays = calendarDays.value.filter(d => d.isSelected)
|
|
83
|
+
expect(selectedDays).toHaveLength(1)
|
|
84
|
+
expect(selectedDays[0]?.day).toBe(15)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('marks today correctly', () => {
|
|
88
|
+
const today = new Date()
|
|
89
|
+
const { calendarDays, goToDate } = useCalendar()
|
|
90
|
+
goToDate(today)
|
|
91
|
+
const todayDays = calendarDays.value.filter(d => d.isToday && !d.isOutsideMonth)
|
|
92
|
+
expect(todayDays).toHaveLength(1)
|
|
93
|
+
expect(todayDays[0]?.day).toBe(today.getDate())
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('navigation', () => {
|
|
98
|
+
it('navigates to next month', () => {
|
|
99
|
+
const { viewMonth, viewYear, nextMonth } = useCalendar(new Date(2025, 5, 15))
|
|
100
|
+
expect(viewMonth.value).toBe(5)
|
|
101
|
+
nextMonth()
|
|
102
|
+
expect(viewMonth.value).toBe(6)
|
|
103
|
+
expect(viewYear.value).toBe(2025)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('navigates to previous month', () => {
|
|
107
|
+
const { viewMonth, viewYear, prevMonth } = useCalendar(new Date(2025, 5, 15))
|
|
108
|
+
expect(viewMonth.value).toBe(5)
|
|
109
|
+
prevMonth()
|
|
110
|
+
expect(viewMonth.value).toBe(4)
|
|
111
|
+
expect(viewYear.value).toBe(2025)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('handles year rollover when navigating forward', () => {
|
|
115
|
+
const { viewMonth, viewYear, nextMonth } = useCalendar(new Date(2025, 11, 15))
|
|
116
|
+
expect(viewMonth.value).toBe(11)
|
|
117
|
+
nextMonth()
|
|
118
|
+
expect(viewMonth.value).toBe(0)
|
|
119
|
+
expect(viewYear.value).toBe(2026)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('handles year rollover when navigating backward', () => {
|
|
123
|
+
const { viewMonth, viewYear, prevMonth } = useCalendar(new Date(2025, 0, 15))
|
|
124
|
+
expect(viewMonth.value).toBe(0)
|
|
125
|
+
prevMonth()
|
|
126
|
+
expect(viewMonth.value).toBe(11)
|
|
127
|
+
expect(viewYear.value).toBe(2024)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('goes to specific month', () => {
|
|
131
|
+
const { viewMonth, goToMonth } = useCalendar(new Date(2025, 5, 15))
|
|
132
|
+
goToMonth(10)
|
|
133
|
+
expect(viewMonth.value).toBe(10)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('goes to specific year', () => {
|
|
137
|
+
const { viewYear, goToYear } = useCalendar(new Date(2025, 5, 15))
|
|
138
|
+
goToYear(2030)
|
|
139
|
+
expect(viewYear.value).toBe(2030)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('goes to today', () => {
|
|
143
|
+
const { viewMonth, viewYear, goToToday } = useCalendar(new Date(2020, 0, 1))
|
|
144
|
+
const today = new Date()
|
|
145
|
+
goToToday()
|
|
146
|
+
expect(viewMonth.value).toBe(today.getMonth())
|
|
147
|
+
expect(viewYear.value).toBe(today.getFullYear())
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
describe('selection', () => {
|
|
152
|
+
it('selects a date', () => {
|
|
153
|
+
const { selectedDate, selectDate } = useCalendar()
|
|
154
|
+
const date = new Date(2025, 5, 20)
|
|
155
|
+
selectDate(date)
|
|
156
|
+
expect(selectedDate.value?.getDate()).toBe(20)
|
|
157
|
+
expect(selectedDate.value?.getMonth()).toBe(5)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('clears selection', () => {
|
|
161
|
+
const { selectedDate, clearSelection } = useCalendar(new Date(2025, 5, 15))
|
|
162
|
+
expect(selectedDate.value).not.toBeNull()
|
|
163
|
+
clearSelection()
|
|
164
|
+
expect(selectedDate.value).toBeNull()
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('does not select disabled dates', () => {
|
|
168
|
+
const minDate = new Date(2025, 5, 10)
|
|
169
|
+
const { selectedDate, selectDate } = useCalendar(null, { minDate })
|
|
170
|
+
selectDate(new Date(2025, 5, 5)) // Before minDate
|
|
171
|
+
expect(selectedDate.value).toBeNull()
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
describe('date constraints', () => {
|
|
176
|
+
it('marks dates before minDate as disabled', () => {
|
|
177
|
+
const minDate = new Date(2025, 5, 15)
|
|
178
|
+
const { calendarDays } = useCalendar(new Date(2025, 5, 20), { minDate })
|
|
179
|
+
const june14 = calendarDays.value.find(d => d.day === 14 && d.month === 5)
|
|
180
|
+
const june15 = calendarDays.value.find(d => d.day === 15 && d.month === 5)
|
|
181
|
+
expect(june14?.isDisabled).toBe(true)
|
|
182
|
+
expect(june15?.isDisabled).toBe(false)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('marks dates after maxDate as disabled', () => {
|
|
186
|
+
const maxDate = new Date(2025, 5, 15)
|
|
187
|
+
const { calendarDays } = useCalendar(new Date(2025, 5, 10), { maxDate })
|
|
188
|
+
const june15 = calendarDays.value.find(d => d.day === 15 && d.month === 5)
|
|
189
|
+
const june16 = calendarDays.value.find(d => d.day === 16 && d.month === 5)
|
|
190
|
+
expect(june15?.isDisabled).toBe(false)
|
|
191
|
+
expect(june16?.isDisabled).toBe(true)
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
describe('formatting', () => {
|
|
196
|
+
it('formats date with default format', () => {
|
|
197
|
+
const { formatDate } = useCalendar(new Date(2025, 5, 15))
|
|
198
|
+
expect(formatDate()).toBe('15 Jun 2025')
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('formats date with custom format', () => {
|
|
202
|
+
const { formatDate } = useCalendar(new Date(2025, 5, 15))
|
|
203
|
+
expect(formatDate('YYYY-MM-DD')).toBe('2025-06-15')
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('formats date with full month name', () => {
|
|
207
|
+
const { formatDate } = useCalendar(new Date(2025, 5, 15))
|
|
208
|
+
expect(formatDate('MMMM D, YYYY')).toBe('June 15, 2025')
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('returns empty string when no date selected', () => {
|
|
212
|
+
const { formatDate } = useCalendar()
|
|
213
|
+
expect(formatDate()).toBe('')
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
describe('utility functions', () => {
|
|
218
|
+
it('isSameDay returns true for same day', () => {
|
|
219
|
+
const { isSameDay } = useCalendar()
|
|
220
|
+
const a = new Date(2025, 5, 15, 10, 30)
|
|
221
|
+
const b = new Date(2025, 5, 15, 20, 45)
|
|
222
|
+
expect(isSameDay(a, b)).toBe(true)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('isSameDay returns false for different days', () => {
|
|
226
|
+
const { isSameDay } = useCalendar()
|
|
227
|
+
const a = new Date(2025, 5, 15)
|
|
228
|
+
const b = new Date(2025, 5, 16)
|
|
229
|
+
expect(isSameDay(a, b)).toBe(false)
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('isSameDay handles null values', () => {
|
|
233
|
+
const { isSameDay } = useCalendar()
|
|
234
|
+
expect(isSameDay(null, new Date())).toBe(false)
|
|
235
|
+
expect(isSameDay(new Date(), null)).toBe(false)
|
|
236
|
+
expect(isSameDay(null, null)).toBe(false)
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
})
|