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

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