daisy-ui-kit 5.0.0-pre.3 → 5.0.0-pre.31
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 +76 -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 +140 -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,288 @@
|
|
|
1
|
+
import { computed, ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export interface CalendarOptions {
|
|
4
|
+
/** First day of week: 0 = Sunday, 1 = Monday, etc. */
|
|
5
|
+
firstDay?: number
|
|
6
|
+
/** Minimum selectable date */
|
|
7
|
+
minDate?: Date | null
|
|
8
|
+
/** Maximum selectable date */
|
|
9
|
+
maxDate?: Date | null
|
|
10
|
+
/** Locale for formatting (default: 'en-US') */
|
|
11
|
+
locale?: string
|
|
12
|
+
/** Short month names */
|
|
13
|
+
monthsShort?: string[]
|
|
14
|
+
/** Full month names */
|
|
15
|
+
months?: string[]
|
|
16
|
+
/** Short weekday names */
|
|
17
|
+
weekdaysShort?: string[]
|
|
18
|
+
/** Full weekday names */
|
|
19
|
+
weekdays?: string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CalendarDay {
|
|
23
|
+
date: Date
|
|
24
|
+
day: number
|
|
25
|
+
month: number
|
|
26
|
+
year: number
|
|
27
|
+
isToday: boolean
|
|
28
|
+
isSelected: boolean
|
|
29
|
+
isDisabled: boolean
|
|
30
|
+
isOutsideMonth: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const DEFAULT_MONTHS = [
|
|
34
|
+
'January',
|
|
35
|
+
'February',
|
|
36
|
+
'March',
|
|
37
|
+
'April',
|
|
38
|
+
'May',
|
|
39
|
+
'June',
|
|
40
|
+
'July',
|
|
41
|
+
'August',
|
|
42
|
+
'September',
|
|
43
|
+
'October',
|
|
44
|
+
'November',
|
|
45
|
+
'December',
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
const DEFAULT_MONTHS_SHORT = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
|
49
|
+
|
|
50
|
+
const DEFAULT_WEEKDAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
|
51
|
+
|
|
52
|
+
const DEFAULT_WEEKDAYS_SHORT = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
|
53
|
+
|
|
54
|
+
export function useCalendar(initialDate: Date | null = null, options: CalendarOptions = {}) {
|
|
55
|
+
const {
|
|
56
|
+
firstDay = 0,
|
|
57
|
+
minDate = null,
|
|
58
|
+
maxDate = null,
|
|
59
|
+
months = DEFAULT_MONTHS,
|
|
60
|
+
monthsShort = DEFAULT_MONTHS_SHORT,
|
|
61
|
+
weekdays = DEFAULT_WEEKDAYS,
|
|
62
|
+
weekdaysShort = DEFAULT_WEEKDAYS_SHORT,
|
|
63
|
+
} = options
|
|
64
|
+
|
|
65
|
+
// The currently selected date
|
|
66
|
+
const selectedDate = ref<Date | null>(initialDate)
|
|
67
|
+
|
|
68
|
+
// The month/year currently being viewed
|
|
69
|
+
const viewDate = ref(initialDate ? new Date(initialDate) : new Date())
|
|
70
|
+
|
|
71
|
+
// Current view month/year
|
|
72
|
+
const viewMonth = computed(() => viewDate.value.getMonth())
|
|
73
|
+
const viewYear = computed(() => viewDate.value.getFullYear())
|
|
74
|
+
|
|
75
|
+
// Month/year display strings
|
|
76
|
+
const monthName = computed(() => months[viewMonth.value])
|
|
77
|
+
const monthNameShort = computed(() => monthsShort[viewMonth.value])
|
|
78
|
+
|
|
79
|
+
// Weekday headers adjusted for firstDay (short names for display)
|
|
80
|
+
const weekdayHeaders = computed(() => {
|
|
81
|
+
const headers: string[] = []
|
|
82
|
+
for (let i = 0; i < 7; i++) {
|
|
83
|
+
headers.push(weekdaysShort[(i + firstDay) % 7] ?? '')
|
|
84
|
+
}
|
|
85
|
+
return headers
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Full weekday names for accessibility (title attributes, screen readers)
|
|
89
|
+
const weekdayHeadersFull = computed(() => {
|
|
90
|
+
const headers: string[] = []
|
|
91
|
+
for (let i = 0; i < 7; i++) {
|
|
92
|
+
headers.push(weekdays[(i + firstDay) % 7] ?? '')
|
|
93
|
+
}
|
|
94
|
+
return headers
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Get days in a month
|
|
98
|
+
function getDaysInMonth(year: number, month: number): number {
|
|
99
|
+
return new Date(year, month + 1, 0).getDate()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check if two dates are the same day
|
|
103
|
+
function isSameDay(a: Date | null, b: Date | null): boolean {
|
|
104
|
+
if (!a || !b) return false
|
|
105
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check if a date is today
|
|
109
|
+
function isToday(date: Date): boolean {
|
|
110
|
+
return isSameDay(date, new Date())
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check if a date is disabled
|
|
114
|
+
function isDisabled(date: Date): boolean {
|
|
115
|
+
if (minDate && date < minDate) return true
|
|
116
|
+
if (maxDate && date > maxDate) return true
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Generate calendar days for the current view
|
|
121
|
+
const calendarDays = computed<CalendarDay[]>(() => {
|
|
122
|
+
const year = viewYear.value
|
|
123
|
+
const month = viewMonth.value
|
|
124
|
+
const days: CalendarDay[] = []
|
|
125
|
+
|
|
126
|
+
// First day of the month
|
|
127
|
+
const firstOfMonth = new Date(year, month, 1)
|
|
128
|
+
const startDayOfWeek = firstOfMonth.getDay()
|
|
129
|
+
|
|
130
|
+
// Calculate offset based on firstDay option
|
|
131
|
+
const offset = (startDayOfWeek - firstDay + 7) % 7
|
|
132
|
+
|
|
133
|
+
// Days from previous month
|
|
134
|
+
const prevMonth = month === 0 ? 11 : month - 1
|
|
135
|
+
const prevYear = month === 0 ? year - 1 : year
|
|
136
|
+
const daysInPrevMonth = getDaysInMonth(prevYear, prevMonth)
|
|
137
|
+
|
|
138
|
+
for (let i = offset - 1; i >= 0; i--) {
|
|
139
|
+
const day = daysInPrevMonth - i
|
|
140
|
+
const date = new Date(prevYear, prevMonth, day)
|
|
141
|
+
days.push({
|
|
142
|
+
date,
|
|
143
|
+
day,
|
|
144
|
+
month: prevMonth,
|
|
145
|
+
year: prevYear,
|
|
146
|
+
isToday: isToday(date),
|
|
147
|
+
isSelected: isSameDay(date, selectedDate.value),
|
|
148
|
+
isDisabled: isDisabled(date),
|
|
149
|
+
isOutsideMonth: true,
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Days in current month
|
|
154
|
+
const daysInMonth = getDaysInMonth(year, month)
|
|
155
|
+
for (let day = 1; day <= daysInMonth; day++) {
|
|
156
|
+
const date = new Date(year, month, day)
|
|
157
|
+
days.push({
|
|
158
|
+
date,
|
|
159
|
+
day,
|
|
160
|
+
month,
|
|
161
|
+
year,
|
|
162
|
+
isToday: isToday(date),
|
|
163
|
+
isSelected: isSameDay(date, selectedDate.value),
|
|
164
|
+
isDisabled: isDisabled(date),
|
|
165
|
+
isOutsideMonth: false,
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Days from next month to fill the grid (always 6 rows = 42 cells)
|
|
170
|
+
const nextMonth = month === 11 ? 0 : month + 1
|
|
171
|
+
const nextYear = month === 11 ? year + 1 : year
|
|
172
|
+
const remainingDays = 42 - days.length
|
|
173
|
+
|
|
174
|
+
for (let day = 1; day <= remainingDays; day++) {
|
|
175
|
+
const date = new Date(nextYear, nextMonth, day)
|
|
176
|
+
days.push({
|
|
177
|
+
date,
|
|
178
|
+
day,
|
|
179
|
+
month: nextMonth,
|
|
180
|
+
year: nextYear,
|
|
181
|
+
isToday: isToday(date),
|
|
182
|
+
isSelected: isSameDay(date, selectedDate.value),
|
|
183
|
+
isDisabled: isDisabled(date),
|
|
184
|
+
isOutsideMonth: true,
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return days
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
// Navigation
|
|
192
|
+
function prevMonth() {
|
|
193
|
+
const d = new Date(viewDate.value)
|
|
194
|
+
d.setMonth(d.getMonth() - 1)
|
|
195
|
+
viewDate.value = d
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function nextMonth() {
|
|
199
|
+
const d = new Date(viewDate.value)
|
|
200
|
+
d.setMonth(d.getMonth() + 1)
|
|
201
|
+
viewDate.value = d
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function goToMonth(month: number) {
|
|
205
|
+
const d = new Date(viewDate.value)
|
|
206
|
+
d.setMonth(month)
|
|
207
|
+
viewDate.value = d
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function goToYear(year: number) {
|
|
211
|
+
const d = new Date(viewDate.value)
|
|
212
|
+
d.setFullYear(year)
|
|
213
|
+
viewDate.value = d
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function goToDate(date: Date) {
|
|
217
|
+
viewDate.value = new Date(date)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function goToToday() {
|
|
221
|
+
viewDate.value = new Date()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Selection
|
|
225
|
+
function selectDate(date: Date) {
|
|
226
|
+
if (isDisabled(date)) return
|
|
227
|
+
selectedDate.value = new Date(date)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function clearSelection() {
|
|
231
|
+
selectedDate.value = null
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Format selected date
|
|
235
|
+
function formatDate(format: string = 'D MMM YYYY'): string {
|
|
236
|
+
if (!selectedDate.value) return ''
|
|
237
|
+
|
|
238
|
+
const d = selectedDate.value
|
|
239
|
+
const day = d.getDate()
|
|
240
|
+
const month = d.getMonth()
|
|
241
|
+
const year = d.getFullYear()
|
|
242
|
+
|
|
243
|
+
return format
|
|
244
|
+
.replace('YYYY', String(year))
|
|
245
|
+
.replace('YY', String(year).slice(-2))
|
|
246
|
+
.replace('MMMM', months[month] ?? '')
|
|
247
|
+
.replace('MMM', monthsShort[month] ?? '')
|
|
248
|
+
.replace('MM', String(month + 1).padStart(2, '0'))
|
|
249
|
+
.replace('M', String(month + 1))
|
|
250
|
+
.replace('DD', String(day).padStart(2, '0'))
|
|
251
|
+
.replace('D', String(day))
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
// State
|
|
256
|
+
selectedDate,
|
|
257
|
+
viewDate,
|
|
258
|
+
viewMonth,
|
|
259
|
+
viewYear,
|
|
260
|
+
|
|
261
|
+
// Display
|
|
262
|
+
monthName,
|
|
263
|
+
monthNameShort,
|
|
264
|
+
weekdayHeaders,
|
|
265
|
+
weekdayHeadersFull,
|
|
266
|
+
calendarDays,
|
|
267
|
+
|
|
268
|
+
// Navigation
|
|
269
|
+
prevMonth,
|
|
270
|
+
nextMonth,
|
|
271
|
+
goToMonth,
|
|
272
|
+
goToYear,
|
|
273
|
+
goToDate,
|
|
274
|
+
goToToday,
|
|
275
|
+
|
|
276
|
+
// Selection
|
|
277
|
+
selectDate,
|
|
278
|
+
clearSelection,
|
|
279
|
+
|
|
280
|
+
// Formatting
|
|
281
|
+
formatDate,
|
|
282
|
+
|
|
283
|
+
// Utilities
|
|
284
|
+
isSameDay,
|
|
285
|
+
isToday,
|
|
286
|
+
isDisabled,
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { Ref } from 'vue'
|
|
2
|
+
import { usePreferredDark } from '@vueuse/core'
|
|
3
|
+
import { computed, ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
// Type for a theme object
|
|
6
|
+
export interface DaisyThemeMeta {
|
|
7
|
+
theme: string
|
|
8
|
+
cssVars?: string
|
|
9
|
+
name?: string
|
|
10
|
+
[key: string]: any
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type DaisyThemeInput = string | DaisyThemeMeta
|
|
14
|
+
|
|
15
|
+
interface DaisyThemeOptions {
|
|
16
|
+
themes: DaisyThemeInput[]
|
|
17
|
+
defaultTheme?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeTheme(input: DaisyThemeInput): DaisyThemeMeta {
|
|
21
|
+
if (typeof input === 'string') {
|
|
22
|
+
return { theme: input }
|
|
23
|
+
}
|
|
24
|
+
return { ...input }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// SSR state using Nuxt's useState for proper hydration
|
|
28
|
+
function useSSRState<T>(key: string, init: () => T): Ref<T> {
|
|
29
|
+
// Use Nuxt's useState if available (works on both server and client with proper hydration)
|
|
30
|
+
if (typeof useState !== 'undefined') {
|
|
31
|
+
try {
|
|
32
|
+
return useState(key, init)
|
|
33
|
+
} catch {
|
|
34
|
+
// Fallback if useState is not available
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return ref(init()) as Ref<T>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Global storage ref - set once in app.vue, reused elsewhere
|
|
41
|
+
let globalStorageRef: Ref<string> | null = null
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* useDaisyTheme composable
|
|
45
|
+
* @param storage Optional. Ref factory for persistence (e.g., useCookie, useLocalStorage, or ref). Defaults to ref.
|
|
46
|
+
* @param options Optional. Theme options (themes, defaultTheme, etc.).
|
|
47
|
+
*
|
|
48
|
+
* Calling with arguments (in app.vue/root): initializes global state.
|
|
49
|
+
* Calling with no arguments (in any component): reuses global state.
|
|
50
|
+
*/
|
|
51
|
+
export function useDaisyTheme(storage?: <T>(key: string, initial: T) => Ref<T>, options?: DaisyThemeOptions) {
|
|
52
|
+
// Use SSR-safe state that hydrates properly
|
|
53
|
+
const themes = useSSRState('daisy-themes', () => options?.themes?.map(normalizeTheme) ?? [])
|
|
54
|
+
|
|
55
|
+
// If options provided, update themes
|
|
56
|
+
if (options?.themes) {
|
|
57
|
+
themes.value = options.themes.map(normalizeTheme)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Theme name - use provided storage or reuse global ref
|
|
61
|
+
let theme: Ref<string>
|
|
62
|
+
if (storage) {
|
|
63
|
+
// Initialize with provided storage (e.g., useCookie in app.vue)
|
|
64
|
+
theme = storage('theme', options?.defaultTheme ?? themes.value[0]?.theme ?? 'light')
|
|
65
|
+
globalStorageRef = theme
|
|
66
|
+
} else if (globalStorageRef) {
|
|
67
|
+
// Reuse the existing storage ref
|
|
68
|
+
theme = globalStorageRef
|
|
69
|
+
} else {
|
|
70
|
+
// Fallback to SSR state if no storage was ever provided
|
|
71
|
+
theme = useSSRState('daisy-theme', () => options?.defaultTheme ?? themes.value[0]?.theme ?? 'light')
|
|
72
|
+
globalStorageRef = theme
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// System dark mode
|
|
76
|
+
const preferredDark = usePreferredDark()
|
|
77
|
+
|
|
78
|
+
// Compute the effective theme for UI/DOM
|
|
79
|
+
const effectiveTheme = computed(() => {
|
|
80
|
+
if (theme.value === 'system') {
|
|
81
|
+
return preferredDark.value ? 'dark' : 'light'
|
|
82
|
+
}
|
|
83
|
+
return theme.value
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// Set theme by name
|
|
87
|
+
function setTheme(name: string) {
|
|
88
|
+
if (themes.value.some(t => t.theme === name) || name === 'system') {
|
|
89
|
+
theme.value = name
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Cycle to next theme
|
|
94
|
+
function cycleTheme() {
|
|
95
|
+
const names = themes.value.map(t => t.theme)
|
|
96
|
+
if (!names.length) {
|
|
97
|
+
return // Guard: no themes
|
|
98
|
+
}
|
|
99
|
+
const idx = names.indexOf(theme.value)
|
|
100
|
+
const nextIdx = (idx + 1) % names.length
|
|
101
|
+
// Only set if defined (TypeScript safety)
|
|
102
|
+
if (typeof names[nextIdx] === 'string') {
|
|
103
|
+
setTheme(names[nextIdx]!)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Register a new theme
|
|
108
|
+
function registerTheme(newTheme: DaisyThemeInput) {
|
|
109
|
+
const meta = normalizeTheme(newTheme)
|
|
110
|
+
if (!themes.value.some(t => t.theme === meta.theme)) {
|
|
111
|
+
themes.value.push(meta)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Remove a theme by name
|
|
116
|
+
function removeTheme(name: string) {
|
|
117
|
+
const idx = themes.value.findIndex(t => t.theme === name)
|
|
118
|
+
if (idx !== -1) {
|
|
119
|
+
themes.value.splice(idx, 1)
|
|
120
|
+
// If current theme was removed, fallback to first
|
|
121
|
+
if (theme.value === name) {
|
|
122
|
+
theme.value = themes.value[0]?.theme ?? 'light'
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Get the current theme object
|
|
128
|
+
const themeInfo = computed(() => themes.value.find(t => t.theme === theme.value))
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
themes,
|
|
132
|
+
theme,
|
|
133
|
+
effectiveTheme,
|
|
134
|
+
themeInfo,
|
|
135
|
+
setTheme,
|
|
136
|
+
cycleTheme,
|
|
137
|
+
registerTheme,
|
|
138
|
+
removeTheme,
|
|
139
|
+
}
|
|
140
|
+
}
|