daisy-ui-kit 5.0.0-pre.3 → 5.0.0-pre.30
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 +29 -0
- package/app/components/Alert.vue +36 -0
- package/app/components/Avatar.vue +131 -0
- package/app/components/AvatarGroup.vue +22 -0
- package/app/components/Badge.vue +72 -0
- package/app/components/Breadcrumbs.vue +7 -0
- package/app/components/Button.vue +140 -0
- package/app/components/Calendar.vue +175 -0
- package/app/components/CalendarInput.vue +275 -0
- package/app/components/CalendarSkeleton.vue +87 -0
- package/app/components/Card.vue +51 -0
- package/app/components/CardActions.vue +13 -0
- package/app/components/CardBody.vue +13 -0
- package/app/components/CardTitle.vue +11 -0
- package/app/components/Carousel.vue +24 -0
- package/app/components/CarouselItem.vue +5 -0
- package/app/components/Chat.vue +26 -0
- package/app/components/ChatBubble.vue +31 -0
- package/app/components/ChatFooter.vue +5 -0
- package/app/components/ChatHeader.vue +5 -0
- package/app/components/ChatImage.vue +5 -0
- package/app/components/Checkbox.vue +51 -0
- package/app/components/Collapse.vue +75 -0
- package/app/components/CollapseContent.vue +5 -0
- package/app/components/CollapseTitle.vue +15 -0
- package/app/components/Countdown.vue +15 -0
- package/app/components/CountdownTimers.vue +69 -0
- package/app/components/Counter.vue +21 -0
- package/app/components/Crumb.vue +5 -0
- package/app/components/DaisyLink.vue +56 -0
- package/app/components/Diff.vue +11 -0
- package/app/components/Divider.vue +43 -0
- package/app/components/Dock.vue +57 -0
- package/app/components/DockItem.vue +27 -0
- package/app/components/DockLabel.vue +5 -0
- package/app/components/Drawer.vue +50 -0
- package/app/components/DrawerContent.vue +20 -0
- package/app/components/DrawerSide.vue +21 -0
- package/app/components/Dropdown.vue +106 -0
- package/app/components/DropdownButton.vue +23 -0
- package/app/components/DropdownContent.vue +127 -0
- package/app/components/DropdownTarget.vue +21 -0
- 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 +20 -0
- package/app/components/FileInput.vue +53 -0
- package/app/components/Filter.vue +129 -0
- package/app/components/Flex.vue +89 -0
- package/app/components/FlexItem.vue +62 -0
- package/app/components/Footer.vue +31 -0
- package/app/components/FooterTitle.vue +18 -0
- package/app/components/FormControl.vue +5 -0
- package/app/components/Hero.vue +18 -0
- package/app/components/HeroContent.vue +18 -0
- package/app/components/HeroOverlay.vue +5 -0
- package/app/components/Hover3D.vue +22 -0
- package/app/components/HoverGallery.vue +11 -0
- package/app/components/Indicator.vue +20 -0
- package/app/components/IndicatorItem.vue +43 -0
- package/app/components/Input.vue +116 -0
- package/app/components/Join.vue +5 -0
- package/app/components/Kbd.vue +25 -0
- package/app/components/Label.vue +100 -0
- package/app/components/List.vue +5 -0
- package/app/components/ListColGrow.vue +5 -0
- package/app/components/ListColWrap.vue +5 -0
- package/app/components/ListRow.vue +5 -0
- package/app/components/LoadingBall.vue +42 -0
- package/app/components/LoadingBars.vue +42 -0
- package/app/components/LoadingDots.vue +42 -0
- package/app/components/LoadingInfinity.vue +42 -0
- package/app/components/LoadingRing.vue +42 -0
- package/app/components/LoadingSpinner.vue +42 -0
- package/app/components/Mask.vue +49 -0
- package/app/components/Menu.vue +30 -0
- package/app/components/MenuExpand.vue +92 -0
- package/app/components/MenuExpandToggle.vue +20 -0
- package/app/components/MenuItem.vue +39 -0
- package/app/components/MenuTitle.vue +5 -0
- package/app/components/MockupBrowser.vue +5 -0
- package/app/components/MockupBrowserToolbar.vue +5 -0
- package/app/components/MockupCode.vue +4 -0
- package/app/components/MockupPhone.vue +14 -0
- package/app/components/MockupWindow.vue +5 -0
- package/app/components/Modal.vue +63 -0
- package/app/components/ModalAction.vue +5 -0
- package/app/components/ModalBox.vue +5 -0
- package/app/components/NavButton.vue +12 -0
- package/app/components/Navbar.vue +12 -0
- package/app/components/NavbarCenter.vue +11 -0
- package/app/components/NavbarEnd.vue +11 -0
- package/app/components/NavbarStart.vue +11 -0
- package/app/components/Progress.vue +46 -0
- package/app/components/Prose.vue +37 -0
- package/app/components/RadialProgress.vue +36 -0
- package/app/components/Radio.vue +69 -0
- package/app/components/RadioGroup.vue +47 -0
- package/app/components/Range.vue +201 -0
- package/app/components/RangeMeasure.vue +87 -0
- package/app/components/RangeMeasureTick.vue +69 -0
- package/app/components/Rating.vue +197 -0
- package/app/components/Select.vue +101 -0
- package/app/components/Skeleton.vue +5 -0
- package/app/components/SkeletonText.vue +11 -0
- package/app/components/Stack.vue +30 -0
- package/app/components/Stat.vue +19 -0
- package/app/components/StatActions.vue +5 -0
- package/app/components/StatDesc.vue +5 -0
- package/app/components/StatFigure.vue +5 -0
- package/app/components/StatTitle.vue +5 -0
- package/app/components/StatValue.vue +5 -0
- package/app/components/Stats.vue +5 -0
- package/app/components/Status.vue +43 -0
- package/app/components/Step.vue +34 -0
- package/app/components/StepIcon.vue +5 -0
- package/app/components/Steps.vue +23 -0
- package/app/components/Swap.vue +56 -0
- package/app/components/Tab.vue +56 -0
- package/app/components/TabContent.vue +29 -0
- package/app/components/Table.vue +32 -0
- package/app/components/Tabs.vue +53 -0
- package/app/components/Text.vue +166 -0
- package/app/components/TextArea.vue +106 -0
- package/app/components/TextRotate.vue +24 -0
- package/app/components/ThemeController.vue +45 -0
- package/app/components/ThemeProvider.vue +302 -0
- package/app/components/ThemeTile.vue +50 -0
- package/app/components/Timeline.vue +22 -0
- package/app/components/TimelineEnd.vue +14 -0
- package/app/components/TimelineItem.vue +5 -0
- package/app/components/TimelineLine.vue +29 -0
- package/app/components/TimelineMiddle.vue +5 -0
- package/app/components/TimelineStart.vue +14 -0
- package/app/components/Toast.vue +67 -0
- package/app/components/Toggle.vue +60 -0
- package/app/components/Tooltip.vue +137 -0
- package/app/components/TooltipContent.vue +283 -0
- package/app/components/TooltipTarget.vue +20 -0
- package/app/components/ValidatorHint.vue +5 -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 +131 -0
- package/app/composables/use-toast.ts +345 -0
- package/app/composables/useSearch.ts +22 -0
- package/app/utils/drawer-utils.ts +34 -0
- package/app/utils/position-area.ts +40 -0
- package/nuxt.d.ts +13 -0
- package/nuxt.js +31 -0
- package/package.json +50 -22
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { inject } from 'vue'
|
|
3
|
+
|
|
4
|
+
const checkboxId = inject('collapseCheckboxId', null)
|
|
5
|
+
const hasToggle = inject('collapseToggle', false)
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<label v-if="hasToggle && checkboxId" :for="checkboxId" class="collapse-title">
|
|
10
|
+
<slot />
|
|
11
|
+
</label>
|
|
12
|
+
<div v-else class="collapse-title">
|
|
13
|
+
<slot />
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
is?: any
|
|
6
|
+
}>()
|
|
7
|
+
|
|
8
|
+
const tag = computed(() => props.is || 'span')
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<component :is="tag" class="countdown">
|
|
13
|
+
<slot />
|
|
14
|
+
</component>
|
|
15
|
+
</template>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useIntervalFn } from '@vueuse/core'
|
|
3
|
+
import { computed, ref, watch } from 'vue'
|
|
4
|
+
|
|
5
|
+
const { durationInSeconds = 0, untilDate } = defineProps<{
|
|
6
|
+
durationInSeconds?: number
|
|
7
|
+
untilDate?: Date
|
|
8
|
+
}>()
|
|
9
|
+
const emit = defineEmits(['done'])
|
|
10
|
+
|
|
11
|
+
function getTargetDate() {
|
|
12
|
+
return untilDate || new Date(Date.now() + durationInSeconds * 1000)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const targetDate = ref(getTargetDate())
|
|
16
|
+
|
|
17
|
+
watch(
|
|
18
|
+
() => [durationInSeconds, untilDate],
|
|
19
|
+
() => {
|
|
20
|
+
targetDate.value = getTargetDate()
|
|
21
|
+
},
|
|
22
|
+
{ immediate: true },
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const timeLeft = ref(0)
|
|
26
|
+
const calcTimeLeft = () => Math.max(0, targetDate.value.getTime() - Date.now())
|
|
27
|
+
|
|
28
|
+
useIntervalFn(() => {
|
|
29
|
+
timeLeft.value = calcTimeLeft()
|
|
30
|
+
}, 1000)
|
|
31
|
+
|
|
32
|
+
watch(timeLeft, val => {
|
|
33
|
+
if (val === 0) {
|
|
34
|
+
emit('done')
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const totalSeconds = computed(() => Math.round(timeLeft.value / 1000))
|
|
39
|
+
const totalMinutes = computed(() => Math.floor(totalSeconds.value / 60))
|
|
40
|
+
const totalHours = computed(() => Math.floor(totalMinutes.value / 60))
|
|
41
|
+
const totalDays = computed(() => Math.floor(totalHours.value / 24))
|
|
42
|
+
const totalWeeks = computed(() => Math.floor(totalDays.value / 7))
|
|
43
|
+
const totalMonths = computed(() => {
|
|
44
|
+
const now = new Date()
|
|
45
|
+
return (targetDate.value.getFullYear() - now.getFullYear()) * 12 + (targetDate.value.getMonth() - now.getMonth())
|
|
46
|
+
})
|
|
47
|
+
const split = computed(() => {
|
|
48
|
+
const days = totalDays.value
|
|
49
|
+
const hours = totalHours.value - days * 24
|
|
50
|
+
const minutes = totalMinutes.value - totalHours.value * 60
|
|
51
|
+
const seconds = totalSeconds.value - totalMinutes.value * 60
|
|
52
|
+
return { days, hours, minutes, seconds }
|
|
53
|
+
})
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<template>
|
|
57
|
+
<slot
|
|
58
|
+
v-bind="{
|
|
59
|
+
totalSeconds,
|
|
60
|
+
totalMinutes,
|
|
61
|
+
totalHours,
|
|
62
|
+
totalDays,
|
|
63
|
+
totalWeeks,
|
|
64
|
+
totalMonths,
|
|
65
|
+
targetDate,
|
|
66
|
+
split,
|
|
67
|
+
}"
|
|
68
|
+
/>
|
|
69
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
value: number | string
|
|
6
|
+
is?: any
|
|
7
|
+
digits?: 2 | 3 | '2' | '3'
|
|
8
|
+
}>()
|
|
9
|
+
|
|
10
|
+
const style = computed(() => {
|
|
11
|
+
let css = `--value:${props.value};`
|
|
12
|
+
if (props.digits) {
|
|
13
|
+
css += `--digits:${props.digits};`
|
|
14
|
+
}
|
|
15
|
+
return css
|
|
16
|
+
})
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<component :is="props.is || 'span'" v-bind="$attrs" :style="style" />
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, resolveComponent } from 'vue'
|
|
3
|
+
|
|
4
|
+
defineOptions({
|
|
5
|
+
inheritAttrs: false,
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(
|
|
9
|
+
defineProps<{
|
|
10
|
+
is?: string
|
|
11
|
+
hover?: boolean
|
|
12
|
+
|
|
13
|
+
color?: 'neutral' | 'primary' | 'secondary' | 'accent' | 'success' | 'info' | 'warning' | 'error'
|
|
14
|
+
neutral?: boolean
|
|
15
|
+
primary?: boolean
|
|
16
|
+
secondary?: boolean
|
|
17
|
+
accent?: boolean
|
|
18
|
+
success?: boolean
|
|
19
|
+
info?: boolean
|
|
20
|
+
warning?: boolean
|
|
21
|
+
error?: boolean
|
|
22
|
+
}>(),
|
|
23
|
+
{
|
|
24
|
+
is: 'a',
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
const NuxtLink = resolveComponent('NuxtLink')
|
|
28
|
+
const RouterLink = resolveComponent('RouterLink')
|
|
29
|
+
|
|
30
|
+
const resolvedComponent = computed(() => {
|
|
31
|
+
if (props.is === 'NuxtLink') return NuxtLink
|
|
32
|
+
if (props.is === 'RouterLink') return RouterLink
|
|
33
|
+
return props.is
|
|
34
|
+
})
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<component
|
|
39
|
+
:is="resolvedComponent"
|
|
40
|
+
v-bind="$attrs"
|
|
41
|
+
class="link"
|
|
42
|
+
:class="{
|
|
43
|
+
'link-neutral': neutral || color === 'neutral',
|
|
44
|
+
'link-primary': primary || color === 'primary',
|
|
45
|
+
'link-secondary': secondary || color === 'secondary',
|
|
46
|
+
'link-accent': accent || color === 'accent',
|
|
47
|
+
'link-success': success || color === 'success',
|
|
48
|
+
'link-info': info || color === 'info',
|
|
49
|
+
'link-warning': warning || color === 'warning',
|
|
50
|
+
'link-error': error || color === 'error',
|
|
51
|
+
'link-hover': hover,
|
|
52
|
+
}"
|
|
53
|
+
>
|
|
54
|
+
<slot />
|
|
55
|
+
</component>
|
|
56
|
+
</template>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<figure class="diff" tabindex="0">
|
|
3
|
+
<div class="diff-item-1" role="img" tabindex="0">
|
|
4
|
+
<slot name="one" />
|
|
5
|
+
</div>
|
|
6
|
+
<div class="diff-item-2" role="img">
|
|
7
|
+
<slot name="two" />
|
|
8
|
+
</div>
|
|
9
|
+
<div class="diff-resizer" />
|
|
10
|
+
</figure>
|
|
11
|
+
</template>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
orientation?: 'horizontal' | 'vertical'
|
|
4
|
+
horizontal?: boolean
|
|
5
|
+
vertical?: boolean
|
|
6
|
+
align?: 'center' | 'start' | 'end'
|
|
7
|
+
center?: boolean
|
|
8
|
+
start?: boolean
|
|
9
|
+
end?: boolean
|
|
10
|
+
color?: 'neutral' | 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'info' | 'error'
|
|
11
|
+
neutral?: boolean
|
|
12
|
+
primary?: boolean
|
|
13
|
+
secondary?: boolean
|
|
14
|
+
accent?: boolean
|
|
15
|
+
success?: boolean
|
|
16
|
+
warning?: boolean
|
|
17
|
+
info?: boolean
|
|
18
|
+
error?: boolean
|
|
19
|
+
}>()
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<div
|
|
24
|
+
class="divider"
|
|
25
|
+
:class="{
|
|
26
|
+
'divider-vertical': orientation === 'vertical' || vertical,
|
|
27
|
+
'divider-horizontal': orientation !== 'vertical' || horizontal,
|
|
28
|
+
'divider-neutral': color === 'neutral' || neutral,
|
|
29
|
+
'divider-primary': color === 'primary' || primary,
|
|
30
|
+
'divider-secondary': color === 'secondary' || secondary,
|
|
31
|
+
'divider-accent': color === 'accent' || accent,
|
|
32
|
+
'divider-success': color === 'success' || success,
|
|
33
|
+
'divider-warning': color === 'warning' || warning,
|
|
34
|
+
'divider-info': color === 'info' || info,
|
|
35
|
+
'divider-error': color === 'error' || error,
|
|
36
|
+
'divider-start': align === 'start' || start,
|
|
37
|
+
'divider-center': align === 'center' || center,
|
|
38
|
+
'divider-end': align === 'end' || end,
|
|
39
|
+
}"
|
|
40
|
+
>
|
|
41
|
+
<slot />
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { Ref } from 'vue'
|
|
3
|
+
import { provide, ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
const { size, xl, lg, md, sm, xs } = defineProps<{
|
|
6
|
+
size?: string
|
|
7
|
+
xl?: boolean
|
|
8
|
+
lg?: boolean
|
|
9
|
+
md?: boolean
|
|
10
|
+
sm?: boolean
|
|
11
|
+
xs?: boolean
|
|
12
|
+
}>()
|
|
13
|
+
|
|
14
|
+
const activeItemId = ref<string | null>(null)
|
|
15
|
+
const itemIds = ref<string[]>([])
|
|
16
|
+
|
|
17
|
+
function registerItem(itemId: string) {
|
|
18
|
+
itemIds.value.push(itemId)
|
|
19
|
+
return function unregister() {
|
|
20
|
+
itemIds.value = itemIds.value.filter(id => id !== itemId)
|
|
21
|
+
if (activeItemId.value === itemId) {
|
|
22
|
+
activeItemId.value = null
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function setActiveItemId(itemId: string) {
|
|
28
|
+
activeItemId.value = itemId
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DockState {
|
|
32
|
+
activeItemId: Ref<string | null>
|
|
33
|
+
registerItem: (itemId: string) => () => void
|
|
34
|
+
setActiveItemId: (itemId: string) => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
provide<DockState>('dockState', {
|
|
38
|
+
activeItemId,
|
|
39
|
+
registerItem,
|
|
40
|
+
setActiveItemId,
|
|
41
|
+
})
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<div
|
|
46
|
+
class="dock"
|
|
47
|
+
:class="{
|
|
48
|
+
'dock-xl': xl || size === 'xl',
|
|
49
|
+
'dock-lg': lg || size === 'lg',
|
|
50
|
+
'dock-md': md || size === 'md',
|
|
51
|
+
'dock-sm': sm || size === 'sm',
|
|
52
|
+
'dock-xs': xs || size === 'xs',
|
|
53
|
+
}"
|
|
54
|
+
>
|
|
55
|
+
<slot />
|
|
56
|
+
</div>
|
|
57
|
+
</template>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DockState } from './Dock.vue'
|
|
3
|
+
import { inject, onUnmounted, useId } from 'vue'
|
|
4
|
+
|
|
5
|
+
const { active } = defineProps<{
|
|
6
|
+
active?: boolean
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const itemId = useId()
|
|
10
|
+
const { registerItem, setActiveItemId, activeItemId } = inject<DockState>('dockState')!
|
|
11
|
+
const unregister = registerItem(itemId)
|
|
12
|
+
|
|
13
|
+
onUnmounted(() => {
|
|
14
|
+
unregister()
|
|
15
|
+
})
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<div
|
|
20
|
+
:id="`dock-item-${itemId}`"
|
|
21
|
+
class="dock-item"
|
|
22
|
+
:class="{ 'dock-active': active || activeItemId === itemId }"
|
|
23
|
+
@click="setActiveItemId(itemId)"
|
|
24
|
+
>
|
|
25
|
+
<slot />
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, watch } from 'vue'
|
|
3
|
+
import { createDrawerState } from '../utils/drawer-utils'
|
|
4
|
+
|
|
5
|
+
const props = withDefaults(
|
|
6
|
+
defineProps<{
|
|
7
|
+
open?: boolean
|
|
8
|
+
name?: string
|
|
9
|
+
end?: boolean
|
|
10
|
+
}>(),
|
|
11
|
+
{
|
|
12
|
+
name: 'drawer',
|
|
13
|
+
},
|
|
14
|
+
)
|
|
15
|
+
const emit = defineEmits(['update:open'])
|
|
16
|
+
|
|
17
|
+
// sync `open` prop with drawerState.isDrawerOpen
|
|
18
|
+
const drawerState = createDrawerState(props.name)
|
|
19
|
+
watch(
|
|
20
|
+
() => props.open,
|
|
21
|
+
value => {
|
|
22
|
+
if (drawerState.isDrawerOpen !== value) {
|
|
23
|
+
drawerState.isDrawerOpen = value
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{ immediate: true },
|
|
27
|
+
)
|
|
28
|
+
watch(
|
|
29
|
+
() => drawerState.isDrawerOpen,
|
|
30
|
+
value => {
|
|
31
|
+
if (props.open !== value) {
|
|
32
|
+
emit('update:open', value)
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{ immediate: true },
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
const classes = computed(() => {
|
|
39
|
+
return {
|
|
40
|
+
'drawer-end': props.end,
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<div class="drawer" :class="classes">
|
|
47
|
+
<input :id="name" v-model="drawerState.isDrawerOpen" type="checkbox" class="drawer-toggle" />
|
|
48
|
+
<slot v-bind="drawerState" />
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { createDrawerState } from '../utils/drawer-utils'
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(
|
|
5
|
+
defineProps<{
|
|
6
|
+
name?: string
|
|
7
|
+
}>(),
|
|
8
|
+
{
|
|
9
|
+
name: 'drawer',
|
|
10
|
+
},
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
const drawerState = createDrawerState(props.name)
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div class="drawer-content">
|
|
18
|
+
<slot v-bind="drawerState" />
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { createDrawerState } from '../utils/drawer-utils'
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(
|
|
5
|
+
defineProps<{
|
|
6
|
+
name?: string
|
|
7
|
+
}>(),
|
|
8
|
+
{
|
|
9
|
+
name: 'drawer',
|
|
10
|
+
},
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
const drawerState = createDrawerState(props.name)
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div class="drawer-side">
|
|
18
|
+
<div class="drawer-overlay" @click="() => drawerState?.closeDrawer()" />
|
|
19
|
+
<slot v-bind="drawerState" />
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useId } from 'vue'
|
|
3
|
+
import { useElementHover } from '@vueuse/core'
|
|
4
|
+
import { onMounted, provide, ref, watch } from 'vue'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(
|
|
7
|
+
defineProps<{
|
|
8
|
+
autoFocus?: boolean
|
|
9
|
+
|
|
10
|
+
placement?:
|
|
11
|
+
| 'top'
|
|
12
|
+
| 'top-start'
|
|
13
|
+
| 'top-end'
|
|
14
|
+
| 'right'
|
|
15
|
+
| 'right-start'
|
|
16
|
+
| 'right-end'
|
|
17
|
+
| 'bottom'
|
|
18
|
+
| 'bottom-start'
|
|
19
|
+
| 'bottom-end'
|
|
20
|
+
| 'left'
|
|
21
|
+
| 'left-start'
|
|
22
|
+
| 'left-end'
|
|
23
|
+
|
|
24
|
+
hover?: boolean
|
|
25
|
+
delayEnter?: number
|
|
26
|
+
delayLeave?: number
|
|
27
|
+
closeOnClickOutside?: boolean
|
|
28
|
+
}>(),
|
|
29
|
+
{
|
|
30
|
+
autoFocus: false,
|
|
31
|
+
placement: 'bottom-start',
|
|
32
|
+
hover: false,
|
|
33
|
+
delayEnter: 0,
|
|
34
|
+
delayLeave: 300,
|
|
35
|
+
closeOnClickOutside: true,
|
|
36
|
+
},
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
// Dropdown Visibility
|
|
40
|
+
const isOpen = defineModel('open', { default: false })
|
|
41
|
+
provide('isDropdownOpen', isOpen)
|
|
42
|
+
|
|
43
|
+
const autoFocus = ref(props.autoFocus)
|
|
44
|
+
provide('dropdownAutoFocus', autoFocus)
|
|
45
|
+
|
|
46
|
+
// Use Nuxt's useId() for unique IDs
|
|
47
|
+
const uniqueId = useId()
|
|
48
|
+
const wrapperId = `dropdown-wrapper-${uniqueId}`
|
|
49
|
+
const id = `dropdown-${uniqueId}`
|
|
50
|
+
provide('dropdownId', id)
|
|
51
|
+
|
|
52
|
+
// Provide placement for CSS anchor positioning
|
|
53
|
+
provide('dropdownPlacement', ref(props.placement))
|
|
54
|
+
|
|
55
|
+
// Provide closeOnClickOutside for popover mode selection
|
|
56
|
+
provide('dropdownCloseOnClickOutside', ref(props.closeOnClickOutside))
|
|
57
|
+
|
|
58
|
+
// Provide hover mode for button behavior
|
|
59
|
+
provide('dropdownHover', ref(props.hover))
|
|
60
|
+
|
|
61
|
+
// References
|
|
62
|
+
const buttonEl = ref(null)
|
|
63
|
+
const contentEl = ref(null)
|
|
64
|
+
|
|
65
|
+
provide('buttonEl', buttonEl)
|
|
66
|
+
provide('contentEl', contentEl)
|
|
67
|
+
|
|
68
|
+
// Visibility Utils
|
|
69
|
+
function toggle() {
|
|
70
|
+
isOpen.value = !isOpen.value
|
|
71
|
+
}
|
|
72
|
+
function open() {
|
|
73
|
+
isOpen.value = true
|
|
74
|
+
}
|
|
75
|
+
function close() {
|
|
76
|
+
isOpen.value = false
|
|
77
|
+
}
|
|
78
|
+
provide('toggleDropdown', toggle)
|
|
79
|
+
provide('openDropdown', open)
|
|
80
|
+
provide('closeDropdown', close)
|
|
81
|
+
|
|
82
|
+
const dropdownWrapper = ref(null)
|
|
83
|
+
|
|
84
|
+
onMounted(() => {
|
|
85
|
+
// Note: closeOnClickOutside is handled automatically by popover="auto"
|
|
86
|
+
// The popover API provides "light dismiss" behavior by default
|
|
87
|
+
|
|
88
|
+
// Sync with hover state for SSR compatibility
|
|
89
|
+
if (props.hover) {
|
|
90
|
+
const hover = useElementHover(dropdownWrapper, {
|
|
91
|
+
delayLeave: props.delayLeave,
|
|
92
|
+
delayEnter: props.delayEnter,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
watch(hover, newValue => {
|
|
96
|
+
isOpen.value = newValue
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<template>
|
|
103
|
+
<div :id="wrapperId" ref="dropdownWrapper" class="relative inline-block">
|
|
104
|
+
<slot v-bind="{ toggle, open, close }" />
|
|
105
|
+
</div>
|
|
106
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { inject } from 'vue'
|
|
3
|
+
|
|
4
|
+
const id = inject('dropdownId')
|
|
5
|
+
const isOpen = inject('isDropdownOpen')
|
|
6
|
+
const buttonEl = inject('buttonEl')
|
|
7
|
+
const isHover = inject('dropdownHover')
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<Button
|
|
12
|
+
:id="id"
|
|
13
|
+
ref="buttonEl"
|
|
14
|
+
:aria-expanded="isOpen"
|
|
15
|
+
aria-haspopup="menu"
|
|
16
|
+
:popovertarget="`${id}-content`"
|
|
17
|
+
:popovertargetaction="isHover ? 'show' : undefined"
|
|
18
|
+
:style="{ 'anchor-name': `--${id}` }"
|
|
19
|
+
class="dropdown-button"
|
|
20
|
+
>
|
|
21
|
+
<slot />
|
|
22
|
+
</Button>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
|
|
3
|
+
import { computed, inject, nextTick, watch, watchEffect } from 'vue'
|
|
4
|
+
import { getPositionArea, getPositionFallbacks } from '../utils/position-area'
|
|
5
|
+
|
|
6
|
+
const autoFocus = inject('dropdownAutoFocus')
|
|
7
|
+
const id = inject('dropdownId')
|
|
8
|
+
const isOpen = inject('isDropdownOpen')
|
|
9
|
+
const contentEl = inject('contentEl')
|
|
10
|
+
const placement = inject('dropdownPlacement')
|
|
11
|
+
const closeOnClickOutside = inject('dropdownCloseOnClickOutside')
|
|
12
|
+
|
|
13
|
+
// Dropdown Utils
|
|
14
|
+
const toggle = inject('toggleDropdown')
|
|
15
|
+
const open = inject('openDropdown')
|
|
16
|
+
const close = inject('closeDropdown')
|
|
17
|
+
|
|
18
|
+
// Compute CSS position-area value based on placement
|
|
19
|
+
const positionArea = computed(() => getPositionArea(placement.value))
|
|
20
|
+
const positionFallbacks = computed(() => getPositionFallbacks(placement.value))
|
|
21
|
+
|
|
22
|
+
// Determine popover mode: "auto" for light dismiss, "manual" to disable it
|
|
23
|
+
const popoverMode = computed(() => (closeOnClickOutside.value ? 'auto' : 'manual'))
|
|
24
|
+
|
|
25
|
+
let activate
|
|
26
|
+
let deactivate
|
|
27
|
+
|
|
28
|
+
if (autoFocus.value) {
|
|
29
|
+
const { activate: _activate, deactivate: _deactivate, hasFocus } = useFocusTrap(contentEl, { immediate: true })
|
|
30
|
+
activate = _activate
|
|
31
|
+
deactivate = _deactivate
|
|
32
|
+
|
|
33
|
+
// hide the dropdown when the focus-trap drops focus (by pressing escape, for example)
|
|
34
|
+
watchEffect(() => {
|
|
35
|
+
if (!hasFocus.value) {
|
|
36
|
+
close()
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Sync popover state with isOpen model (for programmatic control)
|
|
42
|
+
watch(
|
|
43
|
+
isOpen,
|
|
44
|
+
async newValue => {
|
|
45
|
+
if (contentEl.value) {
|
|
46
|
+
try {
|
|
47
|
+
// Check current popover state
|
|
48
|
+
const isPopoverOpen = contentEl.value.matches(':popover-open')
|
|
49
|
+
|
|
50
|
+
// Only programmatically control if state differs
|
|
51
|
+
if (newValue && !isPopoverOpen) {
|
|
52
|
+
contentEl.value.showPopover()
|
|
53
|
+
if (autoFocus.value) {
|
|
54
|
+
await nextTick()
|
|
55
|
+
activate?.()
|
|
56
|
+
}
|
|
57
|
+
} else if (!newValue && isPopoverOpen) {
|
|
58
|
+
contentEl.value.hidePopover()
|
|
59
|
+
if (autoFocus.value) {
|
|
60
|
+
deactivate?.()
|
|
61
|
+
await nextTick()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
// Silently handle if popover API is not supported
|
|
66
|
+
console.warn('Popover API not supported:', e)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{ flush: 'post' },
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
// Listen to popover toggle events to sync back to isOpen model
|
|
74
|
+
function handleToggle(event) {
|
|
75
|
+
const newState = event.newState === 'open'
|
|
76
|
+
|
|
77
|
+
if (isOpen.value !== newState) {
|
|
78
|
+
isOpen.value = newState
|
|
79
|
+
|
|
80
|
+
if (newState && autoFocus.value) {
|
|
81
|
+
nextTick().then(() => activate?.())
|
|
82
|
+
} else if (!newState && autoFocus.value) {
|
|
83
|
+
deactivate?.()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<template>
|
|
90
|
+
<div
|
|
91
|
+
:id="`${id}-content`"
|
|
92
|
+
ref="contentEl"
|
|
93
|
+
:anchor="id"
|
|
94
|
+
:aria-labelledby="id"
|
|
95
|
+
role="menu"
|
|
96
|
+
:popover="popoverMode"
|
|
97
|
+
class="dropdown-content dropdown-popover"
|
|
98
|
+
:style="{
|
|
99
|
+
'position-anchor': `--${id}`,
|
|
100
|
+
'position-area': positionArea,
|
|
101
|
+
'position-try-fallbacks': positionFallbacks,
|
|
102
|
+
}"
|
|
103
|
+
@toggle="handleToggle"
|
|
104
|
+
>
|
|
105
|
+
<slot v-bind="{ toggle, open, close }" />
|
|
106
|
+
</div>
|
|
107
|
+
</template>
|
|
108
|
+
|
|
109
|
+
<style>
|
|
110
|
+
@layer components {
|
|
111
|
+
/* Reset default popover styles - in components layer so utilities can override */
|
|
112
|
+
.dropdown-popover[popover] {
|
|
113
|
+
border: none;
|
|
114
|
+
color: inherit;
|
|
115
|
+
overflow: visible;
|
|
116
|
+
/* Use auto for inset to allow anchor positioning to work */
|
|
117
|
+
inset: auto;
|
|
118
|
+
margin: 0;
|
|
119
|
+
background-color: transparent;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Position anchoring support */
|
|
123
|
+
.dropdown-popover[popover]:popover-open {
|
|
124
|
+
position: fixed;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
</style>
|