@stacksjs/components 0.2.3

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 (342) hide show
  1. package/README.md +477 -0
  2. package/dist/Accordion-x2gjedap.stx +108 -0
  3. package/dist/Audio-0fk6p9tn.stx +50 -0
  4. package/dist/Avatar-5m0v0s4x.stx +88 -0
  5. package/dist/Badge-ynes82vv.stx +70 -0
  6. package/dist/Breadcrumb-h9n427sk.stx +79 -0
  7. package/dist/Button-7xtrzn14.stx +89 -0
  8. package/dist/Calendar-egvx1pma.stx +232 -0
  9. package/dist/Card-r9w7vy1a.stx +74 -0
  10. package/dist/Checkbox-m8v46pmx.stx +92 -0
  11. package/dist/Checkout-vcnnr176.stx +39 -0
  12. package/dist/CodeBlock-mwex82cm.stx +57 -0
  13. package/dist/Combobox-npb08vkp.stx +13 -0
  14. package/dist/ComboboxButton-vcdyq4zw.stx +13 -0
  15. package/dist/ComboboxInput-c9d2p02m.stx +25 -0
  16. package/dist/ComboboxOption-7d0h9ash.stx +52 -0
  17. package/dist/ComboboxOptions-4dtqc0qa.stx +12 -0
  18. package/dist/CommandPalette-ah00bf8v.stx +39 -0
  19. package/dist/CommandPaletteItem-2yx0gvtj.stx +32 -0
  20. package/dist/DefaultPaymentMethod-emw5fg8s.stx +62 -0
  21. package/dist/Dialog-1w4rz7x0.stx +19 -0
  22. package/dist/DialogBackdrop-qbm3y5ab.stx +9 -0
  23. package/dist/DialogDescription-01w5j74b.stx +11 -0
  24. package/dist/DialogPanel-brsszrrs.stx +16 -0
  25. package/dist/DialogTitle-3rxhwg21.stx +11 -0
  26. package/dist/Drawer-yfaz8q7f.stx +137 -0
  27. package/dist/Dropdown-ng7nx556.stx +24 -0
  28. package/dist/DropdownButton-vc4zpyrb.stx +23 -0
  29. package/dist/DropdownItem-z491y23c.stx +43 -0
  30. package/dist/DropdownItems-7ykf29sj.stx +18 -0
  31. package/dist/EmailInput-bfshpj92.stx +41 -0
  32. package/dist/Footer-xdaf02nr.stx +66 -0
  33. package/dist/Form-z4f7w6r1.stx +209 -0
  34. package/dist/Hero-9vvp7pxf.stx +25 -0
  35. package/dist/Image-ktk0451c.stx +197 -0
  36. package/dist/Installation-33bj4rm7.stx +39 -0
  37. package/dist/Listbox-xcejcmyc.stx +14 -0
  38. package/dist/ListboxButton-nx5kmw19.stx +17 -0
  39. package/dist/ListboxLabel-aky8a73n.stx +11 -0
  40. package/dist/ListboxOption-6h4hsdz5.stx +57 -0
  41. package/dist/ListboxOptions-2sacdtm5.stx +16 -0
  42. package/dist/Login-s2jkvd9y.stx +163 -0
  43. package/dist/Navigator-tqqdgdkw.stx +138 -0
  44. package/dist/Notification-sry8egea.stx +71 -0
  45. package/dist/NumberInput-zm93ddfw.stx +138 -0
  46. package/dist/Pagination-yza9v4qw.stx +183 -0
  47. package/dist/PasswordInput-4xnndp5y.stx +155 -0
  48. package/dist/PaymentMethods-jxba6kek.stx +113 -0
  49. package/dist/Popover-scbqatjq.stx +24 -0
  50. package/dist/PopoverButton-rbxhzj4k.stx +23 -0
  51. package/dist/PopoverPanel-9ng1rm6e.stx +23 -0
  52. package/dist/Portal-j111yvqp.stx +74 -0
  53. package/dist/Progress-gbmw3akr.stx +142 -0
  54. package/dist/Radio-1n9qamv1.stx +81 -0
  55. package/dist/RadioGroup-12dw2vhk.stx +13 -0
  56. package/dist/RadioGroupDescription-bjm3mdht.stx +11 -0
  57. package/dist/RadioGroupLabel-aky8a73n.stx +11 -0
  58. package/dist/RadioGroupOption-9pcxgwmd.stx +51 -0
  59. package/dist/SearchInput-g240e2nq.stx +63 -0
  60. package/dist/Select-p9f94b20.stx +89 -0
  61. package/dist/Sidebar-gwxw8q38.stx +288 -0
  62. package/dist/SidebarFooter-887kxret.stx +95 -0
  63. package/dist/SidebarHeader-qjeyx0hv.stx +100 -0
  64. package/dist/SidebarItem-4dpwtmb4.stx +118 -0
  65. package/dist/SidebarSection-kh92fk4m.stx +108 -0
  66. package/dist/Signup-3nxq5zag.stx +161 -0
  67. package/dist/Skeleton-g0nx1njm.stx +48 -0
  68. package/dist/Spinner-w4zj58xp.stx +109 -0
  69. package/dist/Stepper-rwc5actp.stx +17 -0
  70. package/dist/StepperStep-h0k657nm.stx +53 -0
  71. package/dist/SubscriptionCheckout-4cbxdg7w.stx +353 -0
  72. package/dist/Switch-kjq8qd2p.stx +74 -0
  73. package/dist/Table-59j9qwp0.stx +14 -0
  74. package/dist/TableBody-reka2gwv.stx +10 -0
  75. package/dist/TableCell-j7nh4sgr.stx +10 -0
  76. package/dist/TableHead-ecbrwpqm.stx +10 -0
  77. package/dist/TableHeader-jn2shwsv.stx +13 -0
  78. package/dist/TableRow-xz8rrxhc.stx +11 -0
  79. package/dist/Tabs-kb6ex6m7.stx +145 -0
  80. package/dist/Teleport-819cg9zm.stx +101 -0
  81. package/dist/TextInput-zx9kgr79.stx +146 -0
  82. package/dist/Textarea-5xvmrb0z.stx +119 -0
  83. package/dist/Tooltip-5ehqsy49.stx +76 -0
  84. package/dist/Transition-2j39s044.stx +174 -0
  85. package/dist/TwoFactorChallenge-2q4gg89m.stx +125 -0
  86. package/dist/Video-6aq4shh4.stx +45 -0
  87. package/dist/VirtualList-weh5qtm1.stx +83 -0
  88. package/dist/VirtualTable-59a1eskc.stx +113 -0
  89. package/dist/components/index.d.ts +4 -0
  90. package/dist/composables/index.d.ts +4 -0
  91. package/dist/composables/index.js +3 -0
  92. package/dist/composables/useCopyCode.d.ts +19 -0
  93. package/dist/composables/useDarkMode.d.ts +25 -0
  94. package/dist/composables/useSEO.d.ts +36 -0
  95. package/dist/composables/utilities/index.d.ts +5 -0
  96. package/dist/composables/utilities/useClickOutside.d.ts +28 -0
  97. package/dist/composables/utilities/useFocusTrap.d.ts +34 -0
  98. package/dist/composables/utilities/useKeyboard.d.ts +55 -0
  99. package/dist/composables/utilities/useLocalStorage.d.ts +33 -0
  100. package/dist/composables/utilities/useMediaQuery.d.ts +64 -0
  101. package/dist/index.d.ts +86 -0
  102. package/dist/index.js +19 -0
  103. package/dist/ui/accordion/index.d.ts +12 -0
  104. package/dist/ui/audio/index.d.ts +12 -0
  105. package/dist/ui/auth/index.d.ts +29 -0
  106. package/dist/ui/avatar/index.d.ts +11 -0
  107. package/dist/ui/badge/index.d.ts +9 -0
  108. package/dist/ui/breadcrumb/index.d.ts +13 -0
  109. package/dist/ui/button/index.d.ts +45 -0
  110. package/dist/ui/calendar/index.d.ts +13 -0
  111. package/dist/ui/card/index.d.ts +12 -0
  112. package/dist/ui/checkbox/index.d.ts +15 -0
  113. package/dist/ui/combobox/index.d.ts +35 -0
  114. package/dist/ui/command-palette/index.d.ts +13 -0
  115. package/dist/ui/dialog/index.d.ts +27 -0
  116. package/dist/ui/drawer/index.d.ts +8 -0
  117. package/dist/ui/dropdown/index.d.ts +25 -0
  118. package/dist/ui/form/index.d.ts +48 -0
  119. package/dist/ui/image/index.d.ts +21 -0
  120. package/dist/ui/input/index.d.ts +73 -0
  121. package/dist/ui/listbox/index.d.ts +34 -0
  122. package/dist/ui/navigator/index.d.ts +18 -0
  123. package/dist/ui/notification/index.d.ts +11 -0
  124. package/dist/ui/pagination/index.d.ts +10 -0
  125. package/dist/ui/payment/index.d.ts +53 -0
  126. package/dist/ui/popover/index.d.ts +19 -0
  127. package/dist/ui/portal/index.d.ts +5 -0
  128. package/dist/ui/progress/index.d.ts +11 -0
  129. package/dist/ui/radio/index.d.ts +14 -0
  130. package/dist/ui/radio-group/index.d.ts +26 -0
  131. package/dist/ui/select/index.d.ts +19 -0
  132. package/dist/ui/sidebar/index.d.ts +129 -0
  133. package/dist/ui/skeleton/index.d.ts +10 -0
  134. package/dist/ui/spinner/index.d.ts +8 -0
  135. package/dist/ui/stepper/index.d.ts +17 -0
  136. package/dist/ui/storage/index.d.ts +40 -0
  137. package/dist/ui/switch/index.d.ts +9 -0
  138. package/dist/ui/table/index.d.ts +29 -0
  139. package/dist/ui/tabs/index.d.ts +14 -0
  140. package/dist/ui/teleport/index.d.ts +6 -0
  141. package/dist/ui/textarea/index.d.ts +19 -0
  142. package/dist/ui/tooltip/index.d.ts +9 -0
  143. package/dist/ui/transition/index.d.ts +24 -0
  144. package/dist/ui/video/index.d.ts +15 -0
  145. package/dist/ui/virtual-list/index.d.ts +9 -0
  146. package/dist/ui/virtual-table/index.d.ts +19 -0
  147. package/dist/utils/accessibility.d.ts +285 -0
  148. package/dist/utils/animation/easing.d.ts +112 -0
  149. package/dist/utils/animation/helpers.d.ts +119 -0
  150. package/dist/utils/animation/index.d.ts +3 -0
  151. package/dist/utils/animation/keyframes.d.ts +334 -0
  152. package/dist/utils/error-handling.d.ts +182 -0
  153. package/dist/utils/highlighter.d.ts +27 -0
  154. package/dist/utils/highlighter.js +2 -0
  155. package/dist/utils/i18n.d.ts +245 -0
  156. package/dist/utils/keep-alive.d.ts +171 -0
  157. package/dist/utils/lazy-loading.d.ts +174 -0
  158. package/dist/utils/prop-validation.d.ts +106 -0
  159. package/dist/utils/theme.d.ts +234 -0
  160. package/package.json +69 -0
  161. package/src/components/CodeBlock.stx +57 -0
  162. package/src/components/Footer.stx +66 -0
  163. package/src/components/Hero.stx +25 -0
  164. package/src/components/Installation.stx +39 -0
  165. package/src/components/index.ts +4 -0
  166. package/src/composables/index.ts +4 -0
  167. package/src/composables/useCopyCode.ts +73 -0
  168. package/src/composables/useDarkMode.ts +100 -0
  169. package/src/composables/useSEO.ts +229 -0
  170. package/src/composables/utilities/index.ts +7 -0
  171. package/src/composables/utilities/useClickOutside.ts +72 -0
  172. package/src/composables/utilities/useFocusTrap.ts +159 -0
  173. package/src/composables/utilities/useKeyboard.ts +124 -0
  174. package/src/composables/utilities/useLocalStorage.ts +132 -0
  175. package/src/composables/utilities/useMediaQuery.ts +118 -0
  176. package/src/index.ts +99 -0
  177. package/src/stx.d.ts +4 -0
  178. package/src/ui/accordion/Accordion.stx +108 -0
  179. package/src/ui/accordion/index.ts +14 -0
  180. package/src/ui/audio/Audio.stx +50 -0
  181. package/src/ui/audio/README.md +42 -0
  182. package/src/ui/audio/index.ts +13 -0
  183. package/src/ui/auth/Login.stx +163 -0
  184. package/src/ui/auth/README.md +145 -0
  185. package/src/ui/auth/Signup.stx +161 -0
  186. package/src/ui/auth/TwoFactorChallenge.stx +125 -0
  187. package/src/ui/auth/index.ts +32 -0
  188. package/src/ui/avatar/Avatar.stx +88 -0
  189. package/src/ui/avatar/index.ts +12 -0
  190. package/src/ui/badge/Badge.stx +70 -0
  191. package/src/ui/badge/index.ts +10 -0
  192. package/src/ui/breadcrumb/Breadcrumb.stx +79 -0
  193. package/src/ui/breadcrumb/index.ts +15 -0
  194. package/src/ui/button/Button.stx +89 -0
  195. package/src/ui/button/README.md +100 -0
  196. package/src/ui/button/index.ts +85 -0
  197. package/src/ui/calendar/Calendar.stx +232 -0
  198. package/src/ui/calendar/README.md +254 -0
  199. package/src/ui/calendar/index.ts +14 -0
  200. package/src/ui/card/Card.stx +74 -0
  201. package/src/ui/card/index.ts +13 -0
  202. package/src/ui/checkbox/Checkbox.stx +92 -0
  203. package/src/ui/checkbox/index.ts +16 -0
  204. package/src/ui/combobox/Combobox.stx +13 -0
  205. package/src/ui/combobox/ComboboxButton.stx +13 -0
  206. package/src/ui/combobox/ComboboxInput.stx +25 -0
  207. package/src/ui/combobox/ComboboxOption.stx +52 -0
  208. package/src/ui/combobox/ComboboxOptions.stx +12 -0
  209. package/src/ui/combobox/README.md +154 -0
  210. package/src/ui/combobox/index.ts +40 -0
  211. package/src/ui/command-palette/CommandPalette.stx +39 -0
  212. package/src/ui/command-palette/CommandPaletteItem.stx +32 -0
  213. package/src/ui/command-palette/README.md +42 -0
  214. package/src/ui/command-palette/index.ts +15 -0
  215. package/src/ui/dialog/Dialog.stx +19 -0
  216. package/src/ui/dialog/DialogBackdrop.stx +9 -0
  217. package/src/ui/dialog/DialogDescription.stx +11 -0
  218. package/src/ui/dialog/DialogPanel.stx +16 -0
  219. package/src/ui/dialog/DialogTitle.stx +11 -0
  220. package/src/ui/dialog/README.md +133 -0
  221. package/src/ui/dialog/index.ts +32 -0
  222. package/src/ui/drawer/Drawer.stx +137 -0
  223. package/src/ui/drawer/README.md +110 -0
  224. package/src/ui/drawer/index.ts +9 -0
  225. package/src/ui/dropdown/Dropdown.stx +24 -0
  226. package/src/ui/dropdown/DropdownButton.stx +23 -0
  227. package/src/ui/dropdown/DropdownItem.stx +43 -0
  228. package/src/ui/dropdown/DropdownItems.stx +18 -0
  229. package/src/ui/dropdown/README.md +142 -0
  230. package/src/ui/dropdown/index.ts +29 -0
  231. package/src/ui/form/Form.stx +209 -0
  232. package/src/ui/form/index.ts +67 -0
  233. package/src/ui/heatmap/Heatmap.stx +315 -0
  234. package/src/ui/image/Image.stx +197 -0
  235. package/src/ui/image/README.md +171 -0
  236. package/src/ui/image/index.ts +22 -0
  237. package/src/ui/input/EmailInput.stx +41 -0
  238. package/src/ui/input/NumberInput.stx +138 -0
  239. package/src/ui/input/PasswordInput.stx +155 -0
  240. package/src/ui/input/SearchInput.stx +63 -0
  241. package/src/ui/input/TextInput.stx +146 -0
  242. package/src/ui/input/index.ts +180 -0
  243. package/src/ui/listbox/Listbox.stx +14 -0
  244. package/src/ui/listbox/ListboxButton.stx +17 -0
  245. package/src/ui/listbox/ListboxLabel.stx +11 -0
  246. package/src/ui/listbox/ListboxOption.stx +57 -0
  247. package/src/ui/listbox/ListboxOptions.stx +16 -0
  248. package/src/ui/listbox/README.md +143 -0
  249. package/src/ui/listbox/index.ts +39 -0
  250. package/src/ui/navigator/Navigator.stx +138 -0
  251. package/src/ui/navigator/README.md +352 -0
  252. package/src/ui/navigator/index.ts +20 -0
  253. package/src/ui/notification/Notification.stx +71 -0
  254. package/src/ui/notification/README.md +118 -0
  255. package/src/ui/notification/index.ts +12 -0
  256. package/src/ui/pagination/Pagination.stx +183 -0
  257. package/src/ui/pagination/index.ts +11 -0
  258. package/src/ui/payment/Checkout.stx +39 -0
  259. package/src/ui/payment/DefaultPaymentMethod.stx +62 -0
  260. package/src/ui/payment/PaymentMethods.stx +113 -0
  261. package/src/ui/payment/README.md +427 -0
  262. package/src/ui/payment/SubscriptionCheckout.stx +353 -0
  263. package/src/ui/payment/index.ts +59 -0
  264. package/src/ui/popover/Popover.stx +24 -0
  265. package/src/ui/popover/PopoverButton.stx +23 -0
  266. package/src/ui/popover/PopoverPanel.stx +23 -0
  267. package/src/ui/popover/README.md +95 -0
  268. package/src/ui/popover/index.ts +22 -0
  269. package/src/ui/portal/Portal.stx +74 -0
  270. package/src/ui/portal/index.ts +6 -0
  271. package/src/ui/progress/Progress.stx +142 -0
  272. package/src/ui/progress/index.ts +12 -0
  273. package/src/ui/radio/Radio.stx +81 -0
  274. package/src/ui/radio/index.ts +15 -0
  275. package/src/ui/radio-group/README.md +145 -0
  276. package/src/ui/radio-group/RadioGroup.stx +13 -0
  277. package/src/ui/radio-group/RadioGroupDescription.stx +11 -0
  278. package/src/ui/radio-group/RadioGroupLabel.stx +11 -0
  279. package/src/ui/radio-group/RadioGroupOption.stx +51 -0
  280. package/src/ui/radio-group/index.ts +30 -0
  281. package/src/ui/select/README.md +204 -0
  282. package/src/ui/select/Select.stx +89 -0
  283. package/src/ui/select/index.ts +21 -0
  284. package/src/ui/sidebar/Sidebar.stx +288 -0
  285. package/src/ui/sidebar/SidebarFooter.stx +95 -0
  286. package/src/ui/sidebar/SidebarHeader.stx +100 -0
  287. package/src/ui/sidebar/SidebarItem.stx +118 -0
  288. package/src/ui/sidebar/SidebarSection.stx +108 -0
  289. package/src/ui/sidebar/index.ts +175 -0
  290. package/src/ui/skeleton/Skeleton.stx +48 -0
  291. package/src/ui/skeleton/index.ts +11 -0
  292. package/src/ui/spinner/Spinner.stx +109 -0
  293. package/src/ui/spinner/index.ts +9 -0
  294. package/src/ui/stepper/README.md +36 -0
  295. package/src/ui/stepper/Stepper.stx +17 -0
  296. package/src/ui/stepper/StepperStep.stx +53 -0
  297. package/src/ui/stepper/index.ts +19 -0
  298. package/src/ui/storage/README.md +193 -0
  299. package/src/ui/storage/index.ts +347 -0
  300. package/src/ui/switch/README.md +133 -0
  301. package/src/ui/switch/Switch.stx +74 -0
  302. package/src/ui/switch/index.ts +10 -0
  303. package/src/ui/table/README.md +39 -0
  304. package/src/ui/table/Table.stx +14 -0
  305. package/src/ui/table/TableBody.stx +10 -0
  306. package/src/ui/table/TableCell.stx +10 -0
  307. package/src/ui/table/TableHead.stx +10 -0
  308. package/src/ui/table/TableHeader.stx +13 -0
  309. package/src/ui/table/TableRow.stx +11 -0
  310. package/src/ui/table/index.ts +35 -0
  311. package/src/ui/tabs/Tabs.stx +145 -0
  312. package/src/ui/tabs/index.ts +16 -0
  313. package/src/ui/teleport/Teleport.stx +101 -0
  314. package/src/ui/teleport/index.ts +7 -0
  315. package/src/ui/textarea/Textarea.stx +119 -0
  316. package/src/ui/textarea/index.ts +20 -0
  317. package/src/ui/tooltip/Tooltip.stx +76 -0
  318. package/src/ui/tooltip/index.ts +10 -0
  319. package/src/ui/transition/README.md +30 -0
  320. package/src/ui/transition/Transition.stx +174 -0
  321. package/src/ui/transition/index.ts +25 -0
  322. package/src/ui/video/README.md +48 -0
  323. package/src/ui/video/Video.stx +45 -0
  324. package/src/ui/video/index.ts +16 -0
  325. package/src/ui/virtual-list/VirtualList.stx +83 -0
  326. package/src/ui/virtual-list/index.ts +10 -0
  327. package/src/ui/virtual-table/VirtualTable.stx +113 -0
  328. package/src/ui/virtual-table/index.ts +21 -0
  329. package/src/utils/PROP_VALIDATION.md +458 -0
  330. package/src/utils/THEME_SYSTEM.md +702 -0
  331. package/src/utils/accessibility.ts +841 -0
  332. package/src/utils/animation/easing.ts +368 -0
  333. package/src/utils/animation/helpers.ts +223 -0
  334. package/src/utils/animation/index.ts +5 -0
  335. package/src/utils/animation/keyframes.ts +230 -0
  336. package/src/utils/error-handling.ts +473 -0
  337. package/src/utils/highlighter.ts +93 -0
  338. package/src/utils/i18n.ts +634 -0
  339. package/src/utils/keep-alive.ts +818 -0
  340. package/src/utils/lazy-loading.ts +530 -0
  341. package/src/utils/prop-validation.ts +478 -0
  342. package/src/utils/theme.ts +626 -0
@@ -0,0 +1,197 @@
1
+ <script>
2
+ export const src = $props.src || ''
3
+ export const srcSet = $props.srcSet || ''
4
+ export const sizes = $props.sizes || ''
5
+ export const webpSrc = $props.webpSrc || ''
6
+ export const webpSrcSet = $props.webpSrcSet || ''
7
+ export const alt = $props.alt || ''
8
+ export const width = $props.width
9
+ export const height = $props.height
10
+ export const lazy = $props.lazy ?? true
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
+ export const blurDataURL = $props.blurDataURL || ''
13
+ export const onLoad = $props.onLoad
14
+ export const onError = $props.onError
15
+ export const aspectRatio = $props.aspectRatio
16
+ export const objectFit = $props.objectFit || 'cover'
17
+ export const rounded = $props.rounded ?? false
18
+ export const zoomOnHover = $props.zoomOnHover || false
19
+ export const className = $props.className || ''
20
+
21
+ // Has WebP support
22
+ export const hasWebP = webpSrc || webpSrcSet
23
+
24
+ // Object fit classes
25
+ const objectFitClasses = {
26
+ contain: 'object-contain',
27
+ cover: 'object-cover',
28
+ fill: 'object-fill',
29
+ none: 'object-none',
30
+ 'scale-down': 'object-scale-down',
31
+ }
32
+
33
+ // Rounded classes
34
+ const roundedClass = rounded === true ? 'rounded-lg' : rounded === 'full' ? 'rounded-full' : rounded ? `rounded-${rounded}` : ''
35
+
36
+ const zoomClasses = zoomOnHover ? 'transform hover:scale-110 transition-transform duration-300 ease-in-out cursor-zoom-in' : ''
37
+
38
+ export const containerClasses = `relative overflow-hidden ${roundedClass} ${className}`.trim()
39
+ 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
+ export const blurPlaceholderClasses = `absolute inset-0 ${roundedClass} filter blur-lg scale-110`.trim()
42
+
43
+ // Generate aspect ratio padding if provided
44
+ export const paddingBottom = aspectRatio ? `${(1 / aspectRatio) * 100}%` : undefined
45
+ </script>
46
+
47
+ <div class="{{ containerClasses }}" style="{{ paddingBottom ? `padding-bottom: ${paddingBottom}` : '' }}">
48
+ @if(blurDataURL)
49
+ <!-- Blur placeholder -->
50
+ <img
51
+ src="{{ blurDataURL }}"
52
+ alt=""
53
+ class="{{ blurPlaceholderClasses }}"
54
+ aria-hidden="true"
55
+ />
56
+ @endif
57
+
58
+ @if(hasWebP)
59
+ <!-- Picture element with WebP support -->
60
+ <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
+
70
+ @if(lazy)
71
+ <img
72
+ src="{{ placeholder }}"
73
+ data-src="{{ src }}"
74
+ @if(srcSet)data-srcset="{{ srcSet }}"@endif
75
+ @if(sizes)sizes="{{ sizes }}"@endif
76
+ alt="{{ alt }}"
77
+ class="{{ imageClasses }} lazy-image opacity-0"
78
+ width="{{ width }}"
79
+ height="{{ height }}"
80
+ loading="lazy"
81
+ onload="handleImageLoad(event)"
82
+ onerror="handleImageError(event)"
83
+ />
84
+ @else
85
+ <img
86
+ src="{{ src }}"
87
+ @if(srcSet)srcset="{{ srcSet }}"@endif
88
+ @if(sizes)sizes="{{ sizes }}"@endif
89
+ alt="{{ alt }}"
90
+ class="{{ imageClasses }}"
91
+ width="{{ width }}"
92
+ height="{{ height }}"
93
+ onload="handleImageLoad(event)"
94
+ onerror="handleImageError(event)"
95
+ />
96
+ @endif
97
+ </picture>
98
+ @else
99
+ <!-- Regular img element -->
100
+ @if(lazy)
101
+ <img
102
+ src="{{ placeholder }}"
103
+ data-src="{{ src }}"
104
+ @if(srcSet)data-srcset="{{ srcSet }}"@endif
105
+ @if(sizes)sizes="{{ sizes }}"@endif
106
+ alt="{{ alt }}"
107
+ class="{{ imageClasses }} lazy-image opacity-0"
108
+ width="{{ width }}"
109
+ height="{{ height }}"
110
+ loading="lazy"
111
+ onload="handleImageLoad(event)"
112
+ onerror="handleImageError(event)"
113
+ />
114
+ @else
115
+ <img
116
+ src="{{ src }}"
117
+ @if(srcSet)srcset="{{ srcSet }}"@endif
118
+ @if(sizes)sizes="{{ sizes }}"@endif
119
+ alt="{{ alt }}"
120
+ class="{{ imageClasses }}"
121
+ width="{{ width }}"
122
+ height="{{ height }}"
123
+ onload="handleImageLoad(event)"
124
+ onerror="handleImageError(event)"
125
+ />
126
+ @endif
127
+ @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
+ </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,171 @@
1
+ # Image Component
2
+
3
+ An optimized image component with lazy loading, placeholders, and responsive features.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @stacksjs/components
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Basic Image
14
+
15
+ ```stx
16
+ @component('Image', {
17
+ src: '/path/to/image.jpg',
18
+ alt: 'Description of image',
19
+ width: 400,
20
+ height: 300
21
+ })
22
+ @endcomponent
23
+ ```
24
+
25
+ ### Lazy Loading (default)
26
+
27
+ ```stx
28
+ @component('Image', {
29
+ src: '/large-image.jpg',
30
+ alt: 'Large image',
31
+ lazy: true,
32
+ placeholder: '/thumbnail.jpg'
33
+ })
34
+ @endcomponent
35
+ ```
36
+
37
+ ### Aspect Ratio
38
+
39
+ ```stx
40
+ @component('Image', {
41
+ src: '/hero.jpg',
42
+ alt: 'Hero image',
43
+ aspectRatio: 16/9,
44
+ objectFit: 'cover'
45
+ })
46
+ @endcomponent
47
+ ```
48
+
49
+ ### Rounded Images
50
+
51
+ ```stx
52
+ <!-- Rounded corners -->
53
+ @component('Image', {
54
+ src: '/avatar.jpg',
55
+ alt: 'Avatar',
56
+ rounded: true
57
+ })
58
+ @endcomponent
59
+
60
+ <!-- Circular -->
61
+ @component('Image', {
62
+ src: '/profile.jpg',
63
+ alt: 'Profile',
64
+ rounded: 'full'
65
+ })
66
+ @endcomponent
67
+ ```
68
+
69
+ ### With Callbacks
70
+
71
+ ```stx
72
+ <script>
73
+ export const handleLoad = () => {
74
+ console.log('Image loaded!')
75
+ }
76
+
77
+ export const handleError = () => {
78
+ console.error('Failed to load image')
79
+ }
80
+ </script>
81
+
82
+ @component('Image', {
83
+ src: '/photo.jpg',
84
+ alt: 'Photo',
85
+ onLoad: handleLoad,
86
+ onError: handleError
87
+ })
88
+ @endcomponent
89
+ ```
90
+
91
+ ## Props
92
+
93
+ | Prop | Type | Default | Description |
94
+ |------|------|---------|-------------|
95
+ | `src` | `string` | - | Image source URL (required) |
96
+ | `alt` | `string` | - | Alt text (required) |
97
+ | `width` | `number \| string` | - | Image width |
98
+ | `height` | `number \| string` | - | Image height |
99
+ | `lazy` | `boolean` | `true` | Enable lazy loading |
100
+ | `placeholder` | `string` | gray SVG | Placeholder image while loading |
101
+ | `onLoad` | `function` | - | Callback when image loads |
102
+ | `onError` | `function` | - | Callback when image fails |
103
+ | `aspectRatio` | `number` | - | Aspect ratio (e.g., 16/9, 4/3) |
104
+ | `objectFit` | `'contain' \| 'cover' \| 'fill' \| 'none' \| 'scale-down'` | `'cover'` | How image fits container |
105
+ | `rounded` | `boolean \| 'full' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | `false` | Border radius |
106
+ | `className` | `string` | `''` | Additional CSS classes |
107
+
108
+ ## Features
109
+
110
+ - **Lazy loading** - Loads images only when visible (Intersection Observer)
111
+ - **Placeholders** - Shows placeholder until image loads
112
+ - **Aspect ratio** - Maintains aspect ratio to prevent layout shift
113
+ - **Object fit** - Control how images fill their container
114
+ - **Responsive** - Adapts to container size
115
+ - **Error handling** - Shows fallback on load failure
116
+ - **Dark mode** - Skeleton loader adapts to dark mode
117
+ - **Smooth transitions** - Fade-in effect on load
118
+ - **Performance** - Optimized with intersection observer
119
+ - **Modern ES modules** - Clean export syntax
120
+ - **Headwind styling** - Utility-first CSS classes
121
+
122
+ ## Object Fit Options
123
+
124
+ - `contain` - Image fits within container, maintains aspect ratio
125
+ - `cover` - Image covers container, may crop
126
+ - `fill` - Image stretches to fill container
127
+ - `none` - Image maintains original size
128
+ - `scale-down` - Uses smallest of `none` or `contain`
129
+
130
+ ## Rounded Options
131
+
132
+ - `true` - `rounded-lg` (8px)
133
+ - `'full'` - `rounded-full` (fully circular)
134
+ - `'sm'` - `rounded-sm` (2px)
135
+ - `'md'` - `rounded-md` (6px)
136
+ - `'lg'` - `rounded-lg` (8px)
137
+ - `'xl'` - `rounded-xl` (12px)
138
+ - `'2xl'` - `rounded-2xl` (16px)
139
+
140
+ ## Accessibility
141
+
142
+ - Always provide meaningful `alt` text
143
+ - Component automatically handles loading states
144
+ - Error states show user-friendly fallback
145
+ - Works without JavaScript (progressive enhancement)
146
+
147
+ ## Performance Tips
148
+
149
+ 1. Use `aspectRatio` to prevent layout shift
150
+ 2. Enable `lazy` loading for below-the-fold images
151
+ 3. Provide appropriate `width` and `height`
152
+ 4. Use appropriate image formats (WebP, AVIF)
153
+ 5. Optimize images before upload
154
+
155
+ ## Example: Image Gallery
156
+
157
+ ```stx
158
+ <div class="grid grid-cols-3 gap-4">
159
+ @foreach(image in images)
160
+ @component('Image', {
161
+ src: image.url,
162
+ alt: image.description,
163
+ aspectRatio: 1,
164
+ objectFit: 'cover',
165
+ rounded: true,
166
+ lazy: true
167
+ })
168
+ @endcomponent
169
+ @endforeach
170
+ </div>
171
+ ```
@@ -0,0 +1,22 @@
1
+ export { default as Image } from './Image.stx'
2
+
3
+ export interface ImageProps {
4
+ src: string
5
+ srcSet?: string
6
+ sizes?: string
7
+ webpSrc?: string
8
+ webpSrcSet?: string
9
+ alt: string
10
+ width?: number | string
11
+ height?: number | string
12
+ lazy?: boolean
13
+ placeholder?: string
14
+ blurDataURL?: string
15
+ onLoad?: (event: Event) => void
16
+ onError?: (event: Event) => void
17
+ aspectRatio?: number
18
+ objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down'
19
+ rounded?: boolean | 'full' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
20
+ zoomOnHover?: boolean
21
+ className?: string
22
+ }
@@ -0,0 +1,41 @@
1
+ <script>
2
+ export const value = $props.value || ''
3
+ export const onChange = $props.onChange || (() => {})
4
+ export const placeholder = $props.placeholder || 'Enter your email'
5
+ export const disabled = $props.disabled || false
6
+ export const readonly = $props.readonly || false
7
+ export const size = $props.size || 'md'
8
+ export const error = $props.error || false
9
+ export const label = $props.label || ''
10
+ export const helperText = $props.helperText || ''
11
+ export const required = $props.required || false
12
+ export const className = $props.className || ''
13
+
14
+ // Email icon SVG
15
+ 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>'
16
+ </script>
17
+
18
+ @component('TextInput', {
19
+ value,
20
+ onChange,
21
+ placeholder,
22
+ disabled,
23
+ readonly,
24
+ size,
25
+ error,
26
+ label,
27
+ helperText,
28
+ required,
29
+ leftIcon: emailIcon,
30
+ clearable: true,
31
+ className,
32
+ inputClassName: 'font-mono'
33
+ })
34
+ @endcomponent
35
+
36
+ <script>
37
+ // Add additional email validation logic if needed
38
+ function validateEmail(email) {
39
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
40
+ }
41
+ </script>
@@ -0,0 +1,138 @@
1
+ <script>
2
+ export const value = $props.value || ''
3
+ export const onChange = $props.onChange || (() => {})
4
+ export const placeholder = $props.placeholder || '0'
5
+ export const disabled = $props.disabled || false
6
+ export const readonly = $props.readonly || false
7
+ export const size = $props.size || 'md'
8
+ export const error = $props.error || false
9
+ export const label = $props.label || ''
10
+ export const helperText = $props.helperText || ''
11
+ export const required = $props.required || false
12
+ export const min = $props.min
13
+ export const max = $props.max
14
+ export const step = $props.step || 1
15
+ export const showControls = $props.showControls ?? true
16
+ export const className = $props.className || ''
17
+ export const inputClassName = $props.inputClassName || ''
18
+
19
+ // Size classes
20
+ const sizeClasses = {
21
+ sm: 'px-3 py-1.5 text-sm h-8',
22
+ md: 'px-4 py-2 text-base h-10',
23
+ lg: 'px-5 py-3 text-lg h-12',
24
+ }
25
+
26
+ // Base and state classes
27
+ const baseClasses = 'block w-full border-0 rounded-md shadow-sm ring-1 ring-inset transition-colors duration-200 focus:ring-2 focus:ring-inset dark:bg-blue-gray-700 dark:text-gray-100'
28
+
29
+ const stateClasses = error
30
+ ? 'ring-red-300 dark:ring-red-600 text-red-900 dark:text-red-300 placeholder-red-300 dark:placeholder-red-500 focus:ring-red-500 dark:focus:ring-red-400'
31
+ : 'ring-gray-300 dark:ring-gray-600 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-indigo-600 dark:focus:ring-indigo-400'
32
+
33
+ const disabledClasses = disabled
34
+ ? 'opacity-50 cursor-not-allowed bg-gray-50 dark:bg-gray-800'
35
+ : readonly
36
+ ? 'bg-gray-50 dark:bg-gray-800 cursor-default'
37
+ : 'bg-white dark:bg-blue-gray-700'
38
+
39
+ const paddingClasses = showControls ? 'pr-10' : ''
40
+
41
+ export const inputClasses = `${baseClasses} ${sizeClasses[size]} ${stateClasses} ${disabledClasses} ${paddingClasses} ${inputClassName}`.trim()
42
+ export const labelClasses = `block text-sm font-medium leading-6 mb-2 ${error ? 'text-red-600 dark:text-red-400' : 'text-gray-900 dark:text-gray-100'}`.trim()
43
+ export const helperClasses = `mt-2 text-sm ${error ? 'text-red-600 dark:text-red-400' : 'text-gray-500 dark:text-gray-400'}`.trim()
44
+ </script>
45
+
46
+ <div class="w-full {{ className }}">
47
+ @if(label)
48
+ <label for="number-input" class="{{ labelClasses }}">
49
+ {{ label }}
50
+ @if(required)
51
+ <span class="text-red-500 ml-1">*</span>
52
+ @endif
53
+ </label>
54
+ @endif
55
+
56
+ <div class="relative">
57
+ <input
58
+ type="number"
59
+ id="number-input"
60
+ class="{{ inputClasses }}"
61
+ value="{{ value }}"
62
+ placeholder="{{ placeholder }}"
63
+ disabled="{{ disabled }}"
64
+ readonly="{{ readonly }}"
65
+ required="{{ required }}"
66
+ @if(min !== undefined)min="{{ min }}"@endif
67
+ @if(max !== undefined)max="{{ max }}"@endif
68
+ step="{{ step }}"
69
+ oninput="handleInput(event)"
70
+ aria-invalid="{{ error ? 'true' : 'false' }}"
71
+ @if(helperText)aria-describedby="helper-text"@endif
72
+ />
73
+
74
+ @if(showControls && !disabled && !readonly)
75
+ <!-- Increment/Decrement controls -->
76
+ <div class="absolute inset-y-0 right-0 flex flex-col">
77
+ <button
78
+ type="button"
79
+ onclick="handleIncrement()"
80
+ class="flex items-center justify-center w-8 h-1/2 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 focus:outline-none border-b border-gray-300 dark:border-gray-600"
81
+ aria-label="Increment"
82
+ >
83
+ <svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
84
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
85
+ </svg>
86
+ </button>
87
+ <button
88
+ type="button"
89
+ onclick="handleDecrement()"
90
+ class="flex items-center justify-center w-8 h-1/2 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 focus:outline-none"
91
+ aria-label="Decrement"
92
+ >
93
+ <svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
94
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
95
+ </svg>
96
+ </button>
97
+ </div>
98
+ @endif
99
+ </div>
100
+
101
+ @if(helperText)
102
+ <p id="helper-text" class="{{ helperClasses }}">
103
+ {{ helperText }}
104
+ </p>
105
+ @endif
106
+ </div>
107
+
108
+ <script>
109
+ function handleInput(event) {
110
+ const newValue = event.target.value
111
+
112
+ if (onChange) {
113
+ onChange(newValue)
114
+ }
115
+ }
116
+
117
+ function handleIncrement() {
118
+ const currentValue = parseFloat(value) || 0
119
+ const newValue = currentValue + step
120
+
121
+ if (max !== undefined && newValue > max) return
122
+
123
+ if (onChange) {
124
+ onChange(String(newValue))
125
+ }
126
+ }
127
+
128
+ function handleDecrement() {
129
+ const currentValue = parseFloat(value) || 0
130
+ const newValue = currentValue - step
131
+
132
+ if (min !== undefined && newValue < min) return
133
+
134
+ if (onChange) {
135
+ onChange(String(newValue))
136
+ }
137
+ }
138
+ </script>