daisy-ui-kit 5.0.0-pre.8 → 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.
Files changed (93) hide show
  1. package/app/components/Accordion.vue +8 -5
  2. package/app/components/Alert.vue +2 -1
  3. package/app/components/Avatar.vue +10 -7
  4. package/app/components/AvatarGroup.vue +6 -2
  5. package/app/components/Badge.vue +19 -1
  6. package/app/components/Button.vue +67 -45
  7. package/app/components/Calendar.vue +151 -42
  8. package/app/components/CalendarInput.vue +229 -130
  9. package/app/components/CalendarSkeleton.vue +51 -10
  10. package/app/components/Card.vue +20 -2
  11. package/app/components/CardActions.vue +1 -1
  12. package/app/components/CardBody.vue +1 -1
  13. package/app/components/CardTitle.vue +1 -1
  14. package/app/components/Carousel.vue +2 -1
  15. package/app/components/Chat.vue +6 -1
  16. package/app/components/Checkbox.vue +1 -1
  17. package/app/components/Collapse.vue +38 -5
  18. package/app/components/CollapseTitle.vue +11 -1
  19. package/app/components/Countdown.vue +3 -3
  20. package/app/components/CountdownTimers.vue +4 -7
  21. package/app/components/Counter.vue +14 -3
  22. package/app/components/DaisyLink.vue +33 -15
  23. package/app/components/Dock.vue +5 -6
  24. package/app/components/DockItem.vue +5 -3
  25. package/app/components/Drawer.vue +15 -12
  26. package/app/components/DrawerContent.vue +9 -6
  27. package/app/components/DrawerSide.vue +9 -6
  28. package/app/components/Dropdown.vue +61 -50
  29. package/app/components/DropdownButton.vue +11 -4
  30. package/app/components/DropdownContent.vue +90 -20
  31. package/app/components/DropdownTarget.vue +10 -3
  32. package/app/components/Fab.vue +16 -0
  33. package/app/components/FabClose.vue +18 -0
  34. package/app/components/FabMainAction.vue +5 -0
  35. package/app/components/FabTrigger.vue +117 -0
  36. package/app/components/Fieldset.vue +5 -4
  37. package/app/components/FileInput.vue +1 -1
  38. package/app/components/Filter.vue +45 -38
  39. package/app/components/Flex.vue +8 -1
  40. package/app/components/FlexItem.vue +30 -27
  41. package/app/components/Footer.vue +16 -12
  42. package/app/components/FooterTitle.vue +8 -5
  43. package/app/components/Hero.vue +9 -6
  44. package/app/components/HeroContent.vue +9 -6
  45. package/app/components/Hover3D.vue +22 -0
  46. package/app/components/HoverGallery.vue +11 -0
  47. package/app/components/Indicator.vue +12 -5
  48. package/app/components/IndicatorItem.vue +21 -14
  49. package/app/components/Input.vue +44 -47
  50. package/app/components/Kbd.vue +2 -1
  51. package/app/components/Label.vue +32 -29
  52. package/app/components/MenuExpand.vue +5 -13
  53. package/app/components/MenuExpandToggle.vue +7 -1
  54. package/app/components/MenuItem.vue +6 -4
  55. package/app/components/Modal.vue +23 -17
  56. package/app/components/Progress.vue +13 -1
  57. package/app/components/Prose.vue +7 -2
  58. package/app/components/RadialProgress.vue +8 -8
  59. package/app/components/Radio.vue +1 -1
  60. package/app/components/RadioGroup.vue +2 -2
  61. package/app/components/Range.vue +186 -46
  62. package/app/components/RangeMeasure.vue +33 -30
  63. package/app/components/RangeMeasureTick.vue +4 -5
  64. package/app/components/Rating.vue +70 -53
  65. package/app/components/Select.vue +44 -47
  66. package/app/components/SkeletonText.vue +11 -0
  67. package/app/components/Stack.vue +5 -0
  68. package/app/components/Steps.vue +7 -2
  69. package/app/components/Swap.vue +4 -10
  70. package/app/components/Tab.vue +23 -5
  71. package/app/components/Text.vue +47 -23
  72. package/app/components/TextArea.vue +75 -30
  73. package/app/components/TextRotate.vue +24 -0
  74. package/app/components/ThemeController.vue +3 -4
  75. package/app/components/ThemeProvider.vue +47 -32
  76. package/app/components/TimelineLine.vue +1 -1
  77. package/app/components/TimelineStart.vue +2 -1
  78. package/app/components/Toast.vue +46 -8
  79. package/app/components/Toggle.vue +2 -2
  80. package/app/components/Tooltip.vue +111 -21
  81. package/app/components/TooltipContent.vue +279 -1
  82. package/app/components/TooltipTarget.vue +20 -0
  83. package/app/composables/__tests__/use-calendar.test.ts +239 -0
  84. package/app/composables/use-calendar.ts +288 -0
  85. package/app/composables/use-daisy-theme.ts +140 -0
  86. package/app/composables/use-toast.ts +345 -0
  87. package/app/composables/useSearch.ts +22 -0
  88. package/app/utils/drawer-utils.ts +15 -13
  89. package/app/utils/position-area.ts +40 -0
  90. package/nuxt.d.ts +13 -0
  91. package/nuxt.js +12 -9
  92. package/package.json +52 -27
  93. package/app/utils/random-string.ts +0 -19
@@ -1,15 +1,13 @@
1
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/use-pikaday'
2
+ import type { CalendarOptions } from '../composables/use-calendar'
3
+ import { computed, ref, useId, watch } from 'vue'
4
+ import { useCalendar } from '../composables/use-calendar'
7
5
 
8
6
  const props = defineProps<{
9
7
  /** 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'>
8
+ modelValue?: Date | string | number | null
9
+ /** Calendar options */
10
+ options?: CalendarOptions
13
11
  /** If true, default to today when no value provided */
14
12
  autoDefault?: boolean
15
13
  placeholder?: string
@@ -32,145 +30,246 @@ const props = defineProps<{
32
30
  sm?: boolean
33
31
  xs?: boolean
34
32
  }>()
33
+
35
34
  const emit = defineEmits<{
36
35
  (e: 'update:modelValue', v: Date | null): void
37
36
  (e: 'update:inputValue', v: string | null): void
38
37
  }>()
39
- const inputRef = ref<ComponentPublicInstance | null>(null)
40
- const inputValue = ref<string | null>(null)
41
- const visible = ref(false)
42
38
 
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
- )
39
+ const uniqueId = useId()
40
+ const popoverId = `calendar-popover-${uniqueId}`
41
+ const anchorName = `--calendar-anchor-${uniqueId}`
42
+ const inputRef = ref<HTMLInputElement | null>(null)
43
+ const popoverRef = ref<HTMLElement | null>(null)
58
44
 
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
45
+ // Parse date from various input types
46
+ function parseDate(value: Date | string | number | null | undefined): Date | null {
47
+ if (!value) return null
48
+ if (value instanceof Date) return value
49
+ const d = new Date(value)
50
+ return Number.isNaN(d.getTime()) ? null : d
69
51
  }
70
52
 
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
- }
53
+ const initialDate = computed(() => {
54
+ const parsed = parseDate(props.modelValue)
55
+ if (parsed) return parsed
56
+ if (props.autoDefault) return new Date()
57
+ return null
99
58
  })
100
59
 
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
- }
60
+ const {
61
+ selectedDate,
62
+ viewMonth,
63
+ viewYear,
64
+ monthName,
65
+ weekdayHeaders,
66
+ weekdayHeadersFull,
67
+ calendarDays,
68
+ prevMonth,
69
+ nextMonth,
70
+ selectDate,
71
+ goToDate,
72
+ formatDate,
73
+ } = useCalendar(initialDate.value, props.options)
116
74
 
75
+ // Input display value
76
+ const inputValue = computed(() => formatDate('D MMM YYYY'))
77
+
78
+ // Sync selectedDate back to modelValue
79
+ watch(selectedDate, date => {
80
+ emit('update:modelValue', date)
81
+ emit('update:inputValue', formatDate('D MMM YYYY'))
82
+ })
83
+
84
+ // Watch for external modelValue changes
117
85
  watch(
118
86
  () => 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
- }
87
+ newValue => {
88
+ const parsed = parseDate(newValue)
89
+ if (parsed && (!selectedDate.value || parsed.getTime() !== selectedDate.value.getTime())) {
90
+ selectDate(parsed)
91
+ goToDate(parsed)
92
+ } else if (!newValue && selectedDate.value) {
93
+ selectedDate.value = null
139
94
  }
140
95
  },
141
96
  )
97
+
98
+ function handleDayClick(day: (typeof calendarDays.value)[0]) {
99
+ if (day.isDisabled) return
100
+ selectDate(day.date)
101
+ popoverRef.value?.hidePopover()
102
+ }
103
+
104
+ function handleClick() {
105
+ // Sync view to selected date when opening
106
+ if (selectedDate.value) {
107
+ goToDate(selectedDate.value)
108
+ }
109
+ popoverRef.value?.togglePopover()
110
+ }
142
111
  </script>
143
112
 
144
113
  <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
- >
114
+ <div class="relative inline-block">
115
+ <input
116
+ ref="inputRef"
117
+ type="text"
118
+ readonly
119
+ :value="inputValue"
120
+ :placeholder="props.placeholder"
121
+ :disabled="props.disabled"
122
+ class="input cursor-pointer"
123
+ :class="[
124
+ { validator: props.validator },
125
+ { 'input-primary': props.primary || props.color === 'primary' },
126
+ { 'input-secondary': props.secondary || props.color === 'secondary' },
127
+ { 'input-accent': props.accent || props.color === 'accent' },
128
+ { 'input-info': props.info || props.color === 'info' },
129
+ { 'input-success': props.success || props.color === 'success' },
130
+ { 'input-warning': props.warning || props.color === 'warning' },
131
+ { 'input-error': props.error || props.color === 'error' },
132
+ { 'input-ghost': props.ghost },
133
+ { 'input-xl': props.xl || props.size === 'xl' },
134
+ { 'input-lg': props.lg || props.size === 'lg' },
135
+ { 'input-md': props.md || props.size === 'md' },
136
+ { 'input-sm': props.sm || props.size === 'sm' },
137
+ { 'input-xs': props.xs || props.size === 'xs' },
138
+ { 'join-item': props.join },
139
+ ]"
140
+ :style="{ 'anchor-name': anchorName } as any"
141
+ v-bind="$attrs"
142
+ @click="handleClick"
143
+ />
144
+
145
+ <!-- Dropdown calendar using Popover API -->
146
+ <div
147
+ :id="popoverId"
148
+ ref="popoverRef"
149
+ popover="auto"
150
+ class="pika-single calendar-popover"
151
+ :style="{ 'position-anchor': anchorName } as any"
152
+ >
153
+ <div class="pika-lendar">
154
+ <!-- Header with navigation -->
155
+ <div class="pika-title">
156
+ <div class="pika-label">
157
+ {{ monthName }}
158
+ <select
159
+ class="pika-select pika-select-month"
160
+ :value="viewMonth"
161
+ @change="
162
+ e => {
163
+ const target = e.target as HTMLSelectElement
164
+ const newMonth = parseInt(target.value, 10)
165
+ const d = new Date(viewYear, newMonth, 1)
166
+ goToDate(d)
167
+ }
168
+ "
169
+ >
170
+ <option
171
+ v-for="(m, i) in [
172
+ 'January',
173
+ 'February',
174
+ 'March',
175
+ 'April',
176
+ 'May',
177
+ 'June',
178
+ 'July',
179
+ 'August',
180
+ 'September',
181
+ 'October',
182
+ 'November',
183
+ 'December',
184
+ ]"
185
+ :key="i"
186
+ :value="i"
187
+ >
188
+ {{ m }}
189
+ </option>
190
+ </select>
191
+ </div>
192
+ <div class="pika-label">
193
+ {{ viewYear }}
194
+ <select
195
+ class="pika-select pika-select-year"
196
+ :value="viewYear"
197
+ @change="
198
+ e => {
199
+ const target = e.target as HTMLSelectElement
200
+ const newYear = parseInt(target.value, 10)
201
+ const d = new Date(newYear, viewMonth, 1)
202
+ goToDate(d)
203
+ }
204
+ "
205
+ >
206
+ <option v-for="y in Array.from({ length: 21 }, (_, i) => viewYear - 10 + i)" :key="y" :value="y">
207
+ {{ y }}
208
+ </option>
209
+ </select>
210
+ </div>
211
+ <button type="button" class="pika-prev" @click="prevMonth">Previous Month</button>
212
+ <button type="button" class="pika-next" @click="nextMonth">Next Month</button>
213
+ </div>
214
+
215
+ <!-- Calendar grid -->
216
+ <table class="pika-table" role="grid">
217
+ <thead>
218
+ <tr>
219
+ <th v-for="(day, i) in weekdayHeaders" :key="i" scope="col">
220
+ <abbr :title="weekdayHeadersFull[i]">{{ day }}</abbr>
221
+ </th>
222
+ </tr>
223
+ </thead>
224
+ <tbody>
225
+ <tr v-for="week in 6" :key="week" class="pika-row">
226
+ <td
227
+ v-for="dayIndex in 7"
228
+ :key="dayIndex"
229
+ :class="{
230
+ 'is-today': calendarDays[(week - 1) * 7 + dayIndex - 1]?.isToday,
231
+ 'is-selected': calendarDays[(week - 1) * 7 + dayIndex - 1]?.isSelected,
232
+ 'is-disabled': calendarDays[(week - 1) * 7 + dayIndex - 1]?.isDisabled,
233
+ 'is-outside-current-month': calendarDays[(week - 1) * 7 + dayIndex - 1]?.isOutsideMonth,
234
+ }"
235
+ :aria-selected="calendarDays[(week - 1) * 7 + dayIndex - 1]?.isSelected"
236
+ >
237
+ <button
238
+ type="button"
239
+ class="pika-button pika-day"
240
+ :disabled="calendarDays[(week - 1) * 7 + dayIndex - 1]?.isDisabled"
241
+ @click="handleDayClick(calendarDays[(week - 1) * 7 + dayIndex - 1]!)"
242
+ >
243
+ {{ calendarDays[(week - 1) * 7 + dayIndex - 1]?.day }}
244
+ </button>
245
+ </td>
246
+ </tr>
247
+ </tbody>
248
+ </table>
249
+ </div>
250
+ </div>
251
+ </div>
176
252
  </template>
253
+
254
+ <style>
255
+ .calendar-popover[popover] {
256
+ position-area: block-end span-inline-end;
257
+ position-try-fallbacks:
258
+ flip-block,
259
+ flip-inline,
260
+ flip-block flip-inline;
261
+ margin: 0;
262
+ margin-top: 0.25rem;
263
+ /* Reset default popover styles */
264
+ border: none;
265
+ inset: auto;
266
+ }
267
+
268
+ .calendar-popover[popover]:popover-open {
269
+ position: fixed;
270
+ }
271
+
272
+ .calendar-popover[popover]:not(:popover-open) {
273
+ display: none;
274
+ }
275
+ </style>
@@ -1,17 +1,58 @@
1
1
  <script setup lang="ts">
2
2
  import Skeleton from './Skeleton.vue'
3
3
 
4
- const { numberOfMonths = 1 } = defineProps<{
4
+ const props = defineProps<{
5
5
  numberOfMonths?: number
6
+ date?: Date
7
+ firstDay?: number // 0 = Sunday, 1 = Monday, etc. (matches Pikaday's firstDay option)
6
8
  }>()
9
+
10
+ // Calculate the number of weeks needed for a given month
11
+ function getWeeksInMonth(date: Date, weekStartDay: number = 0): number {
12
+ const year = date.getFullYear()
13
+ const month = date.getMonth()
14
+
15
+ // Get first day of month
16
+ const firstDay = new Date(year, month, 1)
17
+ const firstDayOfWeek = firstDay.getDay()
18
+
19
+ // Get last day of month
20
+ const lastDay = new Date(year, month + 1, 0)
21
+ const daysInMonth = lastDay.getDate()
22
+
23
+ // Calculate offset based on what day the week starts on
24
+ // If week starts on Monday (1) and month starts on Sunday (0), offset is 6
25
+ // If week starts on Sunday (0) and month starts on Sunday (0), offset is 0
26
+ const offset = (firstDayOfWeek - weekStartDay + 7) % 7
27
+
28
+ // Calculate total cells needed (days in month + offset from first day)
29
+ const totalCells = daysInMonth + offset
30
+
31
+ // Return number of weeks (rows) needed
32
+ return Math.ceil(totalCells / 7)
33
+ }
34
+
35
+ // Calculate weeks for each month being displayed
36
+ const weeksPerMonth = computed(() => {
37
+ const baseDate = props.date || new Date()
38
+ const weekStartDay = props.firstDay ?? 0
39
+ const weeks: number[] = []
40
+
41
+ for (let i = 0; i < (props.numberOfMonths || 1); i++) {
42
+ const monthDate = new Date(baseDate.getFullYear(), baseDate.getMonth() + i, 1)
43
+ weeks.push(getWeeksInMonth(monthDate, weekStartDay))
44
+ }
45
+
46
+ return weeks
47
+ })
7
48
  </script>
8
49
 
9
50
  <template>
10
51
  <Skeleton col class="bg-base-200 rounded-box">
11
52
  <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"
53
+ v-for="(weeks, idx) in weeksPerMonth"
54
+ :key="`calendar-${idx}`"
55
+ class="w-[270px] px-3 py-[14px] mx-auto flex flex-col gap-2"
15
56
  :class="{
16
57
  '-mt-3.5': idx > 0,
17
58
  }"
@@ -28,17 +69,17 @@ const { numberOfMonths = 1 } = defineProps<{
28
69
  <span
29
70
  class="size-4 rounded-full bg-base-300 inline-block"
30
71
  :class="{
31
- 'bg-base-300': idx === numberOfMonths - 1,
32
- 'bg-transparent': idx !== numberOfMonths - 1,
72
+ 'bg-base-300': idx === weeksPerMonth.length - 1,
73
+ 'bg-transparent': idx !== weeksPerMonth.length - 1,
33
74
  }"
34
75
  />
35
76
  </Flex>
36
- <Flex col grow justify-between class="h-full">
37
- <div class="grid grid-cols-7 gap-[4px] mb-4 flex-shrink-0">
77
+ <Flex col class="h-full">
78
+ <div class="grid grid-cols-7 gap-[4px] mb-3 flex-shrink-0">
38
79
  <span v-for="d in 7" :key="`dow-${d}`" class="size-4 rounded-full bg-base-300 mx-auto block" />
39
80
  </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" />
81
+ <div class="grid grid-cols-7 gap-y-[8px] gap-x-[4px]">
82
+ <span v-for="i in weeks * 7" :key="`day-${i}`" class="size-7 rounded-full bg-base-300 block mx-auto" />
42
83
  </div>
43
84
  </Flex>
44
85
  </div>
@@ -1,6 +1,12 @@
1
1
  <script setup lang="ts">
2
+ import { computed, resolveComponent } from 'vue'
3
+
4
+ defineOptions({
5
+ inheritAttrs: false,
6
+ })
7
+
2
8
  const { is = 'div', ...props } = defineProps<{
3
- is?: any
9
+ is?: string
4
10
  border?: boolean
5
11
  dash?: boolean
6
12
  side?: boolean
@@ -12,11 +18,23 @@ const { is = 'div', ...props } = defineProps<{
12
18
  sm?: boolean
13
19
  xs?: boolean
14
20
  }>()
21
+
22
+ const NuxtLink = resolveComponent('NuxtLink')
23
+ const RouterLink = resolveComponent('RouterLink')
24
+
25
+ const resolvedComponent = computed(() => {
26
+ if (is === 'NuxtLink') return NuxtLink
27
+ if (is === 'RouterLink') return RouterLink
28
+ return is
29
+ })
15
30
  </script>
16
31
 
17
32
  <template>
18
33
  <component
19
- :is="is" class="card" :class="{
34
+ :is="resolvedComponent"
35
+ v-bind="$attrs"
36
+ class="card"
37
+ :class="{
20
38
  'card-border': props.border,
21
39
  'card-side': props.side,
22
40
  'image-full': props.imageFull,
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import Text from './Text.vue';
2
+ import Text from './Text.vue'
3
3
 
4
4
  const { is = 'div' } = defineProps<{
5
5
  is?: string
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import Text from './Text.vue';
2
+ import Text from './Text.vue'
3
3
 
4
4
  const { is = 'div' } = defineProps<{
5
5
  is?: string
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import Text from './Text.vue';
2
+ import Text from './Text.vue'
3
3
 
4
4
  const { is = 'div' } = defineProps<{ is?: string }>()
5
5
  </script>
@@ -11,7 +11,8 @@ const { snapTo, center, end, vertical } = defineProps<{
11
11
 
12
12
  <template>
13
13
  <div
14
- class="carousel" :class="{
14
+ class="carousel"
15
+ :class="{
15
16
  'carousel-center': center || snapTo === 'center',
16
17
  'carousel-end': end || snapTo === 'end',
17
18
  'carousel-horizontal': horizontal || orientation === 'horizontal',
@@ -1,5 +1,10 @@
1
1
  <script setup lang="ts">
2
- const { pre, align = 'start', start, end } = defineProps<{
2
+ const {
3
+ pre,
4
+ align = 'start',
5
+ start,
6
+ end,
7
+ } = defineProps<{
3
8
  pre?: boolean
4
9
  align?: string
5
10
  start?: boolean
@@ -47,5 +47,5 @@ const currentValue = computed({
47
47
  'checkbox-lg': lg || size === 'lg',
48
48
  'theme-controller': themeController,
49
49
  }"
50
- >
50
+ />
51
51
  </template>
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
- import { inject, ref } from 'vue'
2
+ import { computed, inject, provide, ref, useId, watch } from 'vue'
3
3
 
4
4
  const props = defineProps<{
5
+ id?: string
5
6
  variant?: 'arrow' | 'plus'
6
7
  arrow?: boolean
7
8
  plus?: boolean
@@ -11,19 +12,49 @@ const props = defineProps<{
11
12
  value?: any
12
13
  }>()
13
14
 
15
+ const isOpen = defineModel('open', { default: false })
16
+
14
17
  const accordionValue = inject('accordion-value', ref(null))
15
18
  const useAccordion = accordionValue.value !== null
16
19
 
20
+ // Internal state for toggle mode
21
+ const internalChecked = ref(props.open || false)
22
+
23
+ // Generate unique ID for checkbox
24
+ const checkboxId = props.id || `collapse-${useId()}`
25
+ provide('collapseCheckboxId', checkboxId)
26
+ provide('collapseToggle', props.toggle || useAccordion)
27
+
28
+ // Sync internal state with modelValue
29
+ watch(
30
+ () => isOpen.value,
31
+ newVal => {
32
+ internalChecked.value = newVal
33
+ },
34
+ )
35
+
36
+ watch(internalChecked, newVal => {
37
+ isOpen.value = newVal
38
+ })
39
+
17
40
  function handleClick() {
41
+ // Only handle clicks for accordion mode
18
42
  if (useAccordion) {
19
43
  accordionValue.value = props.value
20
44
  }
21
45
  }
46
+
47
+ const isChecked = computed(() => {
48
+ if (useAccordion) {
49
+ return accordionValue.value === props.value
50
+ }
51
+ return internalChecked.value
52
+ })
22
53
  </script>
23
54
 
24
55
  <template>
25
56
  <div
26
- tabindex="0"
57
+ :tabindex="props.toggle || useAccordion ? undefined : 0"
27
58
  class="collapse"
28
59
  :class="[
29
60
  { 'collapse-arrow': props.arrow || props.variant === 'arrow' },
@@ -31,13 +62,15 @@ function handleClick() {
31
62
  { 'collapse-open': (props.open && !props.close) || (useAccordion && accordionValue === props.value) },
32
63
  { 'collapse-close': props.close },
33
64
  ]"
34
- @click="handleClick"
65
+ @click="useAccordion ? handleClick : undefined"
35
66
  >
36
67
  <input
37
68
  v-if="props.toggle || useAccordion"
69
+ :id="checkboxId"
70
+ v-model="internalChecked"
38
71
  :type="useAccordion ? 'radio' : 'checkbox'"
39
- :checked="useAccordion ? accordionValue === props.value : undefined"
40
- >
72
+ :checked="useAccordion ? isChecked : undefined"
73
+ />
41
74
  <slot />
42
75
  </div>
43
76
  </template>
@@ -1,5 +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
+
1
8
  <template>
2
- <div class="collapse-title">
9
+ <label v-if="hasToggle && checkboxId" :for="checkboxId" class="collapse-title">
10
+ <slot />
11
+ </label>
12
+ <div v-else class="collapse-title">
3
13
  <slot />
4
14
  </div>
5
15
  </template>