daisy-ui-kit 5.2.4 → 5.2.7
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 +3 -1
- package/app/components/Avatar.vue +1 -1
- package/app/components/MenuItem.vue +10 -5
- package/app/components/Prose.vue +7 -7
- package/app/composables/use-toast.ts +2 -6
- package/package.json +52 -44
- package/app/components/content/CodeWrapper.vue +0 -8
- package/app/components/content/ColorBadge.vue +0 -24
- package/app/components/content/ComponentsTree.vue +0 -39
- package/app/components/content/DarkToggle.vue +0 -13
- package/app/components/content/DemoExampleResponsive.vue +0 -67
- package/app/components/content/IframeRenderer.ts +0 -57
- package/app/components/content/Indent.vue +0 -3
- package/app/components/content/LocalLinks.vue +0 -30
- package/app/components/content/Logo.vue +0 -8
- package/app/components/content/NotFound.vue +0 -38
- package/app/components/content/PageNext.vue +0 -25
- package/app/components/content/PagePrevious.vue +0 -25
- package/app/components/content/ProseA.vue +0 -19
- package/app/components/content/ProseAlert.vue +0 -11
- package/app/components/content/ProseBlockquote.vue +0 -11
- package/app/components/content/ProseCode.vue +0 -3
- package/app/components/content/ProseEm.vue +0 -5
- package/app/components/content/ProseH1.vue +0 -22
- package/app/components/content/ProseH2.vue +0 -22
- package/app/components/content/ProseH3.vue +0 -22
- package/app/components/content/ProseH4.vue +0 -22
- package/app/components/content/ProseH5.vue +0 -22
- package/app/components/content/ProseH6.vue +0 -22
- package/app/components/content/ProseHr.vue +0 -3
- package/app/components/content/ProseImg.vue +0 -40
- package/app/components/content/ProseLi.vue +0 -3
- package/app/components/content/ProseOl.vue +0 -5
- package/app/components/content/ProseP.vue +0 -3
- package/app/components/content/ProsePre.vue +0 -38
- package/app/components/content/ProsePre2.vue +0 -68
- package/app/components/content/ProseStrong.vue +0 -5
- package/app/components/content/ProseTable.vue +0 -7
- package/app/components/content/ProseTbody.vue +0 -5
- package/app/components/content/ProseTd.vue +0 -5
- package/app/components/content/ProseTh.vue +0 -5
- package/app/components/content/ProseThead.vue +0 -5
- package/app/components/content/ProseTr.vue +0 -5
- package/app/components/content/ProseUl.vue +0 -5
- package/app/components/content/Search.vue +0 -171
- package/app/components/content/SearchButton.vue +0 -14
- package/app/components/content/SearchModal.vue +0 -292
- package/app/components/content/Sidebar.vue +0 -87
- package/app/components/content/SidebarMenuSection.vue +0 -43
- package/app/components/content/SigninForm.vue +0 -34
- package/app/components/content/TableOfContents.vue +0 -304
- package/app/components/content/TocTree.vue +0 -66
- package/app/components/content/TypeBadge.vue +0 -18
- package/app/components/content/UserMenu.vue +0 -48
- package/app/composables/__tests__/use-calendar.test.ts +0 -239
- package/app/composables/useSearch.ts +0 -24
- package/app/utils/debug-shim.mjs +0 -23
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
3
|
-
import { useRoute } from 'vue-router'
|
|
4
|
-
import TocTree from './TocTree.vue'
|
|
5
|
-
|
|
6
|
-
const pageRoute = useRoute()
|
|
7
|
-
|
|
8
|
-
// Interface for our TOC structure
|
|
9
|
-
interface TocLink {
|
|
10
|
-
id: string
|
|
11
|
-
text: string
|
|
12
|
-
level: number
|
|
13
|
-
children: TocLink[]
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Create a reactive reference for our TOC links
|
|
17
|
-
const tocLinks = ref<TocLink[]>([])
|
|
18
|
-
const pageTitle = ref('Table of Contents')
|
|
19
|
-
|
|
20
|
-
// Track visible headings and active heading
|
|
21
|
-
const visibleHeadings = ref<Set<string>>(new Set())
|
|
22
|
-
const activeHeading = ref<string | null>(null)
|
|
23
|
-
|
|
24
|
-
// Track the currently clicked heading for immediate highlight
|
|
25
|
-
const clickedHeading = ref<string | null>(null)
|
|
26
|
-
|
|
27
|
-
// Intersection observer for tracking visible headings
|
|
28
|
-
let headingObserver: IntersectionObserver | null = null
|
|
29
|
-
|
|
30
|
-
function setupIntersectionObserver() {
|
|
31
|
-
if (typeof IntersectionObserver === 'undefined') return
|
|
32
|
-
|
|
33
|
-
headingObserver = new IntersectionObserver(
|
|
34
|
-
entries => {
|
|
35
|
-
entries.forEach(entry => {
|
|
36
|
-
const id = entry.target.id
|
|
37
|
-
if (entry.isIntersecting) {
|
|
38
|
-
visibleHeadings.value.add(id)
|
|
39
|
-
} else {
|
|
40
|
-
visibleHeadings.value.delete(id)
|
|
41
|
-
}
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
// Set the active heading to the first visible one
|
|
45
|
-
const allIds = getAllHeadingIds(tocLinks.value)
|
|
46
|
-
for (const id of allIds) {
|
|
47
|
-
if (visibleHeadings.value.has(id)) {
|
|
48
|
-
activeHeading.value = id
|
|
49
|
-
break
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
rootMargin: '-80px 0px 0px 0px',
|
|
55
|
-
threshold: 0,
|
|
56
|
-
},
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
// Observe all headings (ids are auto-generated by scanHeadings if missing)
|
|
60
|
-
const headings = document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]')
|
|
61
|
-
headings.forEach(heading => {
|
|
62
|
-
headingObserver?.observe(heading)
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function getAllHeadingIds(links: TocLink[]): string[] {
|
|
67
|
-
const ids: string[] = []
|
|
68
|
-
for (const link of links) {
|
|
69
|
-
ids.push(link.id)
|
|
70
|
-
if (link.children.length) {
|
|
71
|
-
ids.push(...getAllHeadingIds(link.children))
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return ids
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function cleanupIntersectionObserver() {
|
|
78
|
-
if (headingObserver) {
|
|
79
|
-
headingObserver.disconnect()
|
|
80
|
-
headingObserver = null
|
|
81
|
-
}
|
|
82
|
-
visibleHeadings.value.clear()
|
|
83
|
-
activeHeading.value = null
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Function to scan the page for headings and build the TOC
|
|
87
|
-
function scanHeadings() {
|
|
88
|
-
// Wait for the DOM to be ready
|
|
89
|
-
if (typeof document === 'undefined') {
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Find all headings (h1-h6), auto-generating ids for any that lack one
|
|
94
|
-
const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6')
|
|
95
|
-
const links: TocLink[] = []
|
|
96
|
-
const usedIds = new Set<string>()
|
|
97
|
-
|
|
98
|
-
// Get the page title from the first h1
|
|
99
|
-
const h1 = document.querySelector('h1')
|
|
100
|
-
if (h1) {
|
|
101
|
-
pageTitle.value = h1.textContent || 'Table of Contents'
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Process each heading
|
|
105
|
-
headings.forEach(heading => {
|
|
106
|
-
// Skip the first h1 as it's the page title
|
|
107
|
-
if (heading.tagName === 'H1' && heading === h1) {
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Auto-generate id from text content if missing
|
|
112
|
-
if (!heading.id) {
|
|
113
|
-
const text = heading.textContent || ''
|
|
114
|
-
const slug = text
|
|
115
|
-
.toLowerCase()
|
|
116
|
-
.trim()
|
|
117
|
-
.replace(/[^\w\s-]/g, '')
|
|
118
|
-
.replace(/[\s_]+/g, '-')
|
|
119
|
-
.replace(/^-+|-+$/g, '')
|
|
120
|
-
// Deduplicate
|
|
121
|
-
let candidate = slug
|
|
122
|
-
let n = 1
|
|
123
|
-
while (usedIds.has(candidate)) {
|
|
124
|
-
candidate = `${slug}-${n++}`
|
|
125
|
-
}
|
|
126
|
-
heading.id = candidate
|
|
127
|
-
}
|
|
128
|
-
usedIds.add(heading.id)
|
|
129
|
-
|
|
130
|
-
const id = heading.id
|
|
131
|
-
const level = Number.parseInt(heading.tagName.substring(1), 10)
|
|
132
|
-
|
|
133
|
-
// Create a TOC link
|
|
134
|
-
const link: TocLink = { id, text: heading.textContent || '', level, children: [] }
|
|
135
|
-
links.push(link)
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
// Build a robust hierarchical structure in a single pass
|
|
139
|
-
const hierarchicalLinks: TocLink[] = []
|
|
140
|
-
const parents: TocLink[] = []
|
|
141
|
-
|
|
142
|
-
for (const link of links) {
|
|
143
|
-
// Always ensure children is initialized
|
|
144
|
-
if (!('children' in link) || !Array.isArray(link.children)) {
|
|
145
|
-
;(link as TocLink).children = []
|
|
146
|
-
}
|
|
147
|
-
// Find the last parent of lower level
|
|
148
|
-
while (parents.length > 0 && parents[parents.length - 1]!.level >= link.level) {
|
|
149
|
-
parents.pop()
|
|
150
|
-
}
|
|
151
|
-
if (parents.length === 0) {
|
|
152
|
-
hierarchicalLinks.push(link)
|
|
153
|
-
} else {
|
|
154
|
-
parents[parents.length - 1]!.children.push(link)
|
|
155
|
-
}
|
|
156
|
-
parents.push(link)
|
|
157
|
-
}
|
|
158
|
-
tocLinks.value = hierarchicalLinks
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Check if we're in the browser
|
|
162
|
-
let observer: MutationObserver | null = null
|
|
163
|
-
|
|
164
|
-
onMounted(() => {
|
|
165
|
-
// Initial scan
|
|
166
|
-
scanHeadings()
|
|
167
|
-
|
|
168
|
-
// Initialize clickedHeading from URL hash if present
|
|
169
|
-
if (window.location.hash) {
|
|
170
|
-
clickedHeading.value = window.location.hash.slice(1)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Setup intersection observer after initial scan
|
|
174
|
-
nextTick(() => {
|
|
175
|
-
setupIntersectionObserver()
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
// Re-scan when route changes (for SPA navigation)
|
|
179
|
-
watch(
|
|
180
|
-
() => pageRoute.path,
|
|
181
|
-
() => {
|
|
182
|
-
nextTick(() => {
|
|
183
|
-
cleanupIntersectionObserver()
|
|
184
|
-
scanHeadings()
|
|
185
|
-
nextTick(() => {
|
|
186
|
-
setupIntersectionObserver()
|
|
187
|
-
})
|
|
188
|
-
})
|
|
189
|
-
},
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
// Watch for DOM mutations affecting headings
|
|
193
|
-
observer = new MutationObserver(mutations => {
|
|
194
|
-
let shouldRescan = false
|
|
195
|
-
for (const mutation of mutations) {
|
|
196
|
-
if (
|
|
197
|
-
Array.from(mutation.addedNodes).some(isHeadingNode) ||
|
|
198
|
-
Array.from(mutation.removedNodes).some(isHeadingNode) ||
|
|
199
|
-
(mutation.type === 'attributes' && isHeadingNode(mutation.target))
|
|
200
|
-
) {
|
|
201
|
-
shouldRescan = true
|
|
202
|
-
break
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
if (shouldRescan) {
|
|
206
|
-
cleanupIntersectionObserver()
|
|
207
|
-
scanHeadings()
|
|
208
|
-
nextTick(() => {
|
|
209
|
-
setupIntersectionObserver()
|
|
210
|
-
})
|
|
211
|
-
}
|
|
212
|
-
})
|
|
213
|
-
observer.observe(document.body, {
|
|
214
|
-
childList: true,
|
|
215
|
-
subtree: true,
|
|
216
|
-
attributes: true,
|
|
217
|
-
attributeFilter: ['id'],
|
|
218
|
-
})
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
onBeforeUnmount(() => {
|
|
222
|
-
if (observer) {
|
|
223
|
-
observer.disconnect()
|
|
224
|
-
observer = null
|
|
225
|
-
}
|
|
226
|
-
cleanupIntersectionObserver()
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
function isHeadingNode(node: Node | EventTarget): boolean {
|
|
230
|
-
if (!(node instanceof HTMLElement)) {
|
|
231
|
-
return false
|
|
232
|
-
}
|
|
233
|
-
return /^H[1-6]$/.test(node.tagName)
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function checkActive(hash: string) {
|
|
237
|
-
return pageRoute.hash === hash ? 'active' : null
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function toTop() {
|
|
241
|
-
window.scrollTo({
|
|
242
|
-
top: 0,
|
|
243
|
-
behavior: 'smooth',
|
|
244
|
-
})
|
|
245
|
-
setTimeout(() => {
|
|
246
|
-
window.location.hash = ''
|
|
247
|
-
}, 600)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function toBottom() {
|
|
251
|
-
window.scrollTo({
|
|
252
|
-
top: document.body.scrollHeight,
|
|
253
|
-
behavior: 'smooth',
|
|
254
|
-
})
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function scrollToHeading(id: string) {
|
|
258
|
-
const el = document.getElementById(id)
|
|
259
|
-
if (el) {
|
|
260
|
-
// Immediately highlight the clicked item
|
|
261
|
-
clickedHeading.value = id
|
|
262
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
263
|
-
// Update the hash after the scroll
|
|
264
|
-
history.replaceState(null, '', `#${id}`)
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
</script>
|
|
268
|
-
|
|
269
|
-
<template>
|
|
270
|
-
<Flex col>
|
|
271
|
-
<Menu v-if="tocLinks.length" class="md:menu-sm xl:menu-md w-full">
|
|
272
|
-
<NuxtLink href="#" custom>
|
|
273
|
-
<template #default="{ route }">
|
|
274
|
-
<MenuItem class="text-base-content cursor-pointer">
|
|
275
|
-
<a
|
|
276
|
-
class="flex flex-row items-center justify-between font-semibold bg-neutral/30"
|
|
277
|
-
:class="checkActive(route?.hash || '')"
|
|
278
|
-
@click="toTop"
|
|
279
|
-
>
|
|
280
|
-
{{ pageTitle }}
|
|
281
|
-
<Icon name="feather:arrow-up" class="text-base" />
|
|
282
|
-
</a>
|
|
283
|
-
</MenuItem>
|
|
284
|
-
</template>
|
|
285
|
-
</NuxtLink>
|
|
286
|
-
|
|
287
|
-
<TocTree
|
|
288
|
-
:links="tocLinks"
|
|
289
|
-
:check-active="checkActive"
|
|
290
|
-
:scroll-to-heading="scrollToHeading"
|
|
291
|
-
:visible-headings="visibleHeadings"
|
|
292
|
-
:active-heading="activeHeading"
|
|
293
|
-
:clicked-heading="clickedHeading"
|
|
294
|
-
/>
|
|
295
|
-
|
|
296
|
-
<MenuItem>
|
|
297
|
-
<a class="flex flex-row items-center justify-between hover:bg-accent/25 mt-6" @click="toBottom">
|
|
298
|
-
scroll to bottom
|
|
299
|
-
<Icon name="feather:arrow-down" class="text-base" />
|
|
300
|
-
</a>
|
|
301
|
-
</MenuItem>
|
|
302
|
-
</Menu>
|
|
303
|
-
</Flex>
|
|
304
|
-
</template>
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { PropType } from 'vue'
|
|
3
|
-
|
|
4
|
-
interface TocLink {
|
|
5
|
-
id: string
|
|
6
|
-
text: string
|
|
7
|
-
level: number
|
|
8
|
-
children: TocLink[]
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
defineProps({
|
|
12
|
-
links: {
|
|
13
|
-
type: Array as PropType<TocLink[]>,
|
|
14
|
-
required: true,
|
|
15
|
-
},
|
|
16
|
-
checkActive: {
|
|
17
|
-
type: Function as PropType<(hash: string) => string | null>,
|
|
18
|
-
required: true,
|
|
19
|
-
},
|
|
20
|
-
scrollToHeading: {
|
|
21
|
-
type: Function as PropType<(id: string) => void>,
|
|
22
|
-
required: true,
|
|
23
|
-
},
|
|
24
|
-
visibleHeadings: {
|
|
25
|
-
type: Set as unknown as PropType<Set<string>>,
|
|
26
|
-
default: () => new Set(),
|
|
27
|
-
},
|
|
28
|
-
activeHeading: {
|
|
29
|
-
type: String as PropType<string | null>,
|
|
30
|
-
default: null,
|
|
31
|
-
},
|
|
32
|
-
clickedHeading: {
|
|
33
|
-
type: String as PropType<string | null>,
|
|
34
|
-
default: null,
|
|
35
|
-
},
|
|
36
|
-
})
|
|
37
|
-
</script>
|
|
38
|
-
|
|
39
|
-
<template>
|
|
40
|
-
<Menu v-if="links && links.length" class="w-full">
|
|
41
|
-
<MenuItem v-for="link in links" :key="link.id">
|
|
42
|
-
<a
|
|
43
|
-
class="block px-2 py-1 rounded cursor-pointer transition-colors"
|
|
44
|
-
:class="[
|
|
45
|
-
clickedHeading === link.id
|
|
46
|
-
? 'text-primary font-semibold bg-primary/10'
|
|
47
|
-
: visibleHeadings.has(link.id)
|
|
48
|
-
? 'text-base-content font-medium bg-base-300'
|
|
49
|
-
: 'text-base-content hover:bg-accent/10',
|
|
50
|
-
]"
|
|
51
|
-
@click.prevent="scrollToHeading(link.id)"
|
|
52
|
-
>
|
|
53
|
-
{{ link.text }}
|
|
54
|
-
</a>
|
|
55
|
-
<TocTree
|
|
56
|
-
v-if="link.children && link.children.length"
|
|
57
|
-
:links="link.children"
|
|
58
|
-
:check-active="checkActive"
|
|
59
|
-
:scroll-to-heading="scrollToHeading"
|
|
60
|
-
:visible-headings="visibleHeadings"
|
|
61
|
-
:active-heading="activeHeading"
|
|
62
|
-
:clicked-heading="clickedHeading"
|
|
63
|
-
/>
|
|
64
|
-
</MenuItem>
|
|
65
|
-
</Menu>
|
|
66
|
-
</template>
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
const props = defineProps<{
|
|
3
|
-
text: string
|
|
4
|
-
}>()
|
|
5
|
-
|
|
6
|
-
const color = computed(() => {
|
|
7
|
-
if (props.text === 'boolean') {
|
|
8
|
-
return 'info'
|
|
9
|
-
}
|
|
10
|
-
return 'warning'
|
|
11
|
-
})
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
<template>
|
|
15
|
-
<Badge :color="color" sm class="whitespace-nowrap">
|
|
16
|
-
{{ text }}
|
|
17
|
-
</Badge>
|
|
18
|
-
</template>
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
// @ts-expect-error headlessui types may not be available
|
|
3
|
-
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
|
|
4
|
-
|
|
5
|
-
const userNavigation = [
|
|
6
|
-
{ name: 'Your Profile', href: '/app/profile' },
|
|
7
|
-
{ name: 'Settings', href: '/app/settings' },
|
|
8
|
-
{ name: 'Home', href: '/' },
|
|
9
|
-
]
|
|
10
|
-
|
|
11
|
-
const user: any = useState('user')
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
<template>
|
|
15
|
-
<Menu as="div" class="relative ml-3">
|
|
16
|
-
<MenuButton
|
|
17
|
-
class="flex items-center max-w-xs text-sm rounded-full bg-base-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
18
|
-
>
|
|
19
|
-
<span class="sr-only">Open user menu</span>
|
|
20
|
-
<img class="w-8 h-8 rounded-full" :src="user?.picture" />
|
|
21
|
-
</MenuButton>
|
|
22
|
-
|
|
23
|
-
<transition
|
|
24
|
-
enter-active-class="transition duration-100 ease-out"
|
|
25
|
-
enter-from-class="transform scale-95 opacity-0"
|
|
26
|
-
enter-to-class="transform scale-100 opacity-100"
|
|
27
|
-
leave-active-class="transition duration-75 ease-in"
|
|
28
|
-
leave-from-class="transform scale-100 opacity-100"
|
|
29
|
-
leave-to-class="transform scale-95 opacity-0"
|
|
30
|
-
>
|
|
31
|
-
<MenuItems
|
|
32
|
-
class="absolute right-0 w-48 py-1 mt-2 origin-top-right rounded-md shadow-lg bg-base-100 ring-1 ring-black ring-opacity-5 focus:outline-none"
|
|
33
|
-
>
|
|
34
|
-
<MenuItem v-for="item in userNavigation" :key="item.name">
|
|
35
|
-
<NuxtLink :to="item.href" exact-active-class="bg-gray-100" class="block px-4 py-2 text-sm text-gray-700">
|
|
36
|
-
{{ item.name }}
|
|
37
|
-
</NuxtLink>
|
|
38
|
-
</MenuItem>
|
|
39
|
-
|
|
40
|
-
<MenuItem>
|
|
41
|
-
<a href="/api/auth/logout" exact-active-class="bg-gray-100" class="block px-4 py-2 text-sm text-gray-700"
|
|
42
|
-
>Sign out</a
|
|
43
|
-
>
|
|
44
|
-
</MenuItem>
|
|
45
|
-
</MenuItems>
|
|
46
|
-
</transition>
|
|
47
|
-
</Menu>
|
|
48
|
-
</template>
|
|
@@ -1,239 +0,0 @@
|
|
|
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
|
-
})
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { ref } from 'vue'
|
|
2
|
-
|
|
3
|
-
const isSearchOpen = ref(false)
|
|
4
|
-
|
|
5
|
-
export function useSearch() {
|
|
6
|
-
function openSearch() {
|
|
7
|
-
isSearchOpen.value = true
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function closeSearch() {
|
|
11
|
-
isSearchOpen.value = false
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function toggleSearch() {
|
|
15
|
-
isSearchOpen.value = !isSearchOpen.value
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
isSearchOpen,
|
|
20
|
-
openSearch,
|
|
21
|
-
closeSearch,
|
|
22
|
-
toggleSearch,
|
|
23
|
-
}
|
|
24
|
-
}
|