daisy-ui-kit 5.0.0-pre.5 → 5.0.0-pre.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.
Files changed (133) hide show
  1. package/app/components/Accordion.vue +26 -0
  2. package/app/components/Alert.vue +35 -0
  3. package/app/components/Avatar.vue +128 -0
  4. package/app/components/AvatarGroup.vue +18 -0
  5. package/app/components/Badge.vue +54 -0
  6. package/app/components/Breadcrumbs.vue +7 -0
  7. package/app/components/Button.vue +113 -0
  8. package/app/components/Calendar.vue +66 -0
  9. package/app/components/CalendarInput.vue +176 -0
  10. package/app/components/CalendarSkeleton.vue +46 -0
  11. package/app/components/Card.vue +33 -0
  12. package/app/components/CardActions.vue +13 -0
  13. package/app/components/CardBody.vue +13 -0
  14. package/app/components/CardTitle.vue +11 -0
  15. package/app/components/Carousel.vue +23 -0
  16. package/app/components/CarouselItem.vue +5 -0
  17. package/app/components/Chat.vue +21 -0
  18. package/app/components/ChatBubble.vue +31 -0
  19. package/app/components/ChatFooter.vue +5 -0
  20. package/app/components/ChatHeader.vue +5 -0
  21. package/app/components/ChatImage.vue +5 -0
  22. package/app/components/Checkbox.vue +49 -0
  23. package/app/components/Collapse.vue +43 -0
  24. package/app/components/CollapseContent.vue +5 -0
  25. package/app/components/CollapseTitle.vue +5 -0
  26. package/app/components/Countdown.vue +15 -0
  27. package/app/components/CountdownTimers.vue +72 -0
  28. package/app/components/Counter.vue +10 -0
  29. package/app/components/Crumb.vue +5 -0
  30. package/app/components/DaisyLink.vue +38 -0
  31. package/app/components/Diff.vue +11 -0
  32. package/app/components/Divider.vue +43 -0
  33. package/app/components/Dock.vue +58 -0
  34. package/app/components/DockItem.vue +25 -0
  35. package/app/components/DockLabel.vue +5 -0
  36. package/app/components/Drawer.vue +47 -0
  37. package/app/components/DrawerContent.vue +17 -0
  38. package/app/components/DrawerSide.vue +18 -0
  39. package/app/components/Dropdown.vue +95 -0
  40. package/app/components/DropdownButton.vue +16 -0
  41. package/app/components/DropdownContent.vue +57 -0
  42. package/app/components/DropdownTarget.vue +14 -0
  43. package/app/components/Fieldset.vue +19 -0
  44. package/app/components/FileInput.vue +53 -0
  45. package/app/components/Filter.vue +122 -0
  46. package/app/components/Flex.vue +82 -0
  47. package/app/components/FlexItem.vue +59 -0
  48. package/app/components/Footer.vue +27 -0
  49. package/app/components/FooterTitle.vue +15 -0
  50. package/app/components/FormControl.vue +5 -0
  51. package/app/components/Hero.vue +15 -0
  52. package/app/components/HeroContent.vue +15 -0
  53. package/app/components/HeroOverlay.vue +5 -0
  54. package/app/components/Indicator.vue +13 -0
  55. package/app/components/IndicatorItem.vue +36 -0
  56. package/app/components/Input.vue +102 -0
  57. package/app/components/Join.vue +5 -0
  58. package/app/components/Kbd.vue +24 -0
  59. package/app/components/Label.vue +61 -0
  60. package/app/components/List.vue +5 -0
  61. package/app/components/ListColGrow.vue +5 -0
  62. package/app/components/ListColWrap.vue +5 -0
  63. package/app/components/ListRow.vue +5 -0
  64. package/app/components/LoadingBall.vue +42 -0
  65. package/app/components/LoadingBars.vue +42 -0
  66. package/app/components/LoadingDots.vue +42 -0
  67. package/app/components/LoadingInfinity.vue +42 -0
  68. package/app/components/LoadingRing.vue +42 -0
  69. package/app/components/LoadingSpinner.vue +42 -0
  70. package/app/components/Mask.vue +49 -0
  71. package/app/components/Menu.vue +30 -0
  72. package/app/components/MenuExpand.vue +100 -0
  73. package/app/components/MenuExpandToggle.vue +14 -0
  74. package/app/components/MenuItem.vue +37 -0
  75. package/app/components/MenuTitle.vue +5 -0
  76. package/app/components/MockupBrowser.vue +5 -0
  77. package/app/components/MockupBrowserToolbar.vue +5 -0
  78. package/app/components/MockupCode.vue +4 -0
  79. package/app/components/MockupPhone.vue +8 -0
  80. package/app/components/MockupWindow.vue +5 -0
  81. package/app/components/Modal.vue +57 -0
  82. package/app/components/ModalAction.vue +5 -0
  83. package/app/components/ModalBox.vue +5 -0
  84. package/app/components/NavButton.vue +12 -0
  85. package/app/components/Navbar.vue +12 -0
  86. package/app/components/NavbarCenter.vue +11 -0
  87. package/app/components/NavbarEnd.vue +11 -0
  88. package/app/components/NavbarStart.vue +11 -0
  89. package/app/components/Progress.vue +34 -0
  90. package/app/components/Prose.vue +32 -0
  91. package/app/components/RadialProgress.vue +36 -0
  92. package/app/components/Radio.vue +67 -0
  93. package/app/components/RadioGroup.vue +47 -0
  94. package/app/components/Range.vue +61 -0
  95. package/app/components/RangeMeasure.vue +84 -0
  96. package/app/components/RangeMeasureTick.vue +70 -0
  97. package/app/components/Rating.vue +180 -0
  98. package/app/components/Select.vue +104 -0
  99. package/app/components/Skeleton.vue +5 -0
  100. package/app/components/Stack.vue +25 -0
  101. package/app/components/Stat.vue +19 -0
  102. package/app/components/StatActions.vue +5 -0
  103. package/app/components/StatDesc.vue +5 -0
  104. package/app/components/StatFigure.vue +5 -0
  105. package/app/components/StatTitle.vue +5 -0
  106. package/app/components/StatValue.vue +5 -0
  107. package/app/components/Stats.vue +5 -0
  108. package/app/components/Status.vue +43 -0
  109. package/app/components/Step.vue +34 -0
  110. package/app/components/StepIcon.vue +5 -0
  111. package/app/components/Steps.vue +18 -0
  112. package/app/components/Swap.vue +62 -0
  113. package/app/components/Tab.vue +38 -0
  114. package/app/components/TabContent.vue +29 -0
  115. package/app/components/Table.vue +32 -0
  116. package/app/components/Tabs.vue +53 -0
  117. package/app/components/Text.vue +142 -0
  118. package/app/components/TextArea.vue +61 -0
  119. package/app/components/Timeline.vue +22 -0
  120. package/app/components/TimelineEnd.vue +14 -0
  121. package/app/components/TimelineItem.vue +5 -0
  122. package/app/components/TimelineLine.vue +29 -0
  123. package/app/components/TimelineMiddle.vue +5 -0
  124. package/app/components/TimelineStart.vue +13 -0
  125. package/app/components/Toast.vue +29 -0
  126. package/app/components/Toggle.vue +58 -0
  127. package/app/components/Tooltip.vue +47 -0
  128. package/app/components/TooltipContent.vue +5 -0
  129. package/app/components/ValidatorHint.vue +5 -0
  130. package/app/utils/drawer-utils.ts +32 -0
  131. package/app/utils/random-string.ts +19 -0
  132. package/nuxt.js +1 -1
  133. package/package.json +4 -4
@@ -0,0 +1,26 @@
1
+ <script setup lang="ts">
2
+ import { provide, ref, watch } from 'vue';
3
+
4
+ const props = defineProps<{
5
+ modelValue?: string | number
6
+ }>()
7
+ const emit = defineEmits(['update:modelValue'])
8
+
9
+ const value = ref(props.modelValue)
10
+ watch(() => props.modelValue, (val) => {
11
+ value.value = val
12
+ })
13
+ watch(value, (val) => {
14
+ if (props.modelValue !== val) {
15
+ emit('update:modelValue', val)
16
+ }
17
+ })
18
+
19
+ provide('accordion-value', value)
20
+ </script>
21
+
22
+ <template>
23
+ <div class="daisy-ui-kit-accordion">
24
+ <slot />
25
+ </div>
26
+ </template>
@@ -0,0 +1,35 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ outline?: boolean
4
+ dash?: boolean
5
+ soft?: boolean
6
+ type?: 'info' | 'success' | 'warning' | 'error'
7
+ info?: boolean
8
+ success?: boolean
9
+ warning?: boolean
10
+ error?: boolean
11
+ orientation?: 'vertical' | 'horizontal'
12
+ vertical?: boolean
13
+ horizontal?: boolean
14
+ }>()
15
+ </script>
16
+
17
+ <template>
18
+ <div
19
+ class="alert" :class="{
20
+ 'alert-outline': props.outline,
21
+ 'alert-dash': props.dash,
22
+ 'alert-soft': props.soft,
23
+
24
+ 'alert-info': props.info || props.type === 'info',
25
+ 'alert-success': props.success || props.type === 'success',
26
+ 'alert-warning': props.warning || props.type === 'warning',
27
+ 'alert-error': props.error || props.type === 'error',
28
+
29
+ 'alert-vertical': props.vertical || props.orientation === 'vertical',
30
+ 'alert-horizontal': props.horizontal || props.orientation === 'horizontal',
31
+ }"
32
+ >
33
+ <slot />
34
+ </div>
35
+ </template>
@@ -0,0 +1,128 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import Mask from './Mask.vue'
4
+
5
+ const props = defineProps({
6
+ backgroundColor: { type: String, default: '#BBB' },
7
+ maskClasses: String,
8
+ showStatus: Boolean,
9
+ // Presence props
10
+ presence: String,
11
+ online: Boolean,
12
+ offline: Boolean,
13
+ // Mask shape props (self-contained, no Mask.config.ts dependency)
14
+ shape: String,
15
+ squircle: Boolean,
16
+ heart: Boolean,
17
+ hexagon: Boolean,
18
+ hexagon2: Boolean,
19
+ decagon: Boolean,
20
+ pentagon: Boolean,
21
+ diamond: Boolean,
22
+ square: Boolean,
23
+ circle: Boolean,
24
+ star: Boolean,
25
+ star2: Boolean,
26
+ triangle: Boolean,
27
+ triangle2: Boolean,
28
+ triangle3: Boolean,
29
+ triangle4: Boolean,
30
+ half1: Boolean,
31
+ half2: Boolean,
32
+ })
33
+
34
+ const maskShapeKeys = [
35
+ 'mask-squircle',
36
+ 'mask-heart',
37
+ 'mask-hexagon',
38
+ 'mask-hexagon-2',
39
+ 'mask-decagon',
40
+ 'mask-pentagon',
41
+ 'mask-diamond',
42
+ 'mask-square',
43
+ 'mask-circle',
44
+ 'mask-star',
45
+ 'mask-star-2',
46
+ 'mask-triangle',
47
+ 'mask-triangle-2',
48
+ 'mask-triangle-3',
49
+ 'mask-triangle-4',
50
+ 'mask-half-1',
51
+ 'mask-half-2',
52
+ ] as const
53
+
54
+ type AvatarClassKey = typeof maskShapeKeys[number] | 'rounded-box' | 'avatar-online' | 'avatar-offline'
55
+
56
+ const avatarClasses = computed<Record<AvatarClassKey, boolean>>(() => {
57
+ const mask: Record<AvatarClassKey, boolean> = {
58
+ 'mask-squircle': props.squircle || props.shape === 'squircle',
59
+ 'mask-heart': props.heart || props.shape === 'heart',
60
+ 'mask-hexagon': props.hexagon || props.shape === 'hexagon',
61
+ 'mask-hexagon-2': props.hexagon2 || props.shape === 'hexagon-2',
62
+ 'mask-decagon': props.decagon || props.shape === 'decagon',
63
+ 'mask-pentagon': props.pentagon || props.shape === 'pentagon',
64
+ 'mask-diamond': props.diamond || props.shape === 'diamond',
65
+ 'mask-square': props.square || props.shape === 'square',
66
+ 'mask-circle': props.circle || props.shape === 'circle',
67
+ 'mask-star': props.star || props.shape === 'star',
68
+ 'mask-star-2': props.star2 || props.shape === 'star-2',
69
+ 'mask-triangle': props.triangle || props.shape === 'triangle',
70
+ 'mask-triangle-2': props.triangle2 || props.shape === 'triangle-2',
71
+ 'mask-triangle-3': props.triangle3 || props.shape === 'triangle-3',
72
+ 'mask-triangle-4': props.triangle4 || props.shape === 'triangle-4',
73
+ 'mask-half-1': props.half1 || props.shape === 'half-1',
74
+ 'mask-half-2': props.half2 || props.shape === 'half-2',
75
+ 'rounded-box': false,
76
+ 'avatar-online': props.presence === 'online' || props.online,
77
+ 'avatar-offline': props.presence === 'offline' || props.offline,
78
+ }
79
+ const hasMask = maskShapeKeys.some(k => mask[k])
80
+ mask['rounded-box'] = !hasMask
81
+ return mask
82
+ })
83
+
84
+ const color = computed(() => {
85
+ return `#${contrastingColor(props.backgroundColor.replace('#', ''))}`
86
+ })
87
+
88
+ function contrastingColor(color: any) {
89
+ return (luma(color) >= 155) ? '000' : 'fff'
90
+ }
91
+ // color can be a hx string or an array of RGB values 0-255
92
+ function luma(color: any) {
93
+ const rgb = (typeof color === 'string') ? hexToRGBArray(color) : color
94
+ return (0.2126 * rgb[0]) + (0.7152 * rgb[1]) + (0.0722 * rgb[2]) // SMPTE C, Rec. 709 weightings
95
+ }
96
+ function hexToRGBArray(color: any) {
97
+ if (color.length === 3) {
98
+ color = color.charAt(0) + color.charAt(0) + color.charAt(1) + color.charAt(1) + color.charAt(2) + color.charAt(2)
99
+ }
100
+ else if (color.length !== 6) {
101
+ throw new Error(`Invalid hex color: ${color}`)
102
+ }
103
+ const rgb = []
104
+ for (let i = 0; i <= 2; i++) {
105
+ rgb[i] = Number.parseInt(color.substr(i * 2, 2), 16)
106
+ }
107
+ return rgb
108
+ }
109
+ </script>
110
+
111
+ <template>
112
+ <div class="avatar">
113
+ <Mask :style="{ backgroundColor, color }" class="w-full h-full avatar-mask aspect-square" :class="[avatarClasses, maskClasses]">
114
+ <slot />
115
+ </Mask>
116
+ </div>
117
+ </template>
118
+
119
+ <style lang="postcss">
120
+ .avatar-mask > * {
121
+ aspect-ratio: 1/1;
122
+ width: 100%;
123
+ height: 100%;
124
+ display: flex;
125
+ align-items: center;
126
+ justify-content: center;
127
+ }
128
+ </style>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ orientation: string
4
+ horizontal: boolean
5
+ vertical: boolean
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <div
11
+ class="avatar-group" :class="{
12
+ 'flex-row': props.orientation === 'horizontal' || props.horizontal || (!props.orientation && !props.vertical && !props.horizontal),
13
+ 'flex-col': props.orientation === 'vertical' || props.vertical,
14
+ }"
15
+ >
16
+ <slot />
17
+ </div>
18
+ </template>
@@ -0,0 +1,54 @@
1
+ <script setup lang="ts">
2
+ const { is = 'div', ...props } = defineProps<{
3
+ is?: string
4
+ outline?: boolean
5
+ soft?: boolean
6
+ dash?: boolean
7
+ ghost?: boolean
8
+
9
+ size?: string
10
+ xl?: boolean
11
+ lg?: boolean
12
+ md?: boolean
13
+ sm?: boolean
14
+ xs?: boolean
15
+
16
+ color?: string
17
+ neutral?: boolean
18
+ primary?: boolean
19
+ secondary?: boolean
20
+ accent?: boolean
21
+ info?: boolean
22
+ success?: boolean
23
+ warning?: boolean
24
+ error?: boolean
25
+ }>()
26
+ </script>
27
+
28
+ <template>
29
+ <component
30
+ :is="is" class="badge" :class="{
31
+ 'badge-outline': props.outline,
32
+ 'badge-ghost': props.ghost,
33
+ 'badge-soft': props.soft,
34
+ 'badge-dash': props.dash,
35
+
36
+ 'badge-xl': props.xl || props.size === 'xl',
37
+ 'badge-lg': props.lg || props.size === 'lg',
38
+ 'badge-md': props.md || props.size === 'md',
39
+ 'badge-sm': props.sm || props.size === 'sm',
40
+ 'badge-xs': props.xs || props.size === 'xs',
41
+
42
+ 'badge-neutral': props.neutral || props.color === 'neutral',
43
+ 'badge-primary': props.primary || props.color === 'primary',
44
+ 'badge-secondary': props.secondary || props.color === 'secondary',
45
+ 'badge-accent': props.accent || props.color === 'accent',
46
+ 'badge-info': props.info || props.color === 'info',
47
+ 'badge-success': props.success || props.color === 'success',
48
+ 'badge-warning': props.warning || props.color === 'warning',
49
+ 'badge-error': props.error || props.color === 'error',
50
+ }"
51
+ >
52
+ <slot />
53
+ </component>
54
+ </template>
@@ -0,0 +1,7 @@
1
+ <template>
2
+ <div class="breadcrumbs">
3
+ <ul>
4
+ <slot />
5
+ </ul>
6
+ </div>
7
+ </template>
@@ -0,0 +1,113 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+
4
+ const props = defineProps<{
5
+ is?: string
6
+ join?: boolean
7
+
8
+ color?: string
9
+ neutral?: boolean
10
+ primary?: boolean
11
+ secondary?: boolean
12
+ accent?: boolean
13
+ info?: boolean
14
+ success?: boolean
15
+ warning?: boolean
16
+ error?: boolean
17
+
18
+ ghost?: boolean
19
+ link?: boolean
20
+ glass?: boolean
21
+ outline?: boolean
22
+ dash?: boolean
23
+ soft?: boolean
24
+ disabled?: boolean
25
+
26
+ shape?: 'circle' | 'square' | 'wide' | 'block'
27
+ circle?: boolean
28
+ square?: boolean
29
+ wide?: boolean
30
+ block?: boolean
31
+
32
+ noAnimation?: boolean
33
+ active?: boolean
34
+
35
+ size?: 'lg' | 'md' | 'sm' | 'xs' | 'xl'
36
+ xl?: boolean
37
+ lg?: boolean
38
+ md?: boolean
39
+ sm?: boolean
40
+ xs?: boolean
41
+
42
+ type?: 'button' | 'submit' | 'reset'
43
+ }>()
44
+
45
+ // Accessibility-friendly attributes for disabling the button
46
+ const disabledAttrs = computed(() => {
47
+ if (props.disabled) {
48
+ return {
49
+ 'tabindex': -1,
50
+ 'role': 'button',
51
+ 'aria-disabled': true,
52
+ }
53
+ }
54
+ else {
55
+ return {}
56
+ }
57
+ })
58
+ </script>
59
+
60
+ <template>
61
+ <component
62
+ :is="is || 'button'"
63
+ :type="type"
64
+ class="btn"
65
+ :class="{
66
+ 'join-item': join,
67
+
68
+ 'btn-neutral': !disabled && (neutral || color === 'neutral'),
69
+ 'btn-primary': !disabled && (primary || color === 'primary'),
70
+ 'btn-secondary': !disabled && (secondary || color === 'secondary'),
71
+ 'btn-accent': !disabled && (accent || color === 'accent'),
72
+ 'btn-info': !disabled && (info || color === 'info'),
73
+ 'btn-success': !disabled && (success || color === 'success'),
74
+ 'btn-warning': !disabled && (warning || color === 'warning'),
75
+ 'btn-error': !disabled && (error || color === 'error'),
76
+
77
+ 'text-primary': !disabled && (primary || color === 'primary') && link,
78
+ 'text-secondary': !disabled && (secondary || color === 'secondary') && link,
79
+ 'text-neutral': !disabled && (neutral || color === 'neutral') && link,
80
+ 'text-accent': !disabled && (accent || color === 'accent') && link,
81
+ 'text-info': !disabled && (info || color === 'info') && link,
82
+ 'text-success': !disabled && (success || color === 'success') && link,
83
+ 'text-warning': !disabled && (warning || color === 'warning') && link,
84
+ 'text-error': !disabled && (error || color === 'error') && link,
85
+
86
+ 'glass': !disabled && glass,
87
+
88
+ 'btn-circle': circle || shape === 'circle',
89
+ 'btn-square': square || shape === 'square',
90
+ 'btn-wide': wide || shape === 'wide',
91
+ 'btn-block': block || shape === 'block',
92
+
93
+ 'btn-xl': xl || size === 'xl',
94
+ 'btn-lg': lg || size === 'lg',
95
+ 'btn-md': md || size === 'md',
96
+ 'btn-sm': sm || size === 'sm',
97
+ 'btn-xs': xs || size === 'xs',
98
+
99
+ 'btn-outline': !disabled && outline,
100
+ 'btn-dash': !disabled && dash,
101
+ 'btn-ghost': !disabled && ghost,
102
+ 'btn-soft': !disabled && soft,
103
+ 'btn-link': !disabled && link,
104
+ 'btn-disabled': disabled,
105
+
106
+ 'no-animation': noAnimation,
107
+ 'btn-active': !disabled && active,
108
+ }"
109
+ v-bind="{ ...disabledAttrs, ...$attrs }"
110
+ >
111
+ <slot />
112
+ </component>
113
+ </template>
@@ -0,0 +1,66 @@
1
+ <script setup lang="ts">
2
+ import type { PikadayOptions } from 'pikaday'
3
+ import { onMounted, ref } from 'vue'
4
+ import { usePikaday } from '~/composables/usePikaday'
5
+ import CalendarSkeleton from './CalendarSkeleton.vue'
6
+
7
+ const props = defineProps<{
8
+ /** Bound value: Date object or ISO string or null */
9
+ modelValue: Date | string | null
10
+ /** All Pikaday options (except `container` and `bound`) */
11
+ options?: Omit<PikadayOptions, 'container' | 'bound'>
12
+ /** If true, default to today when no value provided */
13
+ autoDefault?: boolean
14
+ }>()
15
+ const emit = defineEmits<{
16
+ (e: 'update:modelValue', v: Date | null): void
17
+ }>()
18
+
19
+ const containerRef = ref<HTMLElement | null>(null)
20
+ const loading = ref(true)
21
+
22
+ const defaultOptions: Partial<PikadayOptions> = {
23
+ format: 'D MMM YYYY',
24
+ }
25
+
26
+ function handleSelect(date: Date) {
27
+ emit('update:modelValue', date)
28
+ }
29
+
30
+ const { createPicker } = usePikaday(
31
+ {
32
+ ...defaultOptions,
33
+ ...props.options,
34
+ bound: false,
35
+ },
36
+ handleSelect,
37
+ )
38
+
39
+ onMounted(async () => {
40
+ if (containerRef.value) {
41
+ const picker = await createPicker(containerRef.value)
42
+ if (picker && picker.el && !containerRef.value.contains(picker.el)) {
43
+ containerRef.value.appendChild(picker.el)
44
+ }
45
+ loading.value = false
46
+ }
47
+ })
48
+ </script>
49
+
50
+ <template>
51
+ <div class="relative w-[270px] h-[270px]">
52
+ <div class="absolute inset-0">
53
+ <CalendarSkeleton :number-of-months="options?.numberOfMonths ?? 1" :class="loading ? '' : 'opacity-0 pointer-events-none transition-opacity duration-300'" />
54
+ </div>
55
+ <div ref="containerRef" class="absolute inset-0 inline-block w-full h-full" />
56
+ <span class="pika-single hidden" />
57
+ </div>
58
+ </template>
59
+
60
+ <style>
61
+ .has-event {
62
+ .pika-button {
63
+ color: var(--color-primary);
64
+ }
65
+ }
66
+ </style>
@@ -0,0 +1,176 @@
1
+ <script setup lang="ts">
2
+ import type { PikadayOptions } from 'pikaday'
3
+ import type { ComponentPublicInstance } from 'vue'
4
+ import { onMounted, ref, watch } from 'vue'
5
+
6
+ import { usePikaday } from '~/composables/usePikaday'
7
+
8
+ const props = defineProps<{
9
+ /** Bound value: Date object or ISO string or null */
10
+ modelValue: Date | string | number | null
11
+ /** All Pikaday options (except `field`) */
12
+ options?: Omit<PikadayOptions, 'field'>
13
+ /** If true, default to today when no value provided */
14
+ autoDefault?: boolean
15
+ placeholder?: string
16
+ disabled?: boolean
17
+ validator?: boolean
18
+ join?: boolean
19
+ color?: string
20
+ primary?: boolean
21
+ secondary?: boolean
22
+ accent?: boolean
23
+ info?: boolean
24
+ success?: boolean
25
+ warning?: boolean
26
+ error?: boolean
27
+ ghost?: boolean
28
+ size?: 'xl' | 'lg' | 'md' | 'sm' | 'xs'
29
+ xl?: boolean
30
+ lg?: boolean
31
+ md?: boolean
32
+ sm?: boolean
33
+ xs?: boolean
34
+ }>()
35
+ const emit = defineEmits<{
36
+ (e: 'update:modelValue', v: Date | null): void
37
+ (e: 'update:inputValue', v: string | null): void
38
+ }>()
39
+ const inputRef = ref<ComponentPublicInstance | null>(null)
40
+ const inputValue = ref<string | null>(null)
41
+ const visible = ref(false)
42
+
43
+ const defaultOptions: Partial<PikadayOptions> = {
44
+ format: 'D MMM YYYY',
45
+ }
46
+
47
+ const { picker, createPicker } = usePikaday(
48
+ {
49
+ ...defaultOptions,
50
+ ...props.options,
51
+ bound: true,
52
+ field: undefined, // will be set below
53
+ },
54
+ handleSelect,
55
+ handleOpen,
56
+ handleClose,
57
+ )
58
+
59
+ function handleSelect(date: Date) {
60
+ emit('update:modelValue', date)
61
+ inputValue.value = picker.value?.toString() ?? ''
62
+ emit('update:inputValue', inputValue.value)
63
+ }
64
+ function handleOpen() {
65
+ visible.value = true
66
+ }
67
+ function handleClose() {
68
+ visible.value = false
69
+ }
70
+
71
+ onMounted(async () => {
72
+ const inputEl = inputRef.value as HTMLInputElement | null
73
+ // Format and set the initial input value BEFORE initializing Pikaday
74
+ if (props.modelValue) {
75
+ let d: Date | null = null
76
+ if (props.modelValue instanceof Date) {
77
+ d = props.modelValue
78
+ }
79
+ else if (typeof props.modelValue === 'string') {
80
+ const tmp = new Date(props.modelValue)
81
+ if (!Number.isNaN(tmp.getTime())) {
82
+ d = tmp
83
+ }
84
+ }
85
+ if (d) {
86
+ const day = d.getDate()
87
+ const month = d.toLocaleString('en-US', { month: 'short' })
88
+ const year = d.getFullYear()
89
+ inputValue.value = `${day} ${month} ${year}`
90
+ }
91
+ }
92
+ if (inputEl) {
93
+ picker.value = await createPicker(inputEl)
94
+ // Do not call setDate here
95
+ if (picker.value && (picker.value as any).config?.bound === false) {
96
+ picker.value.hide()
97
+ }
98
+ }
99
+ })
100
+
101
+ function onFocus() {
102
+ if (picker.value) {
103
+ let d: Date | null = null
104
+ if (props.modelValue instanceof Date) {
105
+ d = props.modelValue
106
+ }
107
+ else if (typeof props.modelValue === 'string') {
108
+ const tmp = new Date(props.modelValue)
109
+ if (!Number.isNaN(tmp.getTime())) {
110
+ d = tmp
111
+ }
112
+ }
113
+ picker.value.setDate(d, true)
114
+ }
115
+ }
116
+
117
+ watch(
118
+ () => props.modelValue,
119
+ (val) => {
120
+ if (!picker.value) {
121
+ return
122
+ }
123
+ if (!visible.value) {
124
+ if (val instanceof Date) {
125
+ picker.value.setDate(val, true)
126
+ inputValue.value = picker.value.toString()
127
+ }
128
+ else if (typeof val === 'string') {
129
+ const d = new Date(val)
130
+ if (!Number.isNaN(d.getTime())) {
131
+ picker.value.setDate(d, true)
132
+ inputValue.value = picker.value.toString()
133
+ }
134
+ }
135
+ else {
136
+ picker.value.setDate(null, true)
137
+ inputValue.value = ''
138
+ }
139
+ }
140
+ },
141
+ )
142
+ </script>
143
+
144
+ <template>
145
+ <input
146
+ ref="inputRef"
147
+ type="text"
148
+ :value="inputValue"
149
+ :placeholder="props.placeholder"
150
+ :disabled="props.disabled"
151
+ class="input"
152
+ :class="[
153
+ { validator: props.validator },
154
+ { 'input-primary': props.primary || props.color === 'primary' },
155
+ { 'input-secondary': props.secondary || props.color === 'secondary' },
156
+ { 'input-accent': props.accent || props.color === 'accent' },
157
+ { 'input-info': props.info || props.color === 'info' },
158
+ { 'input-success': props.success || props.color === 'success' },
159
+ { 'input-warning': props.warning || props.color === 'warning' },
160
+ { 'input-error': props.error || props.color === 'error' },
161
+ { 'input-ghost': props.ghost },
162
+ { 'input-xl': props.xl || props.size === 'xl' },
163
+ { 'input-lg': props.lg || props.size === 'lg' },
164
+ { 'input-md': props.md || props.size === 'md' },
165
+ { 'input-sm': props.sm || props.size === 'sm' },
166
+ { 'input-xs': props.xs || props.size === 'xs' },
167
+ { 'join-item': props.join },
168
+ ]"
169
+ v-bind="$attrs"
170
+ @focus="onFocus"
171
+ @input="e => {
172
+ const val = (e.target as HTMLInputElement).value
173
+ emit('update:inputValue', val)
174
+ }"
175
+ >
176
+ </template>
@@ -0,0 +1,46 @@
1
+ <script setup lang="ts">
2
+ import Skeleton from './Skeleton.vue'
3
+
4
+ const { numberOfMonths = 1 } = defineProps<{
5
+ numberOfMonths?: number
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <Skeleton col class="bg-base-200 rounded-box">
11
+ <div
12
+ v-for="(month, idx) in numberOfMonths"
13
+ :key="`calendar-${month}`"
14
+ class="w-[270px] h-[270px] p-3 mx-auto flex flex-col gap-2"
15
+ :class="{
16
+ '-mt-3.5': idx > 0,
17
+ }"
18
+ >
19
+ <Flex justify-between items-center class="mb-3 px-2">
20
+ <span
21
+ class="size-4 rounded-full bg-base-300 inline-block"
22
+ :class="{
23
+ 'bg-base-300': idx === 0,
24
+ 'bg-transparent': idx !== 0,
25
+ }"
26
+ />
27
+ <span class="h-5 w-20 rounded-full bg-base-300 inline-block" />
28
+ <span
29
+ class="size-4 rounded-full bg-base-300 inline-block"
30
+ :class="{
31
+ 'bg-base-300': idx === numberOfMonths - 1,
32
+ 'bg-transparent': idx !== numberOfMonths - 1,
33
+ }"
34
+ />
35
+ </Flex>
36
+ <Flex col grow justify-between class="h-full">
37
+ <div class="grid grid-cols-7 gap-[4px] mb-4 flex-shrink-0">
38
+ <span v-for="d in 7" :key="`dow-${d}`" class="size-4 rounded-full bg-base-300 mx-auto block" />
39
+ </div>
40
+ <div class="grid grid-cols-7 gap-[4px] flex-grow items-center">
41
+ <span v-for="i in 35" :key="`day-${i}`" class="size-7 rounded-full bg-base-300 block mx-auto" />
42
+ </div>
43
+ </Flex>
44
+ </div>
45
+ </Skeleton>
46
+ </template>