daisy-ui-kit 3.0.12 → 5.0.0-pre.10

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 (198) hide show
  1. package/README.md +62 -14
  2. package/{components → app/components}/Accordion.vue +7 -10
  3. package/app/components/Alert.vue +35 -0
  4. package/app/components/Avatar.vue +128 -0
  5. package/app/components/AvatarGroup.vue +18 -0
  6. package/app/components/Badge.vue +54 -0
  7. package/app/components/Button.vue +120 -0
  8. package/app/components/Calendar.vue +66 -0
  9. package/app/components/CalendarInput.vue +174 -0
  10. package/app/components/CalendarSkeleton.vue +46 -0
  11. package/app/components/Card.vue +33 -0
  12. package/{components → app/components}/CardActions.vue +3 -6
  13. package/{components → app/components}/CardBody.vue +3 -6
  14. package/{components → app/components}/CardTitle.vue +2 -7
  15. package/app/components/Carousel.vue +23 -0
  16. package/app/components/Chat.vue +21 -0
  17. package/app/components/ChatBubble.vue +31 -0
  18. package/app/components/Checkbox.vue +51 -0
  19. package/app/components/Collapse.vue +43 -0
  20. package/app/components/Countdown.vue +15 -0
  21. package/app/components/CountdownTimers.vue +72 -0
  22. package/app/components/Counter.vue +10 -0
  23. package/app/components/DaisyLink.vue +38 -0
  24. package/app/components/Diff.vue +11 -0
  25. package/app/components/Divider.vue +43 -0
  26. package/app/components/Dock.vue +60 -0
  27. package/app/components/DockItem.vue +26 -0
  28. package/app/components/DockLabel.vue +5 -0
  29. package/{components → app/components}/Drawer.vue +6 -4
  30. package/{components → app/components}/DrawerContent.vue +1 -1
  31. package/{components → app/components}/DrawerSide.vue +1 -1
  32. package/{components → app/components}/Dropdown.vue +2 -2
  33. package/{components → app/components}/DropdownContent.vue +3 -3
  34. package/app/components/Fieldset.vue +19 -0
  35. package/app/components/FileInput.vue +53 -0
  36. package/app/components/Filter.vue +122 -0
  37. package/app/components/Flex.vue +82 -0
  38. package/{components → app/components}/FlexItem.vue +1 -1
  39. package/app/components/Footer.vue +27 -0
  40. package/app/components/FormControl.vue +5 -0
  41. package/{components → app/components}/Hero.vue +1 -1
  42. package/{components → app/components}/HeroContent.vue +1 -1
  43. package/app/components/Input.vue +119 -0
  44. package/app/components/Kbd.vue +24 -0
  45. package/app/components/Label.vue +97 -0
  46. package/app/components/List.vue +5 -0
  47. package/{components/FormControl.vue → app/components/ListColGrow.vue} +1 -1
  48. package/app/components/ListColWrap.vue +5 -0
  49. package/{components/Stat.vue → app/components/ListRow.vue} +1 -1
  50. package/app/components/LoadingBall.vue +42 -0
  51. package/app/components/LoadingBars.vue +42 -0
  52. package/app/components/LoadingDots.vue +42 -0
  53. package/app/components/LoadingInfinity.vue +42 -0
  54. package/app/components/LoadingRing.vue +42 -0
  55. package/app/components/LoadingSpinner.vue +42 -0
  56. package/app/components/Mask.vue +49 -0
  57. package/app/components/Menu.vue +30 -0
  58. package/app/components/MenuExpand.vue +100 -0
  59. package/{components → app/components}/MenuExpandToggle.vue +5 -4
  60. package/{components → app/components}/MenuItem.vue +3 -4
  61. package/app/components/MockupPhone.vue +14 -0
  62. package/{components → app/components}/Modal.vue +14 -5
  63. package/app/components/NavButton.vue +12 -0
  64. package/{components → app/components}/Navbar.vue +3 -5
  65. package/{components → app/components}/NavbarCenter.vue +3 -5
  66. package/{components → app/components}/NavbarEnd.vue +3 -5
  67. package/{components → app/components}/NavbarStart.vue +3 -5
  68. package/app/components/Progress.vue +34 -0
  69. package/{components → app/components}/Prose.vue +1 -1
  70. package/app/components/RadialProgress.vue +36 -0
  71. package/app/components/Radio.vue +69 -0
  72. package/{components → app/components}/RadioGroup.vue +2 -1
  73. package/app/components/Range.vue +61 -0
  74. package/{components → app/components}/RangeMeasure.vue +22 -21
  75. package/{components → app/components}/RangeMeasureTick.vue +9 -14
  76. package/app/components/Rating.vue +180 -0
  77. package/{components → app/components}/Select.vue +33 -29
  78. package/app/components/Skeleton.vue +5 -0
  79. package/app/components/Stack.vue +25 -0
  80. package/app/components/Stat.vue +19 -0
  81. package/app/components/Status.vue +43 -0
  82. package/app/components/Step.vue +34 -0
  83. package/app/components/StepIcon.vue +5 -0
  84. package/app/components/Steps.vue +18 -0
  85. package/app/components/Swap.vue +62 -0
  86. package/app/components/Tab.vue +38 -0
  87. package/{components → app/components}/TabContent.vue +10 -10
  88. package/app/components/Table.vue +32 -0
  89. package/app/components/Tabs.vue +53 -0
  90. package/app/components/Text.vue +142 -0
  91. package/app/components/TextArea.vue +61 -0
  92. package/app/components/ThemeController.vue +46 -0
  93. package/app/components/ThemeProvider.vue +287 -0
  94. package/app/components/ThemeTile.vue +50 -0
  95. package/app/components/Timeline.vue +22 -0
  96. package/app/components/TimelineEnd.vue +14 -0
  97. package/app/components/TimelineItem.vue +5 -0
  98. package/app/components/TimelineLine.vue +29 -0
  99. package/app/components/TimelineMiddle.vue +5 -0
  100. package/app/components/TimelineStart.vue +13 -0
  101. package/app/components/Toast.vue +72 -0
  102. package/app/components/Toggle.vue +60 -0
  103. package/app/components/Tooltip.vue +47 -0
  104. package/app/components/TooltipContent.vue +5 -0
  105. package/app/components/ValidatorHint.vue +5 -0
  106. package/nuxt.js +7 -1
  107. package/package.json +58 -61
  108. package/components/Alert.vue +0 -25
  109. package/components/Artboard.vue +0 -33
  110. package/components/Avatar.vue +0 -70
  111. package/components/AvatarGroup.vue +0 -19
  112. package/components/Badge.vue +0 -50
  113. package/components/BottomNav.vue +0 -25
  114. package/components/Button.vue +0 -111
  115. package/components/Card.vue +0 -30
  116. package/components/Carousel.vue +0 -25
  117. package/components/Chat.vue +0 -27
  118. package/components/ChatBubble.vue +0 -34
  119. package/components/Checkbox.vue +0 -55
  120. package/components/Code.vue +0 -92
  121. package/components/Collapse.vue +0 -54
  122. package/components/Countdown.vue +0 -15
  123. package/components/CountdownTimers.vue +0 -70
  124. package/components/Counter.vue +0 -14
  125. package/components/Divider.vue +0 -24
  126. package/components/FileInput.vue +0 -59
  127. package/components/Flex.vue +0 -59
  128. package/components/Footer.vue +0 -24
  129. package/components/Kbd.vue +0 -25
  130. package/components/Label.vue +0 -15
  131. package/components/LabelText.vue +0 -15
  132. package/components/LabelTextAlt.vue +0 -15
  133. package/components/Link.vue +0 -40
  134. package/components/LoadingBall.vue +0 -43
  135. package/components/LoadingBars.vue +0 -43
  136. package/components/LoadingDots.vue +0 -43
  137. package/components/LoadingInfinity.vue +0 -43
  138. package/components/LoadingRing.vue +0 -43
  139. package/components/LoadingSpinner.vue +0 -43
  140. package/components/Mask.config.ts +0 -77
  141. package/components/Mask.vue +0 -14
  142. package/components/Menu.vue +0 -35
  143. package/components/MenuExpand.vue +0 -79
  144. package/components/MockupPhone.vue +0 -8
  145. package/components/NavButton.vue +0 -20
  146. package/components/Progress.vue +0 -42
  147. package/components/RadialProgress.vue +0 -41
  148. package/components/Radio.vue +0 -76
  149. package/components/Range.vue +0 -60
  150. package/components/Rating.vue +0 -167
  151. package/components/Stack.vue +0 -13
  152. package/components/Step.vue +0 -36
  153. package/components/Steps.vue +0 -21
  154. package/components/Swap.vue +0 -58
  155. package/components/Tab.vue +0 -48
  156. package/components/Tabs.vue +0 -77
  157. package/components/TabsManager.vue +0 -38
  158. package/components/Text.vue +0 -142
  159. package/components/TextArea.vue +0 -64
  160. package/components/TextInput.vue +0 -66
  161. package/components/Toast.vue +0 -31
  162. package/components/Toggle.vue +0 -59
  163. package/components/Tooltip.vue +0 -47
  164. package/index.ts +0 -108
  165. package/utils/-utils.ts +0 -41
  166. package/utils/Button.config.ts +0 -26
  167. package/utils/fixtures.ts +0 -62
  168. package/utils/types.ts +0 -7
  169. /package/{components → app/components}/Breadcrumbs.vue +0 -0
  170. /package/{components → app/components}/CarouselItem.vue +0 -0
  171. /package/{components → app/components}/ChatFooter.vue +0 -0
  172. /package/{components → app/components}/ChatHeader.vue +0 -0
  173. /package/{components → app/components}/ChatImage.vue +0 -0
  174. /package/{components → app/components}/CollapseContent.vue +0 -0
  175. /package/{components → app/components}/CollapseTitle.vue +0 -0
  176. /package/{components → app/components}/Crumb.vue +0 -0
  177. /package/{components → app/components}/DropdownButton.vue +0 -0
  178. /package/{components → app/components}/DropdownTarget.vue +0 -0
  179. /package/{components → app/components}/FooterTitle.vue +0 -0
  180. /package/{components → app/components}/HeroOverlay.vue +0 -0
  181. /package/{components → app/components}/Indicator.vue +0 -0
  182. /package/{components → app/components}/IndicatorItem.vue +0 -0
  183. /package/{components → app/components}/Join.vue +0 -0
  184. /package/{components → app/components}/MenuTitle.vue +0 -0
  185. /package/{components → app/components}/MockupBrowser.vue +0 -0
  186. /package/{components → app/components}/MockupBrowserToolbar.vue +0 -0
  187. /package/{components → app/components}/MockupCode.vue +0 -0
  188. /package/{components → app/components}/MockupWindow.vue +0 -0
  189. /package/{components → app/components}/ModalAction.vue +0 -0
  190. /package/{components → app/components}/ModalBox.vue +0 -0
  191. /package/{components → app/components}/StatActions.vue +0 -0
  192. /package/{components → app/components}/StatDesc.vue +0 -0
  193. /package/{components → app/components}/StatFigure.vue +0 -0
  194. /package/{components → app/components}/StatTitle.vue +0 -0
  195. /package/{components → app/components}/StatValue.vue +0 -0
  196. /package/{components → app/components}/Stats.vue +0 -0
  197. /package/{utils → app/utils}/drawer-utils.ts +0 -0
  198. /package/{utils → app/utils}/random-string.ts +0 -0
@@ -0,0 +1,61 @@
1
+ <script setup lang="ts">
2
+ const props = withDefaults(defineProps<{
3
+ modelValue?: string
4
+ placeholder?: string
5
+ type?: 'text' | 'phone' | 'email' | 'search'
6
+
7
+ color?: string
8
+ neutral?: boolean
9
+ primary?: boolean
10
+ secondary?: boolean
11
+ accent?: boolean
12
+ info?: boolean
13
+ success?: boolean
14
+ warning?: boolean
15
+ error?: boolean
16
+
17
+ ghost?: boolean
18
+ disabled?: boolean
19
+ validator?: boolean
20
+
21
+ size?: 'xl' | 'lg' | 'md' | 'sm' | 'xs'
22
+ xl?: boolean
23
+ lg?: boolean
24
+ md?: boolean
25
+ sm?: boolean
26
+ xs?: boolean
27
+ }>(), {
28
+ type: 'text',
29
+ })
30
+ defineEmits(['update:modelValue'])
31
+ </script>
32
+
33
+ <template>
34
+ <textarea
35
+ :value="modelValue"
36
+ :type="type"
37
+ :placeholder="placeholder"
38
+ :disabled="disabled"
39
+ class="textarea"
40
+ :class="{
41
+ 'textarea-neutral': props.neutral || props.color === 'neutral',
42
+ 'textarea-primary': props.primary || props.color === 'primary',
43
+ 'textarea-secondary': props.secondary || props.color === 'secondary',
44
+ 'textarea-accent': props.accent || props.color === 'accent',
45
+ 'textarea-info': props.info || props.color === 'info',
46
+ 'textarea-success': props.success || props.color === 'success',
47
+ 'textarea-warning': props.warning || props.color === 'warning',
48
+ 'textarea-error': props.error || props.color === 'error',
49
+
50
+ 'textarea-ghost': props.ghost,
51
+ 'validator': props.validator,
52
+
53
+ 'textarea-xl': props.xl || props.size === 'xl',
54
+ 'textarea-lg': props.lg || props.size === 'lg',
55
+ 'textarea-md': props.md || props.size === 'md',
56
+ 'textarea-sm': props.sm || props.size === 'sm',
57
+ 'textarea-xs': props.xs || props.size === 'xs',
58
+ }"
59
+ @input="(event) => $emit('update:modelValue', (event.target as HTMLTextAreaElement).value)"
60
+ />
61
+ </template>
@@ -0,0 +1,46 @@
1
+ <script setup lang="ts">
2
+ import type { DaisyThemeInput } from '@/composables/use-daisy-theme'
3
+ import { useDaisyTheme } from '@/composables/use-daisy-theme'
4
+ import { computed } from 'vue'
5
+
6
+ const props = defineProps<{
7
+ /**
8
+ * Optional: List of themes to provide (string or DaisyThemeMeta)
9
+ */
10
+ themes?: DaisyThemeInput[]
11
+ /**
12
+ * Optional: Default theme name (string or 'default')
13
+ */
14
+ defaultTheme?: string
15
+ /**
16
+ * Optional: Storage function for persistence (e.g., useCookie, useLocalStorage, or ref)
17
+ */
18
+ storage?: <T>(key: string, initial: T) => import('vue').Ref<T>
19
+ }>()
20
+
21
+ const themeOptions = props.themes || props.defaultTheme
22
+ ? { themes: props.themes || [], defaultTheme: props.defaultTheme }
23
+ : undefined
24
+
25
+ const { theme, effectiveTheme, themes, setTheme, cycleTheme, registerTheme, removeTheme } = useDaisyTheme(
26
+ props.storage,
27
+ themeOptions,
28
+ )
29
+
30
+ const availableThemes = computed(() => themes.value.map(t => typeof t === 'string' ? t : t.theme))
31
+ </script>
32
+
33
+ <template>
34
+ <div class="theme-controller">
35
+ <slot
36
+ :theme="theme"
37
+ :effective-theme="effectiveTheme"
38
+ :themes="themes"
39
+ :available-themes="availableThemes"
40
+ :set-theme="setTheme"
41
+ :cycle-theme="cycleTheme"
42
+ :register-theme="registerTheme"
43
+ :remove-theme="removeTheme"
44
+ />
45
+ </div>
46
+ </template>
@@ -0,0 +1,287 @@
1
+ <script setup lang="ts">
2
+ import type { ComputedThemeVarsSlot } from './ThemeProvider.inject.js'
3
+ import { computed, nextTick, onUnmounted, provide, ref, watch } from 'vue'
4
+ import { daisyUiThemeKey } from './ThemeProvider.inject.js'
5
+
6
+ const props = defineProps<{
7
+ /**
8
+ * DaisyUI theme string (recommended prop name)
9
+ */
10
+ dataTheme?: string
11
+
12
+ cssVars?: string
13
+ snoop?: boolean
14
+ }>()
15
+
16
+ function cssVarToCamel(str: string): string {
17
+ return str.replace(/^--/, '').replace(/-([a-z0-9])/g, (_, c: string) => c.toUpperCase())
18
+ }
19
+ function dataAttrToCamel(str: string): string {
20
+ return str.replace(/-([a-z0-9])/g, (_, c: string) => c.toUpperCase())
21
+ }
22
+ function parseThemeString(themeString?: string): { style: Record<string, string>, dataAttrs: Record<string, string>, dataTheme: string | undefined, allAttrs: Record<string, string> } {
23
+ if (!themeString) {
24
+ return { style: {}, dataAttrs: {}, dataTheme: undefined, allAttrs: {} }
25
+ }
26
+ const varNamePattern = /^--[\w-]+$/
27
+ const attrNamePattern = /^[a-z_][\w-]*$/i
28
+ const forbiddenChars = /[<>{};]/g
29
+
30
+ const style: Record<string, string> = {}
31
+ const dataAttrs: Record<string, string> = {}
32
+ let dataTheme: string | undefined
33
+ const allAttrs: Record<string, string> = {}
34
+
35
+ // Remove @plugin ... { and closing } if present
36
+ const str = themeString.trim().replace(/^@plugin[^{}]*\{/, '').replace(/\}$/, '').trim()
37
+
38
+ // works with or without newline characters
39
+ str.split(/[\n;]/).forEach((line) => {
40
+ const trimmed = line.trim()
41
+ if (!trimmed || trimmed.startsWith('//')) {
42
+ return
43
+ }
44
+ const colonIdx = trimmed.indexOf(':')
45
+ if (colonIdx === -1) {
46
+ return
47
+ }
48
+ const key = trimmed.slice(0, colonIdx).trim()
49
+ let value = trimmed.slice(colonIdx + 1).trim()
50
+ if (value.endsWith(';')) {
51
+ value = value.slice(0, -1).trim()
52
+ }
53
+ value = value.replace(forbiddenChars, '')
54
+ if (!value) {
55
+ return
56
+ }
57
+ if (varNamePattern.test(key)) {
58
+ style[key] = value
59
+ allAttrs[cssVarToCamel(key)] = value
60
+ }
61
+ else if (key === 'name') {
62
+ dataTheme = value.replace(/"/g, '')
63
+ allAttrs.dataTheme = dataTheme
64
+ }
65
+ else if (attrNamePattern.test(key)) {
66
+ const attrKey = `data-${key.replace(/[^\w-]/g, '')}`
67
+ const attrVal = value.replace(/"/g, '')
68
+ dataAttrs[attrKey] = attrVal
69
+ if (attrKey.startsWith('data-')) {
70
+ allAttrs[dataAttrToCamel(attrKey)] = attrVal
71
+ }
72
+ }
73
+ })
74
+ return { style, dataAttrs, dataTheme, allAttrs }
75
+ }
76
+
77
+ const parsed = computed(() => parseThemeString(props.dataTheme ?? props.cssVars))
78
+
79
+ const themeVars = ref<Record<string, string>>({})
80
+ let observers: MutationObserver[] = []
81
+ let themeControllerInputs: Array<{ el: HTMLInputElement, listener: () => void }> = []
82
+ let themeControllerDomObserver: MutationObserver | null = null
83
+
84
+ // Converts a themeAttrs object to a DaisyUI theme string
85
+ function toThemeString(attrs: Record<string, string>, opts?: { asPlugin?: boolean }) {
86
+ const lines: string[] = []
87
+ // Map camelCase back to DaisyUI keys
88
+ for (const [key, value] of Object.entries(attrs)) {
89
+ if (key === 'dataTheme') {
90
+ lines.push(`name: "${value}";`)
91
+ }
92
+ else if (key.startsWith('data')) {
93
+ // Convert dataFooBar -> foo-bar
94
+ const attr = key.slice(4).replace(/([A-Z])/g, '-$1').toLowerCase()
95
+ if (attr !== 'theme') {
96
+ lines.push(`${attr}: ${value};`)
97
+ }
98
+ }
99
+ else {
100
+ // Convert camelCase to --kebab-case
101
+ const cssVar = `--${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`
102
+ lines.push(`${cssVar}: ${value};`)
103
+ }
104
+ }
105
+ const inner = lines.join('\n')
106
+ if (opts?.asPlugin) {
107
+ return `@plugin \"daisyui/theme\" {\n${inner}\n}`
108
+ }
109
+ return inner
110
+ }
111
+
112
+ // Returns the slot data object depending on snoop mode
113
+ const slotData: ComputedThemeVarsSlot = computed(() => {
114
+ // Access both dependencies so Vue always tracks them for reactivity
115
+ void themeVars.value
116
+ void parsed.value.allAttrs
117
+ if (props.snoop) {
118
+ return {
119
+ vars: themeVars.value,
120
+ toThemeString: (attrs = themeVars.value, opts) => toThemeString(attrs, opts),
121
+ }
122
+ }
123
+ return {
124
+ vars: parsed.value.allAttrs,
125
+ toThemeString: (attrs = parsed.value.allAttrs, opts) => toThemeString(attrs, opts),
126
+ }
127
+ })
128
+
129
+ // Provide the slotData as 'daisyUiTheme' for child consumers
130
+ provide(daisyUiThemeKey, slotData)
131
+
132
+ // DaisyUI variable names
133
+ const daisyVars = [
134
+ '--color-primary',
135
+ '--color-primary-content',
136
+ '--color-secondary',
137
+ '--color-secondary-content',
138
+ '--color-accent',
139
+ '--color-accent-content',
140
+ '--color-neutral',
141
+ '--color-neutral-content',
142
+ '--color-base-100',
143
+ '--color-base-200',
144
+ '--color-base-300',
145
+ '--color-base-content',
146
+ '--color-info',
147
+ '--color-info-content',
148
+ '--color-success',
149
+ '--color-success-content',
150
+ '--color-warning',
151
+ '--color-warning-content',
152
+ '--color-error',
153
+ '--color-error-content',
154
+ '--radius-selector',
155
+ '--radius-field',
156
+ '--radius-box',
157
+ '--size-selector',
158
+ '--size-field',
159
+ '--border',
160
+ '--depth',
161
+ '--noise',
162
+ ]
163
+
164
+ function getDaisyVarsFromEl(el: HTMLElement): Record<string, string> {
165
+ const vars: Record<string, string> = {}
166
+ const style = getComputedStyle(el)
167
+ for (const varName of daisyVars) {
168
+ const val = style.getPropertyValue(varName)
169
+ if (val) {
170
+ vars[cssVarToCamel(varName)] = val.trim()
171
+ }
172
+ }
173
+ return vars
174
+ }
175
+
176
+ let prefersColorSchemeMql: MediaQueryList | null = null
177
+ let prefersColorSchemeCleanup: (() => void) | null = null
178
+
179
+ function setupSnoop(rootEl: HTMLElement): void {
180
+ function updateVars(): void {
181
+ themeVars.value = getDaisyVarsFromEl(rootEl)
182
+ }
183
+ // Observe the current element for data-theme changes
184
+ const obs = new MutationObserver(() => updateVars())
185
+ obs.observe(rootEl, { attributes: true, attributeFilter: ['data-theme'] })
186
+
187
+ // Also observe the <html> element for data-theme changes
188
+ const htmlEl = document.documentElement
189
+ const htmlObs = new MutationObserver(() => updateVars())
190
+ htmlObs.observe(htmlEl, { attributes: true, attributeFilter: ['data-theme'] })
191
+
192
+ observers = [obs, htmlObs]
193
+
194
+ // Listen for prefers-color-scheme changes
195
+ prefersColorSchemeMql = window.matchMedia('(prefers-color-scheme: dark)')
196
+ const prefersColorSchemeListener = () => updateVars()
197
+ prefersColorSchemeMql.addEventListener('change', prefersColorSchemeListener)
198
+ prefersColorSchemeCleanup = () => {
199
+ prefersColorSchemeMql?.removeEventListener('change', prefersColorSchemeListener)
200
+ }
201
+
202
+ // --- Observe theme-controller checkboxes/radios, including dynamic changes ---
203
+ function bindThemeControllerInputs() {
204
+ // Remove any previous listeners
205
+ themeControllerInputs.forEach(({ el, listener }) => {
206
+ el.removeEventListener('change', listener)
207
+ })
208
+ themeControllerInputs = []
209
+ // Find all matching inputs in the document
210
+ const inputs = Array.from(document.querySelectorAll('input[type="checkbox"].theme-controller, input[type="radio"].theme-controller')) as HTMLInputElement[]
211
+ inputs.forEach((el) => {
212
+ const listener = () => updateVars()
213
+ el.addEventListener('change', listener)
214
+ themeControllerInputs.push({ el, listener })
215
+ })
216
+ }
217
+ bindThemeControllerInputs()
218
+ // Observe DOM for dynamic addition/removal of theme-controller inputs
219
+ if (themeControllerDomObserver) {
220
+ themeControllerDomObserver.disconnect()
221
+ themeControllerDomObserver = null
222
+ }
223
+ themeControllerDomObserver = new MutationObserver(() => {
224
+ bindThemeControllerInputs()
225
+ })
226
+ themeControllerDomObserver.observe(document.body, { childList: true, subtree: true })
227
+
228
+ // Watch for applied style changes (parsed.style)
229
+ watch(() => parsed.value.style, () => {
230
+ nextTick(() => updateVars())
231
+ }, { immediate: false, deep: true })
232
+ updateVars()
233
+ }
234
+
235
+ const rootEl = ref<HTMLElement | null>(null)
236
+
237
+ const isClient = typeof window !== 'undefined' && typeof document !== 'undefined'
238
+
239
+ watch([
240
+ () => props.snoop,
241
+ () => rootEl.value,
242
+ ], ([snoop, el]) => {
243
+ if (isClient) {
244
+ // Clean up previous observers if any
245
+ observers.forEach((o: MutationObserver) => o.disconnect())
246
+ observers = []
247
+ if (snoop && el) {
248
+ nextTick(() => {
249
+ setupSnoop(el)
250
+ })
251
+ }
252
+ }
253
+ }, { immediate: true })
254
+
255
+ onUnmounted(() => {
256
+ observers.forEach((o: MutationObserver) => o.disconnect())
257
+ observers = []
258
+ // Remove prefers-color-scheme listener if present
259
+ if (prefersColorSchemeCleanup) {
260
+ prefersColorSchemeCleanup()
261
+ prefersColorSchemeCleanup = null
262
+ prefersColorSchemeMql = null
263
+ }
264
+ // Remove theme-controller listeners
265
+ themeControllerInputs.forEach(({ el, listener }) => {
266
+ el.removeEventListener('change', listener)
267
+ })
268
+ themeControllerInputs = []
269
+ // Disconnect theme-controller DOM observer
270
+ if (themeControllerDomObserver) {
271
+ themeControllerDomObserver.disconnect()
272
+ themeControllerDomObserver = null
273
+ }
274
+ })
275
+ </script>
276
+
277
+ <template>
278
+ <div
279
+ ref="rootEl"
280
+ v-bind="parsed.dataAttrs"
281
+ :data-theme="dataTheme"
282
+ :style="parsed.style"
283
+ class="[background-color:unset] theme-provider"
284
+ >
285
+ <slot v-bind="slotData" />
286
+ </div>
287
+ </template>
@@ -0,0 +1,50 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+
4
+ const props = defineProps<{
5
+ theme?: string
6
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
7
+ xs?: boolean
8
+ sm?: boolean
9
+ md?: boolean
10
+ lg?: boolean
11
+ xl?: boolean
12
+ }>()
13
+ const size = computed(() => props.size ?? 'md')
14
+
15
+ const squareClass = computed(() => {
16
+ switch (size.value) {
17
+ case 'xs':
18
+ return 'w-full h-full rounded-[2px]'
19
+ case 'sm':
20
+ return 'w-full h-full rounded-[2px]'
21
+ case 'md':
22
+ return 'w-full h-full rounded-xs'
23
+ case 'lg':
24
+ return 'w-full h-full rounded-sm'
25
+ case 'xl':
26
+ return 'w-full h-full rounded'
27
+ default:
28
+ return 'w-full h-full rounded-xs'
29
+ }
30
+ })
31
+ </script>
32
+
33
+ <template>
34
+ <div
35
+ :data-theme="theme"
36
+ class="grid grid-cols-2 bg-white rounded-sm"
37
+ :class="{
38
+ '!size-4 p-0.5 gap-px': xs || size === 'xs',
39
+ '!size-5 p-0.5 gap-px': sm || size === 'sm',
40
+ 'size-6 p-0.5 gap-0.5': md || size === 'md' || !size,
41
+ 'size-8 p-0.75 gap-0.5': lg || size === 'lg',
42
+ 'size-10 h-10 p-1 gap-0.75': xl || size === 'xl',
43
+ }"
44
+ >
45
+ <div class="bg-neutral" :class="squareClass" />
46
+ <div class="bg-primary" :class="squareClass" />
47
+ <div class="bg-secondary" :class="squareClass" />
48
+ <div class="bg-accent" :class="squareClass" />
49
+ </div>
50
+ </template>
@@ -0,0 +1,22 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ vertical?: boolean
4
+ horizontal?: boolean
5
+ compact?: boolean
6
+ snapIcon?: boolean
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <ul
12
+ class="timeline"
13
+ :class="{
14
+ 'timeline-vertical': vertical,
15
+ 'timeline-horizontal': horizontal,
16
+ 'timeline-compact': compact,
17
+ 'timeline-snap-icon': snapIcon,
18
+ }"
19
+ >
20
+ <slot />
21
+ </ul>
22
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ defineProps<{ box?: boolean }>()
3
+ </script>
4
+
5
+ <template>
6
+ <div
7
+ class="timeline-end"
8
+ :class="{
9
+ 'timeline-box': box,
10
+ }"
11
+ >
12
+ <slot />
13
+ </div>
14
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <li class="timeline-item">
3
+ <slot />
4
+ </li>
5
+ </template>
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ color?: 'neutral' | 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error'
4
+ neutral?: boolean
5
+ primary?: boolean
6
+ secondary?: boolean
7
+ accent?: boolean
8
+ info?: boolean
9
+ success?: boolean
10
+ warning?: boolean
11
+ error?: boolean
12
+ }>()
13
+ </script>
14
+
15
+ <template>
16
+ <hr
17
+ class="timeline-line"
18
+ :class="{
19
+ 'bg-neutral': neutral || color === 'neutral',
20
+ 'bg-primary': primary || color === 'primary',
21
+ 'bg-secondary': secondary || color === 'secondary',
22
+ 'bg-accent': accent || color === 'accent',
23
+ 'bg-info': info || color === 'info',
24
+ 'bg-success': success || color === 'success',
25
+ 'bg-warning': warning || color === 'warning',
26
+ 'bg-error': error || color === 'error',
27
+ }"
28
+ >
29
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="timeline-middle">
3
+ <slot />
4
+ </div>
5
+ </template>
@@ -0,0 +1,13 @@
1
+ <script setup lang="ts">
2
+ defineProps<{ box?: boolean }>()
3
+ </script>
4
+
5
+ <template>
6
+ <div
7
+ class="timeline-start" :class="{
8
+ 'timeline-box': box,
9
+ }"
10
+ >
11
+ <slot />
12
+ </div>
13
+ </template>
@@ -0,0 +1,72 @@
1
+ <script setup lang="ts">
2
+ import type { Toast } from '~/composables/use-toast'
3
+ import { computed } from 'vue'
4
+ import { useToast } from '~/composables/use-toast'
5
+
6
+ // Explicit slot typing (Vue 3.4+ / Volar)
7
+ interface ToastSlotProps {
8
+ toast?: Toast
9
+ removeToast?: (id: number) => void
10
+ [key: string]: unknown
11
+ }
12
+ const props = defineProps<{
13
+ /**
14
+ * Horizontal alignment (start, center, end)
15
+ */
16
+ hAlign?: 'start' | 'center' | 'end'
17
+ start?: boolean
18
+ center?: boolean
19
+ end?: boolean
20
+ /**
21
+ * Vertical alignment (top, middle, bottom)
22
+ */
23
+ vAlign?: 'top' | 'middle' | 'bottom'
24
+ top?: boolean
25
+ middle?: boolean
26
+ bottom?: boolean
27
+ /**
28
+ * Toast channel name (for named channels)
29
+ */
30
+ name?: string
31
+ /**
32
+ * Default toast settings for this channel (merged into every toast)
33
+ * Example: { duration: 6000, type: 'info', position: 'top-center' }
34
+ */
35
+ defaults?: Partial<Toast>
36
+ }>()
37
+
38
+ defineSlots<{
39
+ default: (props: ToastSlotProps) => any
40
+ }>()
41
+
42
+ // Extract useToast options from props (name, defaults, future-proof)
43
+ const toastOptions = computed(() => {
44
+ const { name, defaults } = props
45
+ return { name, defaults }
46
+ })
47
+
48
+ const { toasts, removeToast } = useToast(toastOptions.value)
49
+ const visibleToasts = computed(() => toasts.value ?? [])
50
+ </script>
51
+
52
+ <template>
53
+ <div
54
+ class="toast"
55
+ :class="{
56
+ 'toast-start': props.start || props.hAlign === 'start',
57
+ 'toast-center': props.center || props.hAlign === 'center',
58
+ 'toast-end': props.end || props.hAlign === 'end',
59
+ 'toast-top': props.top || props.vAlign === 'top',
60
+ 'toast-middle': props.middle || props.vAlign === 'middle',
61
+ 'toast-bottom': props.bottom || props.vAlign === 'bottom',
62
+ }"
63
+ >
64
+ <slot
65
+ v-for="toast in visibleToasts"
66
+ :key="toast.id"
67
+ :toast="toast"
68
+ :remove-toast="removeToast"
69
+ />
70
+ <slot />
71
+ </div>
72
+ </template>
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+
4
+ const props = defineProps<{
5
+ modelValue?: any
6
+ disabled?: boolean
7
+ validator?: boolean
8
+ themeController?: boolean
9
+
10
+ color?: string
11
+ neutral?: boolean
12
+ primary?: boolean
13
+ secondary?: boolean
14
+ accent?: boolean
15
+ success?: boolean
16
+ info?: boolean
17
+ warning?: boolean
18
+ error?: boolean
19
+
20
+ size?: string
21
+ xs?: boolean
22
+ sm?: boolean
23
+ md?: boolean
24
+ lg?: boolean
25
+ xl?: boolean
26
+ }>()
27
+ const emit = defineEmits(['update:modelValue'])
28
+
29
+ const currentValue = computed({
30
+ get: () => props.modelValue,
31
+ set: (val: string) => emit('update:modelValue', val),
32
+ })
33
+ </script>
34
+
35
+ <template>
36
+ <input
37
+ v-model="currentValue"
38
+ type="checkbox"
39
+ v-bind="$attrs"
40
+ class="toggle"
41
+ :disabled="disabled"
42
+ :class="{
43
+ 'validator': validator,
44
+ 'toggle-neutral': neutral || color === 'neutral',
45
+ 'toggle-primary': primary || color === 'primary',
46
+ 'toggle-secondary': secondary || color === 'secondary',
47
+ 'toggle-accent': accent || color === 'accent',
48
+ 'toggle-success': success || color === 'success',
49
+ 'toggle-info': info || color === 'info',
50
+ 'toggle-warning': warning || color === 'warning',
51
+ 'toggle-error': error || color === 'error',
52
+ 'toggle-xs': xs || size === 'xs',
53
+ 'toggle-sm': sm || size === 'sm',
54
+ 'toggle-md': md || size === 'md',
55
+ 'toggle-lg': lg || size === 'lg',
56
+ 'toggle-xl': xl || size === 'xl',
57
+ 'theme-controller': themeController,
58
+ }"
59
+ >
60
+ </template>