@stacksjs/components 0.2.35 → 0.2.54

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 +5 -5
  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,82 @@
1
+ <script server>
2
+ export const className = $props.className || ''
3
+ export const as = $props.as || 'div'
4
+
5
+ const baseClasses = 'relative inline-block text-left'
6
+ export const dropdownClasses = `${baseClasses} ${className}`.trim()
7
+ export const dropdownInstanceId = `stx-dd-${Math.random().toString(36).slice(2, 11)}`
8
+ </script>
9
+
10
+ <script client>
11
+ const emit = defineEmits()
12
+ const dropdownInstanceId = {{ dropdownInstanceId }}
13
+ const isOpen = state(false)
14
+ let rootEl = null
15
+ let outsideHandler = null
16
+
17
+ function toggle() {
18
+ isOpen.set(!isOpen())
19
+ emit('change', isOpen())
20
+ }
21
+
22
+ function open() {
23
+ if (isOpen()) return
24
+ isOpen.set(true)
25
+ emit('change', true)
26
+ }
27
+
28
+ function close() {
29
+ if (!isOpen()) return
30
+ isOpen.set(false)
31
+ emit('change', false)
32
+ }
33
+
34
+ function onItemSelect(event) {
35
+ event.stopPropagation()
36
+ emit('select', event.detail)
37
+ close()
38
+ }
39
+
40
+ function onToggle(event) { event.stopPropagation(); toggle() }
41
+ function onOpen(event) { event.stopPropagation(); open() }
42
+ function onClose(event) { event.stopPropagation(); close() }
43
+
44
+ onMount(() => {
45
+ rootEl = document.querySelector(`[data-dropdown-id="${dropdownInstanceId}"]`)
46
+ outsideHandler = (event) => {
47
+ if (!isOpen()) return
48
+ if (rootEl && !rootEl.contains(event.target)) close()
49
+ }
50
+ document.addEventListener('click', outsideHandler, true)
51
+ })
52
+
53
+ onDestroy(() => {
54
+ if (outsideHandler) document.removeEventListener('click', outsideHandler, true)
55
+ })
56
+
57
+ const dataState = derived(() => isOpen() ? 'open' : 'closed')
58
+ const dataExpanded = derived(() => isOpen() ? 'true' : 'false')
59
+
60
+ defineExpose({ isOpen, toggle, open, close })
61
+ </script>
62
+
63
+ <component
64
+ :is="{{ as }}"
65
+ class="{{ dropdownClasses }}"
66
+ data-stx-dropdown
67
+ data-dropdown-id="{{ dropdownInstanceId }}"
68
+ :data-state="dataState()"
69
+ :data-expanded="dataExpanded()"
70
+ @stx:dropdown-toggle="onToggle($event)"
71
+ @stx:dropdown-open="onOpen($event)"
72
+ @stx:dropdown-close="onClose($event)"
73
+ @stx:dropdown-select="onItemSelect($event)"
74
+ >
75
+ <slot />
76
+ </component>
77
+
78
+ <style>
79
+ [data-stx-dropdown][data-state="closed"] [data-stx-dropdown-items] {
80
+ display: none;
81
+ }
82
+ </style>
@@ -2,22 +2,28 @@
2
2
  export const className = $props.className || ''
3
3
  export const as = $props.as || 'button'
4
4
  export const disabled = $props.disabled || false
5
- export const onClick = $props.onClick
6
5
 
7
6
  const baseClasses = 'inline-flex justify-center items-center rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75'
8
- const buttonClasses = `${baseClasses} ${className}`.trim()
7
+ export const buttonClasses = `${baseClasses} ${className}`.trim()
8
+ </script>
9
+
10
+ <script client>
11
+ const emit = defineEmits()
12
+ const isDisabled = {{ disabled }}
9
13
 
10
- export function handleClick(e) {
11
- if (disabled) return
12
- onClick?.(e)
14
+ function onClick(event) {
15
+ if (isDisabled) return
16
+ emit('click', event)
17
+ event.currentTarget.dispatchEvent(new CustomEvent('stx:dropdown-toggle', { bubbles: true }))
13
18
  }
14
19
  </script>
15
20
 
16
21
  <component
17
22
  :is="{{ as }}"
18
23
  class="{{ buttonClasses }}"
24
+ data-stx-dropdown-button
19
25
  @if(disabled)disabled aria-disabled="true"@endif
20
- @click="handleClick"
26
+ @click="onClick($event)"
21
27
  >
22
28
  <slot />
23
29
  </component>
@@ -0,0 +1,35 @@
1
+ <script server>
2
+ export const className = $props.className || ''
3
+ export const as = $props.as || 'div'
4
+ export const disabled = $props.disabled || false
5
+ export const value = $props.value
6
+
7
+ const baseClasses = 'group flex w-full items-center px-2 py-2 text-sm transition-colors text-neutral-900 dark:text-neutral-100 hover:bg-blue-500 hover:text-white dark:hover:bg-blue-600'
8
+ const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'
9
+ export const itemClasses = `${baseClasses} ${disabledClasses} ${className}`.trim()
10
+ </script>
11
+
12
+ <script client>
13
+ const emit = defineEmits()
14
+ const isDisabled = {{ disabled }}
15
+ const itemValue = {{ value === undefined ? 'null' : value }}
16
+
17
+ function onClick(event) {
18
+ if (isDisabled) return
19
+ emit('click', event)
20
+ event.currentTarget.dispatchEvent(
21
+ new CustomEvent('stx:dropdown-select', { bubbles: true, detail: itemValue }),
22
+ )
23
+ }
24
+ </script>
25
+
26
+ <component
27
+ :is="{{ as }}"
28
+ class="{{ itemClasses }}"
29
+ data-stx-dropdown-item
30
+ role="menuitem"
31
+ @if(disabled)aria-disabled="true"@endif
32
+ @click="onClick($event)"
33
+ >
34
+ <slot :disabled="disabled" />
35
+ </component>
@@ -1,15 +1,15 @@
1
1
  <script server>
2
2
  export const className = $props.className || ''
3
3
  export const as = $props.as || 'div'
4
- export const static_ = $props.static || false
5
4
 
6
5
  const baseClasses = 'absolute z-10 mt-2 origin-top-right divide-y divide-neutral-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-neutral-800 dark:divide-neutral-700 dark:ring-neutral-600'
7
- const itemsClasses = `${baseClasses} ${className}`.trim()
6
+ export const itemsClasses = `${baseClasses} ${className}`.trim()
8
7
  </script>
9
8
 
10
9
  <component
11
10
  :is="{{ as }}"
12
11
  class="{{ itemsClasses }}"
12
+ data-stx-dropdown-items
13
13
  role="menu"
14
14
  aria-orientation="vertical"
15
15
  tabindex="-1"
@@ -0,0 +1,30 @@
1
+ <script server>
2
+ export const value = $props.value || ''
3
+ export const placeholder = $props.placeholder || 'Enter your email'
4
+ export const disabled = $props.disabled || false
5
+ export const readonly = $props.readonly || false
6
+ export const size = $props.size || 'md'
7
+ export const error = $props.error || false
8
+ export const label = $props.label || ''
9
+ export const helperText = $props.helperText || ''
10
+ export const required = $props.required || false
11
+ export const className = $props.className || ''
12
+
13
+ export const emailIcon = '<svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></svg>'
14
+ </script>
15
+
16
+ <TextInput
17
+ :value="value"
18
+ :placeholder="placeholder"
19
+ :disabled="disabled"
20
+ :readonly="readonly"
21
+ :size="size"
22
+ :error="error"
23
+ :label="label"
24
+ :helperText="helperText"
25
+ :required="required"
26
+ :leftIcon="emailIcon"
27
+ :clearable="true"
28
+ :className="className"
29
+ inputClassName="font-mono"
30
+ />
@@ -0,0 +1,95 @@
1
+ <script server>
2
+ export const action = $props.action || ''
3
+ export const method = $props.method || 'POST'
4
+ export const validationSchema = $props.validationSchema || {}
5
+ export const className = $props.className || ''
6
+
7
+ export const formClasses = `space-y-6 ${className}`.trim()
8
+ export const errorClasses = `mt-1 text-sm text-red-600 dark:text-red-400`.trim()
9
+ </script>
10
+
11
+ <script client>
12
+ const emit = defineEmits()
13
+ const validationSchema = {{ validationSchema }}
14
+
15
+ const errors = state({})
16
+ const isSubmitting = state(false)
17
+ const submitCount = state(0)
18
+
19
+ function validateField(name, value, allValues) {
20
+ const schema = validationSchema[name]
21
+ if (!schema) return null
22
+ if (schema.required && !value) {
23
+ return schema.requiredMessage || `${name} is required`
24
+ }
25
+ if (schema.minLength && String(value).length < schema.minLength) {
26
+ return schema.minLengthMessage || `${name} must be at least ${schema.minLength} characters`
27
+ }
28
+ if (schema.maxLength && String(value).length > schema.maxLength) {
29
+ return schema.maxLengthMessage || `${name} must be at most ${schema.maxLength} characters`
30
+ }
31
+ if (schema.pattern && !new RegExp(schema.pattern).test(value)) {
32
+ return schema.patternMessage || `${name} is invalid`
33
+ }
34
+ return null
35
+ }
36
+
37
+ function validateForm(values) {
38
+ const next = {}
39
+ let valid = true
40
+ for (const name of Object.keys(validationSchema)) {
41
+ const err = validateField(name, values[name], values)
42
+ if (err) {
43
+ next[name] = err
44
+ valid = false
45
+ }
46
+ }
47
+ errors.set(next)
48
+ return valid
49
+ }
50
+
51
+ async function onSubmit(event) {
52
+ event.preventDefault()
53
+ submitCount.set(submitCount() + 1)
54
+
55
+ const formData = new FormData(event.target)
56
+ const values = {}
57
+ for (const [k, v] of formData.entries()) values[k] = v
58
+
59
+ if (!validateForm(values)) {
60
+ emit('error', errors())
61
+ return
62
+ }
63
+
64
+ isSubmitting.set(true)
65
+ try {
66
+ emit('submit', values)
67
+ }
68
+ finally {
69
+ isSubmitting.set(false)
70
+ }
71
+ }
72
+
73
+ function setErrors(next) {
74
+ errors.set({ ...errors(), ...next })
75
+ }
76
+
77
+ function reset(form) {
78
+ errors.set({})
79
+ isSubmitting.set(false)
80
+ submitCount.set(0)
81
+ if (form?.reset) form.reset()
82
+ }
83
+
84
+ defineExpose({ errors, isSubmitting, submitCount, setErrors, reset, validateForm })
85
+ </script>
86
+
87
+ <form
88
+ class="{{ formClasses }}"
89
+ @if(action)action="{{ action }}"@endif
90
+ method="{{ method }}"
91
+ @submit="onSubmit($event)"
92
+ novalidate
93
+ >
94
+ <slot />
95
+ </form>
@@ -10,18 +10,14 @@ export const height = $props.height
10
10
  export const lazy = $props.lazy ?? true
11
11
  export const placeholder = $props.placeholder || 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300"%3E%3Crect fill="%23e5e7eb" width="400" height="300"/%3E%3C/svg%3E'
12
12
  export const blurDataURL = $props.blurDataURL || ''
13
- export const onLoad = $props.onLoad
14
- export const onError = $props.onError
15
13
  export const aspectRatio = $props.aspectRatio
16
14
  export const objectFit = $props.objectFit || 'cover'
17
15
  export const rounded = $props.rounded ?? false
18
16
  export const zoomOnHover = $props.zoomOnHover || false
19
17
  export const className = $props.className || ''
20
18
 
21
- // Has WebP support
22
- export const hasWebP = webpSrc || webpSrcSet
19
+ export const hasWebP = !!(webpSrc || webpSrcSet)
23
20
 
24
- // Object fit classes
25
21
  const objectFitClasses = {
26
22
  contain: 'object-contain',
27
23
  cover: 'object-cover',
@@ -30,23 +26,69 @@ const objectFitClasses = {
30
26
  'scale-down': 'object-scale-down',
31
27
  }
32
28
 
33
- // Rounded classes
34
29
  const roundedClass = rounded === true ? 'rounded-lg' : rounded === 'full' ? 'rounded-full' : rounded ? `rounded-${rounded}` : ''
35
-
36
30
  const zoomClasses = zoomOnHover ? 'transform hover:scale-110 transition-transform duration-300 ease-in-out cursor-zoom-in' : ''
37
31
 
38
32
  export const containerClasses = `relative overflow-hidden ${roundedClass} ${className}`.trim()
39
33
  export const imageClasses = `w-full h-full transition-opacity duration-300 ${objectFitClasses[objectFit]} ${zoomClasses}`.trim()
40
- export const placeholderClasses = `absolute inset-0 bg-gray-200 dark:bg-gray-700 ${roundedClass}`.trim()
41
34
  export const blurPlaceholderClasses = `absolute inset-0 ${roundedClass} filter blur-lg scale-110`.trim()
35
+ export const containerStyle = aspectRatio ? `padding-bottom: ${(1 / aspectRatio) * 100}%` : ''
36
+ </script>
37
+
38
+ <script client>
39
+ const emit = defineEmits()
40
+ const lazy = {{ lazy }}
41
+ const realSrc = {{ src }}
42
+ const realSrcSet = {{ srcSet }}
43
+ const errorPlaceholder = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300"%3E%3Crect fill="%23e5e7eb" width="400" height="300"/%3E%3Ctext x="50%25" y="50%25" fill="%239ca3af" font-size="16" text-anchor="middle" dominant-baseline="middle"%3EImage not found%3C/text%3E%3C/svg%3E'
44
+
45
+ function onImageLoad(event) {
46
+ const img = event.target
47
+ img.classList.remove('opacity-0')
48
+ img.classList.add('opacity-100')
49
+ emit('load', event)
50
+ }
42
51
 
43
- // Generate aspect ratio padding if provided
44
- export const paddingBottom = aspectRatio ? `${(1 / aspectRatio) * 100}%` : undefined
52
+ function onImageError(event) {
53
+ const img = event.target
54
+ img.src = errorPlaceholder
55
+ emit('error', event)
56
+ }
57
+
58
+ onMount(() => {
59
+ if (!lazy) return
60
+ if (typeof IntersectionObserver === 'undefined') return
61
+ const lazyImages = document.querySelectorAll('.lazy-image:not([data-stx-img-loaded])')
62
+ const observer = new IntersectionObserver((entries, obs) => {
63
+ for (const entry of entries) {
64
+ if (!entry.isIntersecting) continue
65
+ const img = entry.target
66
+ img.setAttribute('data-stx-img-loaded', '1')
67
+ const dataSrc = img.getAttribute('data-src')
68
+ const dataSrcSet = img.getAttribute('data-srcset')
69
+ if (!dataSrc) continue
70
+ const probe = new Image()
71
+ probe.onload = () => {
72
+ img.src = dataSrc
73
+ if (dataSrcSet) img.srcset = dataSrcSet
74
+ img.classList.remove('opacity-0')
75
+ img.classList.add('opacity-100')
76
+ obs.unobserve(img)
77
+ }
78
+ probe.onerror = () => {
79
+ onImageError({ target: img })
80
+ obs.unobserve(img)
81
+ }
82
+ probe.src = dataSrc
83
+ if (dataSrcSet) probe.srcset = dataSrcSet
84
+ }
85
+ }, { rootMargin: '50px' })
86
+ for (const img of lazyImages) observer.observe(img)
87
+ })
45
88
  </script>
46
89
 
47
- <div class="{{ containerClasses }}" style="{{ paddingBottom ? `padding-bottom: ${paddingBottom}` : '' }}">
90
+ <div class="{{ containerClasses }}" style="{{ containerStyle }}">
48
91
  @if(blurDataURL)
49
- <!-- Blur placeholder -->
50
92
  <img
51
93
  src="{{ blurDataURL }}"
52
94
  alt=""
@@ -56,17 +98,13 @@ export const paddingBottom = aspectRatio ? `${(1 / aspectRatio) * 100}%` : undef
56
98
  @endif
57
99
 
58
100
  @if(hasWebP)
59
- <!-- Picture element with WebP support -->
60
101
  <picture>
61
- @if(webpSrcSet || webpSrc)
62
- <source
63
- type="image/webp"
64
- @if(webpSrcSet)srcset="{{ webpSrcSet }}"@endif
65
- @if(webpSrc && !webpSrcSet)srcset="{{ webpSrc }}"@endif
66
- @if(sizes)sizes="{{ sizes }}"@endif
67
- />
68
- @endif
69
-
102
+ <source
103
+ type="image/webp"
104
+ @if(webpSrcSet)srcset="{{ webpSrcSet }}"@endif
105
+ @if(webpSrc && !webpSrcSet)srcset="{{ webpSrc }}"@endif
106
+ @if(sizes)sizes="{{ sizes }}"@endif
107
+ />
70
108
  @if(lazy)
71
109
  <img
72
110
  src="{{ placeholder }}"
@@ -78,8 +116,8 @@ export const paddingBottom = aspectRatio ? `${(1 / aspectRatio) * 100}%` : undef
78
116
  width="{{ width }}"
79
117
  height="{{ height }}"
80
118
  loading="lazy"
81
- onload="handleImageLoad(event)"
82
- onerror="handleImageError(event)"
119
+ @load="onImageLoad($event)"
120
+ @error="onImageError($event)"
83
121
  />
84
122
  @else
85
123
  <img
@@ -90,13 +128,12 @@ export const paddingBottom = aspectRatio ? `${(1 / aspectRatio) * 100}%` : undef
90
128
  class="{{ imageClasses }}"
91
129
  width="{{ width }}"
92
130
  height="{{ height }}"
93
- onload="handleImageLoad(event)"
94
- onerror="handleImageError(event)"
131
+ @load="onImageLoad($event)"
132
+ @error="onImageError($event)"
95
133
  />
96
134
  @endif
97
135
  </picture>
98
136
  @else
99
- <!-- Regular img element -->
100
137
  @if(lazy)
101
138
  <img
102
139
  src="{{ placeholder }}"
@@ -108,8 +145,8 @@ export const paddingBottom = aspectRatio ? `${(1 / aspectRatio) * 100}%` : undef
108
145
  width="{{ width }}"
109
146
  height="{{ height }}"
110
147
  loading="lazy"
111
- onload="handleImageLoad(event)"
112
- onerror="handleImageError(event)"
148
+ @load="onImageLoad($event)"
149
+ @error="onImageError($event)"
113
150
  />
114
151
  @else
115
152
  <img
@@ -120,78 +157,9 @@ export const paddingBottom = aspectRatio ? `${(1 / aspectRatio) * 100}%` : undef
120
157
  class="{{ imageClasses }}"
121
158
  width="{{ width }}"
122
159
  height="{{ height }}"
123
- onload="handleImageLoad(event)"
124
- onerror="handleImageError(event)"
160
+ @load="onImageLoad($event)"
161
+ @error="onImageError($event)"
125
162
  />
126
163
  @endif
127
164
  @endif
128
-
129
- <!-- Loading skeleton -->
130
- <div class="{{ placeholderClasses }} skeleton" style="display: none;">
131
- <div class="animate-pulse w-full h-full bg-gray-300 dark:bg-gray-600"></div>
132
- </div>
133
165
  </div>
134
-
135
- <script>
136
- // Intersection Observer for lazy loading
137
- if (lazy && typeof window !== 'undefined' && 'IntersectionObserver' in window) {
138
- const lazyImages = document.querySelectorAll('.lazy-image')
139
-
140
- const imageObserver = new IntersectionObserver((entries, observer) => {
141
- entries.forEach(entry => {
142
- if (entry.isIntersecting) {
143
- const img = entry.target
144
- const realSrc = img.getAttribute('data-src')
145
- const realSrcSet = img.getAttribute('data-srcset')
146
-
147
- if (realSrc) {
148
- // Load the real image
149
- const tempImg = new Image()
150
- tempImg.onload = () => {
151
- img.src = realSrc
152
- if (realSrcSet) {
153
- img.srcset = realSrcSet
154
- }
155
- img.classList.remove('opacity-0')
156
- img.classList.add('opacity-100')
157
- observer.unobserve(img)
158
- }
159
- tempImg.onerror = () => {
160
- handleImageError({ target: img })
161
- observer.unobserve(img)
162
- }
163
- tempImg.src = realSrc
164
- if (realSrcSet) {
165
- tempImg.srcset = realSrcSet
166
- }
167
- }
168
- }
169
- })
170
- }, {
171
- rootMargin: '50px', // Start loading 50px before entering viewport
172
- })
173
-
174
- lazyImages.forEach(img => imageObserver.observe(img))
175
- }
176
-
177
- function handleImageLoad(event) {
178
- const img = event.target
179
- img.classList.remove('opacity-0')
180
- img.classList.add('opacity-100')
181
-
182
- if (onLoad) {
183
- onLoad(event)
184
- }
185
- }
186
-
187
- function handleImageError(event) {
188
- const img = event.target
189
-
190
- // Show a broken image placeholder
191
- img.src = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300"%3E%3Crect fill="%23e5e7eb" width="400" height="300"/%3E%3Ctext x="50%25" y="50%25" fill="%239ca3af" font-size="16" text-anchor="middle" dominant-baseline="middle"%3EImage not found%3C/text%3E%3C/svg%3E'
192
-
193
- if (onError) {
194
- onError(event)
195
- }
196
- }
197
- </script>
@@ -0,0 +1,101 @@
1
+ <script server>
2
+ export const value = $props.value
3
+ export const multiple = $props.multiple || false
4
+ export const className = $props.className || ''
5
+ export const as = $props.as || 'div'
6
+
7
+ const baseClasses = 'relative'
8
+ export const listboxClasses = `${baseClasses} ${className}`.trim()
9
+ export const listboxInstanceId = `stx-lb-${Math.random().toString(36).slice(2, 11)}`
10
+ </script>
11
+
12
+ <script client>
13
+ const emit = defineEmits()
14
+ const listboxInstanceId = {{ listboxInstanceId }}
15
+ const multiple = {{ multiple }}
16
+ const initialValue = {{ value === undefined ? 'null' : value }}
17
+
18
+ const isOpen = state(false)
19
+ const selected = state(initialValue)
20
+ let rootEl = null
21
+ let outsideHandler = null
22
+
23
+ function isSelectedValue(v) {
24
+ const cur = selected()
25
+ if (multiple && Array.isArray(cur)) return cur.includes(v)
26
+ return cur === v
27
+ }
28
+
29
+ function selectValue(v) {
30
+ if (multiple) {
31
+ const cur = Array.isArray(selected()) ? selected() : []
32
+ selected.set(cur.includes(v) ? cur.filter(x => x !== v) : [...cur, v])
33
+ }
34
+ else {
35
+ selected.set(v)
36
+ isOpen.set(false)
37
+ }
38
+ syncOptionStates()
39
+ emit('change', selected())
40
+ }
41
+
42
+ function syncOptionStates() {
43
+ if (!rootEl) return
44
+ const options = rootEl.querySelectorAll('[data-stx-listbox-option]')
45
+ for (const opt of options) {
46
+ const raw = opt.dataset.optionValue
47
+ let v
48
+ try { v = JSON.parse(raw ?? 'null') }
49
+ catch { v = raw }
50
+ const sel = isSelectedValue(v)
51
+ opt.setAttribute('aria-selected', sel ? 'true' : 'false')
52
+ opt.setAttribute('data-selected', sel ? 'true' : 'false')
53
+ }
54
+ }
55
+
56
+ function open() { if (!isOpen()) { isOpen.set(true); emit('change', selected()) } }
57
+ function close() { if (isOpen()) { isOpen.set(false) } }
58
+ function toggle() { isOpen() ? close() : open() }
59
+
60
+ function onToggle(event) { event.stopPropagation(); toggle() }
61
+ function onSelect(event) {
62
+ event.stopPropagation()
63
+ selectValue(event.detail)
64
+ }
65
+
66
+ onMount(() => {
67
+ rootEl = document.querySelector(`[data-listbox-id="${listboxInstanceId}"]`)
68
+ syncOptionStates()
69
+ outsideHandler = (event) => {
70
+ if (!isOpen()) return
71
+ if (rootEl && !rootEl.contains(event.target)) close()
72
+ }
73
+ document.addEventListener('click', outsideHandler, true)
74
+ })
75
+
76
+ onDestroy(() => {
77
+ if (outsideHandler) document.removeEventListener('click', outsideHandler, true)
78
+ })
79
+
80
+ const dataState = derived(() => isOpen() ? 'open' : 'closed')
81
+
82
+ defineExpose({ isOpen, selected, toggle, open, close, selectValue })
83
+ </script>
84
+
85
+ <component
86
+ :is="{{ as }}"
87
+ class="{{ listboxClasses }}"
88
+ data-stx-listbox
89
+ data-listbox-id="{{ listboxInstanceId }}"
90
+ :data-state="dataState()"
91
+ @stx:listbox-toggle="onToggle($event)"
92
+ @stx:listbox-select="onSelect($event)"
93
+ >
94
+ <slot />
95
+ </component>
96
+
97
+ <style>
98
+ [data-stx-listbox][data-state="closed"] [data-stx-listbox-options] {
99
+ display: none;
100
+ }
101
+ </style>
@@ -5,13 +5,24 @@ export const disabled = $props.disabled || false
5
5
 
6
6
  const baseClasses = 'relative w-full cursor-pointer rounded-lg bg-white dark:bg-neutral-800 py-2 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-blue-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-blue-300'
7
7
  const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : ''
8
- const buttonClasses = `${baseClasses} ${disabledClasses} ${className}`.trim()
8
+ export const buttonClasses = `${baseClasses} ${disabledClasses} ${className}`.trim()
9
+ </script>
10
+
11
+ <script client>
12
+ const isDisabled = {{ disabled }}
13
+
14
+ function onClick(event) {
15
+ if (isDisabled) return
16
+ event.currentTarget.dispatchEvent(new CustomEvent('stx:listbox-toggle', { bubbles: true }))
17
+ }
9
18
  </script>
10
19
 
11
20
  <component
12
21
  :is="{{ as }}"
13
22
  class="{{ buttonClasses }}"
23
+ data-stx-listbox-button
14
24
  @if(disabled)disabled aria-disabled="true"@endif
25
+ @click="onClick($event)"
15
26
  >
16
27
  <slot />
17
28
  </component>