@stacksjs/components 0.2.35 → 0.2.53

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 (237) hide show
  1. package/dist/Accordion-19x8q4rj.stx +88 -0
  2. package/dist/{Audio-vdmg1258.stx → Audio-hjywk3qc.stx} +4 -4
  3. package/dist/{Avatar-365cvrh4.stx → Avatar-h3jmwcq0.stx} +23 -34
  4. package/dist/{Badge-8m3qete4.stx → Badge-k8dy3d53.stx} +11 -19
  5. package/dist/{Button-3tt4vsqt.stx → Button-1vknasy3.stx} +18 -33
  6. package/dist/Calendar-reny0et7.stx +189 -0
  7. package/dist/{Card-6wdbexqr.stx → Card-c1g1fd1w.stx} +27 -33
  8. package/dist/{Checkbox-p3q3e3nf.stx → Checkbox-zgdmy3p5.stx} +36 -44
  9. package/dist/{Checkout-e4037tyy.stx → Checkout-agt0ngpw.stx} +8 -16
  10. package/dist/Combobox-fdkr6cdq.stx +96 -0
  11. package/dist/{ComboboxButton-8gxwkhdx.stx → ComboboxButton-g76stp1e.stx} +14 -2
  12. package/dist/{ComboboxInput-mk99z65n.stx → ComboboxInput-z1293bbh.stx} +12 -5
  13. package/dist/ComboboxOption-hmxt3gte.stx +49 -0
  14. package/dist/{ListboxOptions-8kqvfp7z.stx → ComboboxOptions-sq5360ae.stx} +2 -2
  15. package/dist/CommandPalette-36mxf05w.stx +81 -0
  16. package/dist/CommandPaletteItem-mvk6q7a2.stx +27 -0
  17. package/dist/Dialog-hczpexs4.stx +37 -0
  18. package/dist/{Drawer-p7m351bn.stx → Drawer-xhevp5cw.stx} +29 -49
  19. package/dist/Dropdown-vvhemtc0.stx +82 -0
  20. package/dist/{DropdownButton-9w8zkfwj.stx → DropdownButton-wcz67dxy.stx} +12 -6
  21. package/dist/DropdownItem-c7rz08e5.stx +35 -0
  22. package/dist/{DropdownItems-aygjpxkx.stx → DropdownItems-1sxnd5qn.stx} +2 -2
  23. package/dist/EmailInput-vak3kkmk.stx +30 -0
  24. package/dist/Form-mas630hy.stx +95 -0
  25. package/dist/{Image-jhkm6w1f.stx → Image-yhxcvgzs.stx} +68 -100
  26. package/dist/Listbox-q49tnr90.stx +101 -0
  27. package/dist/{ListboxButton-spwx7513.stx → ListboxButton-keb8gm0q.stx} +12 -1
  28. package/dist/ListboxOption-e0dtv1gc.stx +49 -0
  29. package/dist/{ComboboxOptions-6b5ywc5x.stx → ListboxOptions-ejkatcfg.stx} +7 -3
  30. package/dist/{Login-rx9gyqhx.stx → Login-gepemsth.stx} +17 -19
  31. package/dist/Navigator-rk2a5wvk.stx +113 -0
  32. package/dist/Notification-tb9b525s.stx +78 -0
  33. package/dist/{NumberInput-nzpkx1ry.stx → NumberInput-drnvtqej.stx} +40 -53
  34. package/dist/{Pagination-h61eq390.stx → Pagination-n3d31cns.stx} +73 -108
  35. package/dist/PasswordInput-n02x6053.stx +142 -0
  36. package/dist/{PaymentMethods-j42jsk5x.stx → PaymentMethods-x4vrx9yb.stx} +21 -30
  37. package/dist/Popover-et2q44m7.stx +60 -0
  38. package/dist/{PopoverButton-fnaqc77g.stx → PopoverButton-mffzc9pr.stx} +12 -6
  39. package/dist/{PopoverPanel-y0vtf5w9.stx → PopoverPanel-054yh38h.stx} +6 -6
  40. package/dist/{Portal-s2q37yp0.stx → Portal-1vf14s4x.stx} +20 -37
  41. package/dist/{Progress-vmdkm06p.stx → Progress-yxcntr4x.stx} +36 -48
  42. package/dist/{Radio-04bs3d21.stx → Radio-cgjv2hb2.stx} +29 -34
  43. package/dist/RadioGroup-qxmnm3xm.stx +60 -0
  44. package/dist/RadioGroupOption-s1d6r221.stx +58 -0
  45. package/dist/SearchInput-pdx7agn0.stx +44 -0
  46. package/dist/{Select-g4mmvfv8.stx → Select-28y0pg83.stx} +23 -31
  47. package/dist/Sidebar-73vc78as.stx +236 -0
  48. package/dist/SidebarFooter-zsfsgcxb.stx +109 -0
  49. package/dist/SidebarHeader-7v174a9c.stx +122 -0
  50. package/dist/SidebarItem-3y3e8g2t.stx +92 -0
  51. package/dist/SidebarSection-z5ey6awe.stx +113 -0
  52. package/dist/{Signup-c4dyv6f3.stx → Signup-hxm5hwkr.stx} +26 -28
  53. package/dist/{StepperStep-en4r00zv.stx → StepperStep-wxgembat.stx} +9 -5
  54. package/dist/{SubscriptionCheckout-gawjzf3q.stx → SubscriptionCheckout-zy2ycrmw.stx} +99 -129
  55. package/dist/Switch-r5t5w04w.stx +70 -0
  56. package/dist/{TableHeader-ydys9rzz.stx → TableHeader-0eyzbqpz.stx} +10 -3
  57. package/dist/Tabs-bzvvn4ke.stx +126 -0
  58. package/dist/Teleport-tsx4bt4n.stx +65 -0
  59. package/dist/{TextInput-k2kfkgy1.stx → TextInput-kf084763.stx} +42 -59
  60. package/dist/{Textarea-szad57dz.stx → Textarea-xg4rr2xz.stx} +42 -54
  61. package/dist/{Tooltip-em7t551n.stx → Tooltip-m0tqd72e.stx} +34 -40
  62. package/dist/Transition-7smenaz8.stx +102 -0
  63. package/dist/{TwoFactorChallenge-an9yvxvq.stx → TwoFactorChallenge-y5m3nk79.stx} +49 -63
  64. package/dist/{Video-crxva65p.stx → Video-84j4pv6h.stx} +4 -4
  65. package/dist/VirtualList-fa7v5a3h.stx +67 -0
  66. package/dist/VirtualTable-f9crbfjr.stx +103 -0
  67. package/dist/components/CodeBlock.stx +1 -6
  68. package/dist/index.js +13 -13
  69. package/dist/ui/accordion/Accordion.stx +1 -3
  70. package/dist/ui/auth/Login.stx +0 -1
  71. package/dist/ui/auth/Signup.stx +0 -1
  72. package/dist/ui/auth/TwoFactorChallenge.stx +1 -3
  73. package/dist/ui/avatar/Avatar.stx +0 -2
  74. package/dist/ui/badge/Badge.stx +0 -1
  75. package/dist/ui/button/Button.stx +1 -2
  76. package/dist/ui/calendar/Calendar.stx +12 -2
  77. package/dist/ui/card/Card.stx +0 -2
  78. package/dist/ui/checkbox/Checkbox.stx +2 -2
  79. package/dist/ui/combobox/Combobox.stx +2 -1
  80. package/dist/ui/combobox/ComboboxButton.stx +1 -0
  81. package/dist/ui/combobox/ComboboxInput.stx +1 -4
  82. package/dist/ui/combobox/ComboboxOption.stx +2 -9
  83. package/dist/ui/combobox/ComboboxOptions.stx +1 -1
  84. package/dist/ui/command-palette/CommandPalette.stx +3 -6
  85. package/dist/ui/command-palette/CommandPaletteItem.stx +2 -7
  86. package/dist/ui/dialog/Dialog.stx +1 -3
  87. package/dist/ui/drawer/Drawer.stx +1 -3
  88. package/dist/ui/dropdown/Dropdown.stx +2 -5
  89. package/dist/ui/dropdown/DropdownButton.stx +1 -3
  90. package/dist/ui/dropdown/DropdownItem.stx +2 -7
  91. package/dist/ui/dropdown/DropdownItems.stx +1 -1
  92. package/dist/ui/form/Form.stx +0 -5
  93. package/dist/ui/image/Image.stx +1 -6
  94. package/dist/ui/input/EmailInput.stx +1 -1
  95. package/dist/ui/input/NumberInput.stx +0 -1
  96. package/dist/ui/input/PasswordInput.stx +1 -4
  97. package/dist/ui/input/SearchInput.stx +2 -2
  98. package/dist/ui/input/TextInput.stx +1 -2
  99. package/dist/ui/listbox/Listbox.stx +2 -1
  100. package/dist/ui/listbox/ListboxButton.stx +1 -0
  101. package/dist/ui/listbox/ListboxOption.stx +2 -9
  102. package/dist/ui/listbox/ListboxOptions.stx +1 -1
  103. package/dist/ui/navigator/Navigator.stx +4 -4
  104. package/dist/ui/notification/Notification.stx +1 -5
  105. package/dist/ui/pagination/Pagination.stx +1 -2
  106. package/dist/ui/payment/Checkout.stx +0 -4
  107. package/dist/ui/payment/PaymentMethods.stx +15 -2
  108. package/dist/ui/payment/SubscriptionCheckout.stx +3 -184
  109. package/dist/ui/popover/Popover.stx +2 -5
  110. package/dist/ui/popover/PopoverButton.stx +1 -3
  111. package/dist/ui/popover/PopoverPanel.stx +1 -1
  112. package/dist/ui/portal/Portal.stx +1 -0
  113. package/dist/ui/progress/Progress.stx +6 -1
  114. package/dist/ui/radio/Radio.stx +1 -1
  115. package/dist/ui/radio-group/RadioGroup.stx +2 -1
  116. package/dist/ui/radio-group/RadioGroupOption.stx +2 -9
  117. package/dist/ui/select/Select.stx +1 -1
  118. package/dist/ui/sidebar/Sidebar.stx +9 -26
  119. package/dist/ui/sidebar/SidebarFooter.stx +6 -21
  120. package/dist/ui/sidebar/SidebarHeader.stx +7 -18
  121. package/dist/ui/sidebar/SidebarItem.stx +4 -20
  122. package/dist/ui/sidebar/SidebarSection.stx +5 -18
  123. package/dist/ui/sidebar/index.d.ts +13 -1
  124. package/dist/ui/stepper/StepperStep.stx +1 -5
  125. package/dist/ui/switch/Switch.stx +4 -10
  126. package/dist/ui/table/TableHeader.stx +1 -1
  127. package/dist/ui/tabs/Tabs.stx +0 -1
  128. package/dist/ui/teleport/Teleport.stx +1 -0
  129. package/dist/ui/textarea/Textarea.stx +1 -1
  130. package/dist/ui/tooltip/Tooltip.stx +1 -2
  131. package/dist/ui/transition/Transition.stx +9 -16
  132. package/dist/ui/virtual-list/VirtualList.stx +2 -4
  133. package/dist/ui/virtual-table/VirtualTable.stx +1 -1
  134. package/package.json +3 -3
  135. package/src/components/CodeBlock.stx +26 -29
  136. package/src/components/Footer.stx +1 -1
  137. package/src/components/Hero.stx +1 -1
  138. package/src/components/Installation.stx +5 -7
  139. package/src/ui/accordion/Accordion.stx +48 -68
  140. package/src/ui/audio/Audio.stx +4 -4
  141. package/src/ui/auth/Login.stx +17 -19
  142. package/src/ui/auth/Signup.stx +26 -28
  143. package/src/ui/auth/TwoFactorChallenge.stx +49 -63
  144. package/src/ui/avatar/Avatar.stx +23 -34
  145. package/src/ui/badge/Badge.stx +11 -19
  146. package/src/ui/button/Button.stx +18 -33
  147. package/src/ui/calendar/Calendar.stx +93 -136
  148. package/src/ui/card/Card.stx +27 -33
  149. package/src/ui/checkbox/Checkbox.stx +36 -44
  150. package/src/ui/combobox/Combobox.stx +87 -4
  151. package/src/ui/combobox/ComboboxButton.stx +14 -2
  152. package/src/ui/combobox/ComboboxInput.stx +12 -5
  153. package/src/ui/combobox/ComboboxOption.stx +29 -32
  154. package/src/ui/combobox/ComboboxOptions.stx +7 -3
  155. package/src/ui/command-palette/CommandPalette.stx +70 -28
  156. package/src/ui/command-palette/CommandPaletteItem.stx +15 -20
  157. package/src/ui/dialog/Dialog.stx +28 -10
  158. package/src/ui/drawer/Drawer.stx +29 -49
  159. package/src/ui/dropdown/Dropdown.stx +69 -11
  160. package/src/ui/dropdown/DropdownButton.stx +12 -6
  161. package/src/ui/dropdown/DropdownItem.stx +18 -26
  162. package/src/ui/dropdown/DropdownItems.stx +2 -2
  163. package/src/ui/form/Form.stx +50 -164
  164. package/src/ui/heatmap/Heatmap.stx +16 -37
  165. package/src/ui/image/Image.stx +68 -100
  166. package/src/ui/input/EmailInput.stx +16 -27
  167. package/src/ui/input/NumberInput.stx +40 -53
  168. package/src/ui/input/PasswordInput.stx +64 -77
  169. package/src/ui/input/SearchInput.stx +27 -46
  170. package/src/ui/input/TextInput.stx +42 -59
  171. package/src/ui/listbox/Listbox.stx +91 -4
  172. package/src/ui/listbox/ListboxButton.stx +12 -1
  173. package/src/ui/listbox/ListboxOption.stx +30 -38
  174. package/src/ui/listbox/ListboxOptions.stx +2 -2
  175. package/src/ui/navigator/Navigator.stx +52 -77
  176. package/src/ui/notification/Notification.stx +46 -39
  177. package/src/ui/pagination/Pagination.stx +73 -108
  178. package/src/ui/payment/Checkout.stx +8 -16
  179. package/src/ui/payment/PaymentMethods.stx +21 -30
  180. package/src/ui/payment/SubscriptionCheckout.stx +99 -129
  181. package/src/ui/popover/Popover.stx +50 -14
  182. package/src/ui/popover/PopoverButton.stx +12 -6
  183. package/src/ui/popover/PopoverPanel.stx +6 -6
  184. package/src/ui/portal/Portal.stx +20 -37
  185. package/src/ui/progress/Progress.stx +36 -48
  186. package/src/ui/radio/Radio.stx +29 -34
  187. package/src/ui/radio-group/RadioGroup.stx +51 -4
  188. package/src/ui/radio-group/RadioGroupOption.stx +37 -30
  189. package/src/ui/select/Select.stx +23 -31
  190. package/src/ui/sidebar/Sidebar.stx +136 -188
  191. package/src/ui/sidebar/SidebarFooter.stx +49 -35
  192. package/src/ui/sidebar/SidebarHeader.stx +52 -30
  193. package/src/ui/sidebar/SidebarItem.stx +52 -78
  194. package/src/ui/sidebar/SidebarSection.stx +56 -51
  195. package/src/ui/sidebar/index.ts +21 -1
  196. package/src/ui/stepper/StepperStep.stx +9 -5
  197. package/src/ui/switch/Switch.stx +38 -42
  198. package/src/ui/table/TableHeader.stx +10 -3
  199. package/src/ui/tabs/Tabs.stx +68 -87
  200. package/src/ui/teleport/Teleport.stx +23 -59
  201. package/src/ui/textarea/Textarea.stx +42 -54
  202. package/src/ui/tooltip/Tooltip.stx +34 -40
  203. package/src/ui/transition/Transition.stx +76 -148
  204. package/src/ui/video/Video.stx +4 -4
  205. package/src/ui/virtual-list/VirtualList.stx +45 -61
  206. package/src/ui/virtual-table/VirtualTable.stx +62 -72
  207. package/dist/Accordion-sbfra0mz.stx +0 -108
  208. package/dist/Calendar-58nnkdh8.stx +0 -232
  209. package/dist/Combobox-th5ebw62.stx +0 -13
  210. package/dist/ComboboxOption-an9zyan6.stx +0 -52
  211. package/dist/CommandPalette-kwmdf2np.stx +0 -39
  212. package/dist/CommandPaletteItem-zx2fbpts.stx +0 -32
  213. package/dist/Dialog-rgmj8q2j.stx +0 -19
  214. package/dist/Dropdown-jz4jy0n3.stx +0 -24
  215. package/dist/DropdownItem-wagdspb8.stx +0 -43
  216. package/dist/EmailInput-ajsp9vw6.stx +0 -41
  217. package/dist/Form-1mdmx9b6.stx +0 -209
  218. package/dist/Listbox-pqqh72g8.stx +0 -14
  219. package/dist/ListboxOption-7nxzaexq.stx +0 -57
  220. package/dist/Navigator-aay8bjab.stx +0 -138
  221. package/dist/Notification-gk33gv7b.stx +0 -71
  222. package/dist/PasswordInput-zv4hm43j.stx +0 -155
  223. package/dist/Popover-469gbt6r.stx +0 -24
  224. package/dist/RadioGroup-bvb1mrhd.stx +0 -13
  225. package/dist/RadioGroupOption-cpdwgnhw.stx +0 -51
  226. package/dist/SearchInput-2521m475.stx +0 -63
  227. package/dist/Sidebar-40ckbzh8.stx +0 -288
  228. package/dist/SidebarFooter-hnth56mg.stx +0 -95
  229. package/dist/SidebarHeader-3wrte2p2.stx +0 -100
  230. package/dist/SidebarItem-zqtvvzpc.stx +0 -118
  231. package/dist/SidebarSection-3etwnbjr.stx +0 -108
  232. package/dist/Switch-036j9vm1.stx +0 -74
  233. package/dist/Tabs-svh721m0.stx +0 -145
  234. package/dist/Teleport-qfch3w0b.stx +0 -101
  235. package/dist/Transition-0hmr6zxb.stx +0 -174
  236. package/dist/VirtualList-cndhjejh.stx +0 -83
  237. package/dist/VirtualTable-ng0b48yp.stx +0 -113
@@ -0,0 +1,88 @@
1
+ <script server>
2
+ export const items = $props.items || []
3
+ export const allowMultiple = $props.allowMultiple || false
4
+ export const defaultOpen = $props.defaultOpen || []
5
+ export const className = $props.className || ''
6
+
7
+ export const accordionClasses = `divide-y divide-gray-200 dark:divide-gray-700 border border-gray-200 dark:border-gray-700 rounded-lg ${className}`.trim()
8
+ export const headerClasses = `w-full flex items-center justify-between px-4 py-3 text-left font-medium text-gray-900 dark:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900`.trim()
9
+ export const iconClasses = `w-5 h-5 text-gray-500 dark:text-gray-400 transition-transform duration-200`.trim()
10
+ export const contentClasses = `px-4 py-3 text-gray-700 dark:text-gray-300`.trim()
11
+ </script>
12
+
13
+ <script client>
14
+ const emit = defineEmits()
15
+ const allowMultiple = {{ allowMultiple }}
16
+ const openItems = state({{ defaultOpen }})
17
+
18
+ function toggleItem(index) {
19
+ const cur = openItems()
20
+ if (allowMultiple) {
21
+ openItems.set(cur.includes(index) ? cur.filter(i => i !== index) : [...cur, index])
22
+ }
23
+ else {
24
+ openItems.set(cur.includes(index) ? [] : [index])
25
+ }
26
+ emit('change', openItems())
27
+ }
28
+
29
+ function isOpen(index) {
30
+ return openItems().includes(index)
31
+ }
32
+
33
+ function onHeaderKey(event, index) {
34
+ const buttons = event.currentTarget.closest('[role="region"]').querySelectorAll('button[data-accordion-header]')
35
+ if (event.key === 'ArrowDown') {
36
+ event.preventDefault()
37
+ buttons[index + 1]?.focus()
38
+ }
39
+ else if (event.key === 'ArrowUp') {
40
+ event.preventDefault()
41
+ buttons[index - 1]?.focus()
42
+ }
43
+ else if (event.key === 'Home') {
44
+ event.preventDefault()
45
+ buttons[0]?.focus()
46
+ }
47
+ else if (event.key === 'End') {
48
+ event.preventDefault()
49
+ buttons[buttons.length - 1]?.focus()
50
+ }
51
+ }
52
+ </script>
53
+
54
+ <div class="{{ accordionClasses }}" role="region">
55
+ @foreach(item in items)
56
+ <div>
57
+ <button
58
+ type="button"
59
+ data-accordion-header
60
+ class="{{ headerClasses }}"
61
+ @click="toggleItem({{ $loop.index }})"
62
+ @keydown="onHeaderKey($event, {{ $loop.index }})"
63
+ :aria-expanded="isOpen({{ $loop.index }}) ? 'true' : 'false'"
64
+ aria-controls="accordion-content-{{ $loop.index }}"
65
+ >
66
+ <span>{{ item.title }}</span>
67
+ <svg
68
+ class="{{ iconClasses }}"
69
+ :style="`transform: rotate(${isOpen({{ $loop.index }}) ? 180 : 0}deg)`"
70
+ fill="none"
71
+ viewBox="0 0 24 24"
72
+ stroke="currentColor"
73
+ >
74
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
75
+ </svg>
76
+ </button>
77
+
78
+ <div
79
+ :show="isOpen({{ $loop.index }})"
80
+ id="accordion-content-{{ $loop.index }}"
81
+ class="{{ contentClasses }}"
82
+ role="region"
83
+ >
84
+ {!! item.content !!}
85
+ </div>
86
+ </div>
87
+ @endforeach
88
+ </div>
@@ -25,10 +25,10 @@ export const audioClasses = 'w-full rounded-md bg-gray-100 dark:bg-gray-800'
25
25
  <audio
26
26
  class="{{ audioClasses }}"
27
27
  src="{{ src }}"
28
- controls="{{ controls }}"
29
- autoplay="{{ autoplay }}"
30
- loop="{{ loop }}"
31
- muted="{{ muted }}"
28
+ @if(controls)controls@endif
29
+ @if(autoplay)autoplay@endif
30
+ @if(loop)loop@endif
31
+ @if(muted)muted@endif
32
32
  preload="{{ preload }}"
33
33
  >
34
34
  Your browser does not support the audio element.
@@ -6,11 +6,7 @@ export const size = $props.size || 'md'
6
6
  export const shape = $props.shape || 'circle'
7
7
  export const status = $props.status || ''
8
8
  export const className = $props.className || ''
9
- export const onError = $props.onError
10
9
 
11
- let imageError = false
12
-
13
- // Size classes
14
10
  const sizeClasses = {
15
11
  xs: 'w-6 h-6 text-xs',
16
12
  sm: 'w-8 h-8 text-sm',
@@ -20,13 +16,8 @@ const sizeClasses = {
20
16
  '2xl': 'w-20 h-20 text-2xl',
21
17
  }
22
18
 
23
- // Shape classes
24
- const shapeClasses = {
25
- circle: 'rounded-full',
26
- square: 'rounded-md',
27
- }
19
+ const shapeClasses = { circle: 'rounded-full', square: 'rounded-md' }
28
20
 
29
- // Status indicator size classes
30
21
  const statusSizeClasses = {
31
22
  xs: 'w-1.5 h-1.5 border',
32
23
  sm: 'w-2 h-2 border',
@@ -36,7 +27,6 @@ const statusSizeClasses = {
36
27
  '2xl': 'w-4 h-4 border-2',
37
28
  }
38
29
 
39
- // Status color classes
40
30
  const statusColorClasses = {
41
31
  online: 'bg-green-500 dark:bg-green-400',
42
32
  offline: 'bg-gray-400 dark:bg-gray-600',
@@ -48,24 +38,33 @@ export const containerClasses = `relative inline-block ${className}`.trim()
48
38
  export const avatarClasses = `flex items-center justify-center overflow-hidden bg-gray-200 dark:bg-gray-700 ${sizeClasses[size]} ${shapeClasses[shape]}`.trim()
49
39
  export const imageClasses = `w-full h-full object-cover`.trim()
50
40
  export const initialsClasses = `font-medium text-gray-600 dark:text-gray-300 uppercase select-none`.trim()
51
- export const statusClasses = `absolute bottom-0 right-0 ${statusSizeClasses[size]} ${statusColorClasses[status]} ${shapeClasses[shape]} border-white dark:border-gray-900`.trim()
52
-
53
- // Generate initials from name if not provided
41
+ export const statusClasses = status ? `absolute bottom-0 right-0 ${statusSizeClasses[size]} ${statusColorClasses[status]} ${shapeClasses[shape]} border-white dark:border-gray-900`.trim() : ''
54
42
  export const displayInitials = initials || (alt ? alt.split(' ').map(n => n[0]).join('').substring(0, 2) : '?')
55
43
  </script>
56
44
 
45
+ <script client>
46
+ const emit = defineEmits()
47
+ const imageError = state(false)
48
+ const hasSrc = {{ !!src }}
49
+
50
+ function onImageError(event) {
51
+ imageError.set(true)
52
+ emit('error', event)
53
+ }
54
+
55
+ const showImage = derived(() => hasSrc && !imageError())
56
+ </script>
57
+
57
58
  <div class="{{ containerClasses }}">
58
59
  <div class="{{ avatarClasses }}">
59
- @if(src && !imageError)
60
- <img
61
- src="{{ src }}"
62
- alt="{{ alt }}"
63
- class="{{ imageClasses }}"
64
- onerror="handleImageError(event)"
65
- />
66
- @else
67
- <span class="{{ initialsClasses }}">{{ displayInitials }}</span>
68
- @endif
60
+ <img
61
+ :show="showImage()"
62
+ src="{{ src }}"
63
+ alt="{{ alt }}"
64
+ class="{{ imageClasses }}"
65
+ @error="onImageError($event)"
66
+ />
67
+ <span :show="!showImage()" class="{{ initialsClasses }}">{{ displayInitials }}</span>
69
68
  </div>
70
69
 
71
70
  @if(status)
@@ -76,13 +75,3 @@ export const displayInitials = initials || (alt ? alt.split(' ').map(n => n[0]).
76
75
  ></span>
77
76
  @endif
78
77
  </div>
79
-
80
- <script>
81
- function handleImageError(event) {
82
- imageError = true
83
-
84
- if (onError) {
85
- onError(event)
86
- }
87
- }
88
- </script>
@@ -4,9 +4,7 @@ export const size = $props.size || 'md'
4
4
  export const dot = $props.dot || false
5
5
  export const removable = $props.removable || false
6
6
  export const className = $props.className || ''
7
- export const onRemove = $props.onRemove
8
7
 
9
- // Variant classes
10
8
  const variantClasses = {
11
9
  default: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300',
12
10
  primary: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
@@ -17,14 +15,8 @@ const variantClasses = {
17
15
  info: 'bg-cyan-100 text-cyan-800 dark:bg-cyan-900 dark:text-cyan-200',
18
16
  }
19
17
 
20
- // Size classes
21
- const sizeClasses = {
22
- sm: 'px-2 py-0.5 text-xs',
23
- md: 'px-2.5 py-0.5 text-sm',
24
- lg: 'px-3 py-1 text-base',
25
- }
18
+ const sizeClasses = { sm: 'px-2 py-0.5 text-xs', md: 'px-2.5 py-0.5 text-sm', lg: 'px-3 py-1 text-base' }
26
19
 
27
- // Dot color classes
28
20
  const dotColorClasses = {
29
21
  default: 'bg-gray-500 dark:bg-gray-400',
30
22
  primary: 'bg-blue-500 dark:bg-blue-400',
@@ -40,6 +32,14 @@ export const dotClasses = `w-2 h-2 rounded-full ${dotColorClasses[variant]}`.tri
40
32
  export const removeButtonClasses = `ml-0.5 -mr-1 text-current hover:bg-black hover:bg-opacity-10 dark:hover:bg-white dark:hover:bg-opacity-10 rounded-full p-0.5 transition-colors duration-150`.trim()
41
33
  </script>
42
34
 
35
+ <script client>
36
+ const emit = defineEmits()
37
+
38
+ function onRemove() {
39
+ emit('remove')
40
+ }
41
+ </script>
42
+
43
43
  <span class="{{ badgeClasses }}" role="status">
44
44
  @if(dot)
45
45
  <span class="{{ dotClasses }}" aria-hidden="true"></span>
@@ -47,10 +47,10 @@ export const removeButtonClasses = `ml-0.5 -mr-1 text-current hover:bg-black hov
47
47
 
48
48
  <slot />
49
49
 
50
- @if(removable && onRemove)
50
+ @if(removable)
51
51
  <button
52
52
  type="button"
53
- @click="handleRemove()"
53
+ @click="onRemove()"
54
54
  class="{{ removeButtonClasses }}"
55
55
  aria-label="Remove"
56
56
  >
@@ -60,11 +60,3 @@ export const removeButtonClasses = `ml-0.5 -mr-1 text-current hover:bg-black hov
60
60
  </button>
61
61
  @endif
62
62
  </span>
63
-
64
- <script>
65
- function handleRemove() {
66
- if (onRemove) {
67
- onRemove()
68
- }
69
- }
70
- </script>
@@ -8,9 +8,7 @@ export const fullWidth = $props.fullWidth || false
8
8
  export const leftIcon = $props.leftIcon || ''
9
9
  export const rightIcon = $props.rightIcon || ''
10
10
  export const className = $props.className || ''
11
- export const onClick = $props.onClick
12
11
 
13
- // Variant styles using headwind
14
12
  const variantClasses = {
15
13
  primary: 'bg-blue-500 hover:bg-blue-600 text-white shadow-md hover:shadow-lg dark:bg-blue-600 dark:hover:bg-blue-700',
16
14
  secondary: 'bg-neutral-200 hover:bg-neutral-300 text-neutral-900 dark:bg-neutral-700 dark:hover:bg-neutral-600 dark:text-white',
@@ -19,7 +17,6 @@ const variantClasses = {
19
17
  danger: 'bg-red-500 hover:bg-red-600 text-white shadow-md hover:shadow-lg dark:bg-red-600 dark:hover:bg-red-700',
20
18
  }
21
19
 
22
- // Size styles using headwind
23
20
  const sizeClasses = {
24
21
  xs: 'px-2.5 py-1 text-xs h-7 gap-1',
25
22
  sm: 'px-3 py-1.5 text-sm h-8 gap-1.5',
@@ -28,62 +25,50 @@ const sizeClasses = {
28
25
  xl: 'px-8 py-4 text-xl h-14 gap-3',
29
26
  }
30
27
 
31
- // Icon size styles
32
- const iconSizeClasses = {
33
- xs: 'h-3 w-3',
34
- sm: 'h-4 w-4',
35
- md: 'h-5 w-5',
36
- lg: 'h-6 w-6',
37
- xl: 'h-7 w-7',
38
- }
39
-
40
- // Base classes
28
+ const iconSizeClasses = { xs: 'h-3 w-3', sm: 'h-4 w-4', md: 'h-5 w-5', lg: 'h-6 w-6', xl: 'h-7 w-7' }
41
29
  const baseClasses = 'inline-flex items-center justify-center font-semibold rounded-md transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none'
42
-
43
- // Width classes
44
30
  const widthClasses = fullWidth ? 'w-full' : ''
31
+ export const isDisabled = disabled || loading
45
32
 
46
- // Loading state disables interactions
47
- const isDisabled = disabled || loading
48
-
49
- // Combine classes
50
33
  export const buttonClasses = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${widthClasses} ${className}`.trim()
51
34
  export const iconClasses = iconSizeClasses[size]
52
35
  export const spinnerClasses = `animate-spin ${iconClasses}`.trim()
53
36
  </script>
54
37
 
38
+ <script client>
39
+ const emit = defineEmits()
40
+ const disabledNow = {{ isDisabled }}
41
+
42
+ function onClick(event) {
43
+ if (disabledNow) {
44
+ event.preventDefault()
45
+ return
46
+ }
47
+ emit('click', event)
48
+ }
49
+ </script>
50
+
55
51
  <button
56
52
  type="{{ type }}"
57
53
  class="{{ buttonClasses }}"
58
54
  @if(isDisabled)disabled@endif
59
- @if(onClick)@click="handleClick(event)"@endif
55
+ @click="onClick($event)"
60
56
  aria-busy="{{ loading ? 'true' : 'false' }}"
61
57
  >
62
58
  @if(loading)
63
- <!-- Loading spinner -->
64
59
  <svg class="{{ spinnerClasses }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
65
60
  <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
66
61
  <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
67
62
  </svg>
68
63
  @else
69
64
  @if(leftIcon)
70
- <!-- Left icon -->
71
- <span class="{{ iconClasses }}" innerHTML="{!! leftIcon !!}"></span>
65
+ <span class="{{ iconClasses }}">{!! leftIcon !!}</span>
72
66
  @endif
73
67
  @endif
74
68
 
75
69
  <slot />
76
70
 
77
71
  @if(!loading && rightIcon)
78
- <!-- Right icon -->
79
- <span class="{{ iconClasses }}" innerHTML="{!! rightIcon !!}"></span>
72
+ <span class="{{ iconClasses }}">{!! rightIcon !!}</span>
80
73
  @endif
81
74
  </button>
82
-
83
- <script>
84
- function handleClick(event) {
85
- if (!isDisabled && onClick) {
86
- onClick(event)
87
- }
88
- }
89
- </script>
@@ -0,0 +1,189 @@
1
+ <script server>
2
+ export const value = $props.value || null
3
+ export const minDate = $props.minDate || null
4
+ export const maxDate = $props.maxDate || null
5
+ export const disabledDates = $props.disabledDates || []
6
+ export const locale = $props.locale || 'en-US'
7
+ export const firstDayOfWeek = $props.firstDayOfWeek || 0
8
+ export const showToday = $props.showToday !== false
9
+ export const className = $props.className || ''
10
+ export const highlightToday = $props.highlightToday !== false
11
+
12
+ const baseClasses = 'bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4 w-full max-w-sm'
13
+ export const calendarClasses = `${baseClasses} ${className}`.trim()
14
+ export const headerClasses = 'flex items-center justify-between mb-4'
15
+ export const monthYearClasses = 'text-lg font-semibold text-gray-900 dark:text-gray-100'
16
+ export const buttonClasses = 'p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors'
17
+ export const weekdaysClasses = 'grid grid-cols-7 gap-1 mb-2'
18
+ export const weekdayClasses = 'text-center text-xs font-medium text-gray-600 dark:text-gray-400 py-2'
19
+ export const daysGridClasses = 'grid grid-cols-7 gap-1'
20
+
21
+ const valueIso = value ? new Date(value).toISOString() : null
22
+ export const initialValueIso = valueIso
23
+ const minIso = minDate ? new Date(minDate).toISOString() : null
24
+ const maxIso = maxDate ? new Date(maxDate).toISOString() : null
25
+ export const minIsoValue = minIso
26
+ export const maxIsoValue = maxIso
27
+ export const disabledIsoList = disabledDates.map(d => new Date(d).toISOString())
28
+ </script>
29
+
30
+ <script client>
31
+ const emit = defineEmits()
32
+ const locale = {{ locale }}
33
+ const firstDayOfWeek = {{ firstDayOfWeek }}
34
+ const highlightToday = {{ highlightToday }}
35
+ const minDate = {{ minIsoValue === null ? 'null' : minIsoValue }} ? new Date({{ minIsoValue === null ? 'null' : minIsoValue }}) : null
36
+ const maxDate = {{ maxIsoValue === null ? 'null' : maxIsoValue }} ? new Date({{ maxIsoValue === null ? 'null' : maxIsoValue }}) : null
37
+ const disabledDates = ({{ disabledIsoList }}).map(s => new Date(s))
38
+ const initialValue = {{ initialValueIso === null ? 'null' : initialValueIso }} ? new Date({{ initialValueIso === null ? 'null' : initialValueIso }}) : null
39
+
40
+ const today = new Date()
41
+ const currentMonth = state(initialValue ? new Date(initialValue) : new Date())
42
+ const selectedDate = state(initialValue)
43
+
44
+ function isSameDay(d1, d2) {
45
+ if (!d1 || !d2) return false
46
+ return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate()
47
+ }
48
+
49
+ function isToday(d) { return isSameDay(d, today) }
50
+
51
+ function isDisabled(d) {
52
+ if (minDate && d < minDate) return true
53
+ if (maxDate && d > maxDate) return true
54
+ return disabledDates.some(x => isSameDay(x, d))
55
+ }
56
+
57
+ function getDaysInMonth(d) { return new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate() }
58
+ function getFirstDayOfMonth(d) { return new Date(d.getFullYear(), d.getMonth(), 1).getDay() }
59
+
60
+ const monthYearLabel = derived(() => {
61
+ const m = currentMonth()
62
+ return `${m.toLocaleDateString(locale, { month: 'long' })} ${m.getFullYear()}`
63
+ })
64
+
65
+ const dayNames = (() => {
66
+ const days = []
67
+ const baseDate = new Date(2024, 0, firstDayOfWeek)
68
+ for (let i = 0; i < 7; i++) {
69
+ const day = new Date(baseDate)
70
+ day.setDate(baseDate.getDate() + i)
71
+ days.push(day.toLocaleDateString(locale, { weekday: 'short' }))
72
+ }
73
+ return days
74
+ })()
75
+
76
+ const calendarDays = derived(() => {
77
+ const month = currentMonth()
78
+ const daysInMonth = getDaysInMonth(month)
79
+ const firstDay = (getFirstDayOfMonth(month) - firstDayOfWeek + 7) % 7
80
+ const days = []
81
+ const prev = new Date(month.getFullYear(), month.getMonth() - 1, 1)
82
+ const daysInPrev = getDaysInMonth(prev)
83
+ for (let i = firstDay - 1; i >= 0; i--) {
84
+ days.push({ date: new Date(prev.getFullYear(), prev.getMonth(), daysInPrev - i), isCurrentMonth: false })
85
+ }
86
+ for (let i = 1; i <= daysInMonth; i++) {
87
+ days.push({ date: new Date(month.getFullYear(), month.getMonth(), i), isCurrentMonth: true })
88
+ }
89
+ const remaining = 42 - days.length
90
+ const next = new Date(month.getFullYear(), month.getMonth() + 1, 1)
91
+ for (let i = 1; i <= remaining; i++) {
92
+ days.push({ date: new Date(next.getFullYear(), next.getMonth(), i), isCurrentMonth: false })
93
+ }
94
+ return days
95
+ })
96
+
97
+ function previousMonth() {
98
+ const m = currentMonth()
99
+ currentMonth.set(new Date(m.getFullYear(), m.getMonth() - 1, 1))
100
+ }
101
+
102
+ function nextMonthFn() {
103
+ const m = currentMonth()
104
+ currentMonth.set(new Date(m.getFullYear(), m.getMonth() + 1, 1))
105
+ }
106
+
107
+ function selectDate(date) {
108
+ if (isDisabled(date)) return
109
+ selectedDate.set(date)
110
+ emit('change', date)
111
+ }
112
+
113
+ function goToToday() {
114
+ currentMonth.set(new Date(today))
115
+ selectDate(today)
116
+ }
117
+
118
+ function dayClasses(day) {
119
+ const base = 'flex items-center justify-center h-10 w-10 rounded-md text-sm transition-colors'
120
+ const isSelected = isSameDay(day.date, selectedDate())
121
+ const isTodayDate = isToday(day.date)
122
+ const disabled = isDisabled(day.date)
123
+ let cls = base
124
+ cls += day.isCurrentMonth ? ' text-gray-900 dark:text-gray-100' : ' text-gray-400 dark:text-gray-600'
125
+ cls += disabled ? ' opacity-50 cursor-not-allowed' : ' cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700'
126
+ if (isSelected) cls += ' bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600'
127
+ if (isTodayDate && highlightToday && !isSelected) cls += ' border-2 border-blue-600 dark:border-blue-400'
128
+ return cls
129
+ }
130
+
131
+ defineExpose({ selectedDate, currentMonth, selectDate })
132
+ </script>
133
+
134
+ <div class="{{ calendarClasses }}">
135
+ <div class="{{ headerClasses }}">
136
+ <button
137
+ type="button"
138
+ class="{{ buttonClasses }}"
139
+ @click="previousMonth()"
140
+ aria-label="Previous month"
141
+ >
142
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
143
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
144
+ </svg>
145
+ </button>
146
+
147
+ <div class="{{ monthYearClasses }}" x-text="monthYearLabel()"></div>
148
+
149
+ <button
150
+ type="button"
151
+ class="{{ buttonClasses }}"
152
+ @click="nextMonthFn()"
153
+ aria-label="Next month"
154
+ >
155
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
156
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
157
+ </svg>
158
+ </button>
159
+ </div>
160
+
161
+ <div class="{{ weekdaysClasses }}">
162
+ <div :for="d in dayNames" :key="d" class="{{ weekdayClasses }}" x-text="d"></div>
163
+ </div>
164
+
165
+ <div class="{{ daysGridClasses }}">
166
+ <button
167
+ :for="day in calendarDays()"
168
+ :key="day.date.toISOString()"
169
+ type="button"
170
+ :class="dayClasses(day)"
171
+ @click="selectDate(day.date)"
172
+ :aria-label="day.date.toLocaleDateString(locale)"
173
+ :disabled="isDisabled(day.date)"
174
+ x-text="day.date.getDate()"
175
+ ></button>
176
+ </div>
177
+
178
+ @if(showToday)
179
+ <div class="mt-4 flex justify-center">
180
+ <button
181
+ type="button"
182
+ class="px-4 py-2 text-sm font-medium text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-gray-700 rounded-md transition-colors"
183
+ @click="goToToday()"
184
+ >
185
+ Today
186
+ </button>
187
+ </div>
188
+ @endif
189
+ </div>
@@ -5,11 +5,8 @@ export const hover = $props.hover || false
5
5
  export const clickable = $props.clickable || false
6
6
  export const image = $props.image || ''
7
7
  export const imageAlt = $props.imageAlt || ''
8
- export const imagePosition = $props.imagePosition || 'top'
9
8
  export const className = $props.className || ''
10
- export const onClick = $props.onClick
11
9
 
12
- // Variant classes
13
10
  const variantClasses = {
14
11
  default: 'bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700',
15
12
  outlined: 'bg-transparent border-2 border-gray-300 dark:border-gray-600',
@@ -17,18 +14,8 @@ const variantClasses = {
17
14
  flat: 'bg-gray-50 dark:bg-gray-900 border-0',
18
15
  }
19
16
 
20
- // Padding classes
21
- const paddingClasses = {
22
- none: 'p-0',
23
- sm: 'p-3',
24
- default: 'p-6',
25
- lg: 'p-8',
26
- }
27
-
28
- // Hover effect classes
17
+ const paddingClasses = { none: 'p-0', sm: 'p-3', default: 'p-6', lg: 'p-8' }
29
18
  const hoverClasses = hover ? 'transition-all duration-200 hover:shadow-lg hover:-translate-y-0.5' : ''
30
-
31
- // Clickable classes
32
19
  const clickableClasses = clickable ? 'cursor-pointer' : ''
33
20
 
34
21
  export const cardClasses = `rounded-lg overflow-hidden ${variantClasses[variant]} ${paddingClasses[image ? 'none' : padding]} ${hoverClasses} ${clickableClasses} ${className}`.trim()
@@ -36,12 +23,35 @@ export const imageClasses = `w-full h-48 object-cover`.trim()
36
23
  export const contentClasses = `${image ? paddingClasses[padding] : ''}`.trim()
37
24
  </script>
38
25
 
39
- @if(onClick)
40
- <div class="{{ cardClasses }}" @click="handleClick(event)" role="button" tabindex="0" onkeydown="handleKeyDown(event)">
26
+ <script client>
27
+ const emit = defineEmits()
28
+ const isClickable = {{ clickable }}
29
+
30
+ function onClick(event) {
31
+ if (!isClickable) return
32
+ emit('click', event)
33
+ }
34
+
35
+ function onKey(event) {
36
+ if (!isClickable) return
37
+ if (event.key === 'Enter' || event.key === ' ') {
38
+ event.preventDefault()
39
+ emit('click', event)
40
+ }
41
+ }
42
+ </script>
43
+
44
+ @if(clickable)
45
+ <div
46
+ class="{{ cardClasses }}"
47
+ @click="onClick($event)"
48
+ @keydown="onKey($event)"
49
+ role="button"
50
+ tabindex="0"
51
+ >
41
52
  @if(image)
42
53
  <img src="{{ image }}" alt="{{ imageAlt }}" class="{{ imageClasses }}" />
43
54
  @endif
44
-
45
55
  <div class="{{ contentClasses }}">
46
56
  <slot />
47
57
  </div>
@@ -51,24 +61,8 @@ export const contentClasses = `${image ? paddingClasses[padding] : ''}`.trim()
51
61
  @if(image)
52
62
  <img src="{{ image }}" alt="{{ imageAlt }}" class="{{ imageClasses }}" />
53
63
  @endif
54
-
55
64
  <div class="{{ contentClasses }}">
56
65
  <slot />
57
66
  </div>
58
67
  </div>
59
68
  @endif
60
-
61
- <script>
62
- function handleClick(event) {
63
- if (clickable && onClick) {
64
- onClick(event)
65
- }
66
- }
67
-
68
- function handleKeyDown(event) {
69
- if ((event.key === 'Enter' || event.key === ' ') && clickable && onClick) {
70
- event.preventDefault()
71
- onClick(event)
72
- }
73
- }
74
- </script>