@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.
- package/README.md +477 -0
- package/dist/Accordion-x2gjedap.stx +108 -0
- package/dist/Audio-0fk6p9tn.stx +50 -0
- package/dist/Avatar-5m0v0s4x.stx +88 -0
- package/dist/Badge-ynes82vv.stx +70 -0
- package/dist/Breadcrumb-h9n427sk.stx +79 -0
- package/dist/Button-7xtrzn14.stx +89 -0
- package/dist/Calendar-egvx1pma.stx +232 -0
- package/dist/Card-r9w7vy1a.stx +74 -0
- package/dist/Checkbox-m8v46pmx.stx +92 -0
- package/dist/Checkout-vcnnr176.stx +39 -0
- package/dist/CodeBlock-mwex82cm.stx +57 -0
- package/dist/Combobox-npb08vkp.stx +13 -0
- package/dist/ComboboxButton-vcdyq4zw.stx +13 -0
- package/dist/ComboboxInput-c9d2p02m.stx +25 -0
- package/dist/ComboboxOption-7d0h9ash.stx +52 -0
- package/dist/ComboboxOptions-4dtqc0qa.stx +12 -0
- package/dist/CommandPalette-ah00bf8v.stx +39 -0
- package/dist/CommandPaletteItem-2yx0gvtj.stx +32 -0
- package/dist/DefaultPaymentMethod-emw5fg8s.stx +62 -0
- package/dist/Dialog-1w4rz7x0.stx +19 -0
- package/dist/DialogBackdrop-qbm3y5ab.stx +9 -0
- package/dist/DialogDescription-01w5j74b.stx +11 -0
- package/dist/DialogPanel-brsszrrs.stx +16 -0
- package/dist/DialogTitle-3rxhwg21.stx +11 -0
- package/dist/Drawer-yfaz8q7f.stx +137 -0
- package/dist/Dropdown-ng7nx556.stx +24 -0
- package/dist/DropdownButton-vc4zpyrb.stx +23 -0
- package/dist/DropdownItem-z491y23c.stx +43 -0
- package/dist/DropdownItems-7ykf29sj.stx +18 -0
- package/dist/EmailInput-bfshpj92.stx +41 -0
- package/dist/Footer-xdaf02nr.stx +66 -0
- package/dist/Form-z4f7w6r1.stx +209 -0
- package/dist/Hero-9vvp7pxf.stx +25 -0
- package/dist/Image-ktk0451c.stx +197 -0
- package/dist/Installation-33bj4rm7.stx +39 -0
- package/dist/Listbox-xcejcmyc.stx +14 -0
- package/dist/ListboxButton-nx5kmw19.stx +17 -0
- package/dist/ListboxLabel-aky8a73n.stx +11 -0
- package/dist/ListboxOption-6h4hsdz5.stx +57 -0
- package/dist/ListboxOptions-2sacdtm5.stx +16 -0
- package/dist/Login-s2jkvd9y.stx +163 -0
- package/dist/Navigator-tqqdgdkw.stx +138 -0
- package/dist/Notification-sry8egea.stx +71 -0
- package/dist/NumberInput-zm93ddfw.stx +138 -0
- package/dist/Pagination-yza9v4qw.stx +183 -0
- package/dist/PasswordInput-4xnndp5y.stx +155 -0
- package/dist/PaymentMethods-jxba6kek.stx +113 -0
- package/dist/Popover-scbqatjq.stx +24 -0
- package/dist/PopoverButton-rbxhzj4k.stx +23 -0
- package/dist/PopoverPanel-9ng1rm6e.stx +23 -0
- package/dist/Portal-j111yvqp.stx +74 -0
- package/dist/Progress-gbmw3akr.stx +142 -0
- package/dist/Radio-1n9qamv1.stx +81 -0
- package/dist/RadioGroup-12dw2vhk.stx +13 -0
- package/dist/RadioGroupDescription-bjm3mdht.stx +11 -0
- package/dist/RadioGroupLabel-aky8a73n.stx +11 -0
- package/dist/RadioGroupOption-9pcxgwmd.stx +51 -0
- package/dist/SearchInput-g240e2nq.stx +63 -0
- package/dist/Select-p9f94b20.stx +89 -0
- package/dist/Sidebar-gwxw8q38.stx +288 -0
- package/dist/SidebarFooter-887kxret.stx +95 -0
- package/dist/SidebarHeader-qjeyx0hv.stx +100 -0
- package/dist/SidebarItem-4dpwtmb4.stx +118 -0
- package/dist/SidebarSection-kh92fk4m.stx +108 -0
- package/dist/Signup-3nxq5zag.stx +161 -0
- package/dist/Skeleton-g0nx1njm.stx +48 -0
- package/dist/Spinner-w4zj58xp.stx +109 -0
- package/dist/Stepper-rwc5actp.stx +17 -0
- package/dist/StepperStep-h0k657nm.stx +53 -0
- package/dist/SubscriptionCheckout-4cbxdg7w.stx +353 -0
- package/dist/Switch-kjq8qd2p.stx +74 -0
- package/dist/Table-59j9qwp0.stx +14 -0
- package/dist/TableBody-reka2gwv.stx +10 -0
- package/dist/TableCell-j7nh4sgr.stx +10 -0
- package/dist/TableHead-ecbrwpqm.stx +10 -0
- package/dist/TableHeader-jn2shwsv.stx +13 -0
- package/dist/TableRow-xz8rrxhc.stx +11 -0
- package/dist/Tabs-kb6ex6m7.stx +145 -0
- package/dist/Teleport-819cg9zm.stx +101 -0
- package/dist/TextInput-zx9kgr79.stx +146 -0
- package/dist/Textarea-5xvmrb0z.stx +119 -0
- package/dist/Tooltip-5ehqsy49.stx +76 -0
- package/dist/Transition-2j39s044.stx +174 -0
- package/dist/TwoFactorChallenge-2q4gg89m.stx +125 -0
- package/dist/Video-6aq4shh4.stx +45 -0
- package/dist/VirtualList-weh5qtm1.stx +83 -0
- package/dist/VirtualTable-59a1eskc.stx +113 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/composables/index.d.ts +4 -0
- package/dist/composables/index.js +3 -0
- package/dist/composables/useCopyCode.d.ts +19 -0
- package/dist/composables/useDarkMode.d.ts +25 -0
- package/dist/composables/useSEO.d.ts +36 -0
- package/dist/composables/utilities/index.d.ts +5 -0
- package/dist/composables/utilities/useClickOutside.d.ts +28 -0
- package/dist/composables/utilities/useFocusTrap.d.ts +34 -0
- package/dist/composables/utilities/useKeyboard.d.ts +55 -0
- package/dist/composables/utilities/useLocalStorage.d.ts +33 -0
- package/dist/composables/utilities/useMediaQuery.d.ts +64 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +19 -0
- package/dist/ui/accordion/index.d.ts +12 -0
- package/dist/ui/audio/index.d.ts +12 -0
- package/dist/ui/auth/index.d.ts +29 -0
- package/dist/ui/avatar/index.d.ts +11 -0
- package/dist/ui/badge/index.d.ts +9 -0
- package/dist/ui/breadcrumb/index.d.ts +13 -0
- package/dist/ui/button/index.d.ts +45 -0
- package/dist/ui/calendar/index.d.ts +13 -0
- package/dist/ui/card/index.d.ts +12 -0
- package/dist/ui/checkbox/index.d.ts +15 -0
- package/dist/ui/combobox/index.d.ts +35 -0
- package/dist/ui/command-palette/index.d.ts +13 -0
- package/dist/ui/dialog/index.d.ts +27 -0
- package/dist/ui/drawer/index.d.ts +8 -0
- package/dist/ui/dropdown/index.d.ts +25 -0
- package/dist/ui/form/index.d.ts +48 -0
- package/dist/ui/image/index.d.ts +21 -0
- package/dist/ui/input/index.d.ts +73 -0
- package/dist/ui/listbox/index.d.ts +34 -0
- package/dist/ui/navigator/index.d.ts +18 -0
- package/dist/ui/notification/index.d.ts +11 -0
- package/dist/ui/pagination/index.d.ts +10 -0
- package/dist/ui/payment/index.d.ts +53 -0
- package/dist/ui/popover/index.d.ts +19 -0
- package/dist/ui/portal/index.d.ts +5 -0
- package/dist/ui/progress/index.d.ts +11 -0
- package/dist/ui/radio/index.d.ts +14 -0
- package/dist/ui/radio-group/index.d.ts +26 -0
- package/dist/ui/select/index.d.ts +19 -0
- package/dist/ui/sidebar/index.d.ts +129 -0
- package/dist/ui/skeleton/index.d.ts +10 -0
- package/dist/ui/spinner/index.d.ts +8 -0
- package/dist/ui/stepper/index.d.ts +17 -0
- package/dist/ui/storage/index.d.ts +40 -0
- package/dist/ui/switch/index.d.ts +9 -0
- package/dist/ui/table/index.d.ts +29 -0
- package/dist/ui/tabs/index.d.ts +14 -0
- package/dist/ui/teleport/index.d.ts +6 -0
- package/dist/ui/textarea/index.d.ts +19 -0
- package/dist/ui/tooltip/index.d.ts +9 -0
- package/dist/ui/transition/index.d.ts +24 -0
- package/dist/ui/video/index.d.ts +15 -0
- package/dist/ui/virtual-list/index.d.ts +9 -0
- package/dist/ui/virtual-table/index.d.ts +19 -0
- package/dist/utils/accessibility.d.ts +285 -0
- package/dist/utils/animation/easing.d.ts +112 -0
- package/dist/utils/animation/helpers.d.ts +119 -0
- package/dist/utils/animation/index.d.ts +3 -0
- package/dist/utils/animation/keyframes.d.ts +334 -0
- package/dist/utils/error-handling.d.ts +182 -0
- package/dist/utils/highlighter.d.ts +27 -0
- package/dist/utils/highlighter.js +2 -0
- package/dist/utils/i18n.d.ts +245 -0
- package/dist/utils/keep-alive.d.ts +171 -0
- package/dist/utils/lazy-loading.d.ts +174 -0
- package/dist/utils/prop-validation.d.ts +106 -0
- package/dist/utils/theme.d.ts +234 -0
- package/package.json +69 -0
- package/src/components/CodeBlock.stx +57 -0
- package/src/components/Footer.stx +66 -0
- package/src/components/Hero.stx +25 -0
- package/src/components/Installation.stx +39 -0
- package/src/components/index.ts +4 -0
- package/src/composables/index.ts +4 -0
- package/src/composables/useCopyCode.ts +73 -0
- package/src/composables/useDarkMode.ts +100 -0
- package/src/composables/useSEO.ts +229 -0
- package/src/composables/utilities/index.ts +7 -0
- package/src/composables/utilities/useClickOutside.ts +72 -0
- package/src/composables/utilities/useFocusTrap.ts +159 -0
- package/src/composables/utilities/useKeyboard.ts +124 -0
- package/src/composables/utilities/useLocalStorage.ts +132 -0
- package/src/composables/utilities/useMediaQuery.ts +118 -0
- package/src/index.ts +99 -0
- package/src/stx.d.ts +4 -0
- package/src/ui/accordion/Accordion.stx +108 -0
- package/src/ui/accordion/index.ts +14 -0
- package/src/ui/audio/Audio.stx +50 -0
- package/src/ui/audio/README.md +42 -0
- package/src/ui/audio/index.ts +13 -0
- package/src/ui/auth/Login.stx +163 -0
- package/src/ui/auth/README.md +145 -0
- package/src/ui/auth/Signup.stx +161 -0
- package/src/ui/auth/TwoFactorChallenge.stx +125 -0
- package/src/ui/auth/index.ts +32 -0
- package/src/ui/avatar/Avatar.stx +88 -0
- package/src/ui/avatar/index.ts +12 -0
- package/src/ui/badge/Badge.stx +70 -0
- package/src/ui/badge/index.ts +10 -0
- package/src/ui/breadcrumb/Breadcrumb.stx +79 -0
- package/src/ui/breadcrumb/index.ts +15 -0
- package/src/ui/button/Button.stx +89 -0
- package/src/ui/button/README.md +100 -0
- package/src/ui/button/index.ts +85 -0
- package/src/ui/calendar/Calendar.stx +232 -0
- package/src/ui/calendar/README.md +254 -0
- package/src/ui/calendar/index.ts +14 -0
- package/src/ui/card/Card.stx +74 -0
- package/src/ui/card/index.ts +13 -0
- package/src/ui/checkbox/Checkbox.stx +92 -0
- package/src/ui/checkbox/index.ts +16 -0
- package/src/ui/combobox/Combobox.stx +13 -0
- package/src/ui/combobox/ComboboxButton.stx +13 -0
- package/src/ui/combobox/ComboboxInput.stx +25 -0
- package/src/ui/combobox/ComboboxOption.stx +52 -0
- package/src/ui/combobox/ComboboxOptions.stx +12 -0
- package/src/ui/combobox/README.md +154 -0
- package/src/ui/combobox/index.ts +40 -0
- package/src/ui/command-palette/CommandPalette.stx +39 -0
- package/src/ui/command-palette/CommandPaletteItem.stx +32 -0
- package/src/ui/command-palette/README.md +42 -0
- package/src/ui/command-palette/index.ts +15 -0
- package/src/ui/dialog/Dialog.stx +19 -0
- package/src/ui/dialog/DialogBackdrop.stx +9 -0
- package/src/ui/dialog/DialogDescription.stx +11 -0
- package/src/ui/dialog/DialogPanel.stx +16 -0
- package/src/ui/dialog/DialogTitle.stx +11 -0
- package/src/ui/dialog/README.md +133 -0
- package/src/ui/dialog/index.ts +32 -0
- package/src/ui/drawer/Drawer.stx +137 -0
- package/src/ui/drawer/README.md +110 -0
- package/src/ui/drawer/index.ts +9 -0
- package/src/ui/dropdown/Dropdown.stx +24 -0
- package/src/ui/dropdown/DropdownButton.stx +23 -0
- package/src/ui/dropdown/DropdownItem.stx +43 -0
- package/src/ui/dropdown/DropdownItems.stx +18 -0
- package/src/ui/dropdown/README.md +142 -0
- package/src/ui/dropdown/index.ts +29 -0
- package/src/ui/form/Form.stx +209 -0
- package/src/ui/form/index.ts +67 -0
- package/src/ui/heatmap/Heatmap.stx +315 -0
- package/src/ui/image/Image.stx +197 -0
- package/src/ui/image/README.md +171 -0
- package/src/ui/image/index.ts +22 -0
- package/src/ui/input/EmailInput.stx +41 -0
- package/src/ui/input/NumberInput.stx +138 -0
- package/src/ui/input/PasswordInput.stx +155 -0
- package/src/ui/input/SearchInput.stx +63 -0
- package/src/ui/input/TextInput.stx +146 -0
- package/src/ui/input/index.ts +180 -0
- package/src/ui/listbox/Listbox.stx +14 -0
- package/src/ui/listbox/ListboxButton.stx +17 -0
- package/src/ui/listbox/ListboxLabel.stx +11 -0
- package/src/ui/listbox/ListboxOption.stx +57 -0
- package/src/ui/listbox/ListboxOptions.stx +16 -0
- package/src/ui/listbox/README.md +143 -0
- package/src/ui/listbox/index.ts +39 -0
- package/src/ui/navigator/Navigator.stx +138 -0
- package/src/ui/navigator/README.md +352 -0
- package/src/ui/navigator/index.ts +20 -0
- package/src/ui/notification/Notification.stx +71 -0
- package/src/ui/notification/README.md +118 -0
- package/src/ui/notification/index.ts +12 -0
- package/src/ui/pagination/Pagination.stx +183 -0
- package/src/ui/pagination/index.ts +11 -0
- package/src/ui/payment/Checkout.stx +39 -0
- package/src/ui/payment/DefaultPaymentMethod.stx +62 -0
- package/src/ui/payment/PaymentMethods.stx +113 -0
- package/src/ui/payment/README.md +427 -0
- package/src/ui/payment/SubscriptionCheckout.stx +353 -0
- package/src/ui/payment/index.ts +59 -0
- package/src/ui/popover/Popover.stx +24 -0
- package/src/ui/popover/PopoverButton.stx +23 -0
- package/src/ui/popover/PopoverPanel.stx +23 -0
- package/src/ui/popover/README.md +95 -0
- package/src/ui/popover/index.ts +22 -0
- package/src/ui/portal/Portal.stx +74 -0
- package/src/ui/portal/index.ts +6 -0
- package/src/ui/progress/Progress.stx +142 -0
- package/src/ui/progress/index.ts +12 -0
- package/src/ui/radio/Radio.stx +81 -0
- package/src/ui/radio/index.ts +15 -0
- package/src/ui/radio-group/README.md +145 -0
- package/src/ui/radio-group/RadioGroup.stx +13 -0
- package/src/ui/radio-group/RadioGroupDescription.stx +11 -0
- package/src/ui/radio-group/RadioGroupLabel.stx +11 -0
- package/src/ui/radio-group/RadioGroupOption.stx +51 -0
- package/src/ui/radio-group/index.ts +30 -0
- package/src/ui/select/README.md +204 -0
- package/src/ui/select/Select.stx +89 -0
- package/src/ui/select/index.ts +21 -0
- package/src/ui/sidebar/Sidebar.stx +288 -0
- package/src/ui/sidebar/SidebarFooter.stx +95 -0
- package/src/ui/sidebar/SidebarHeader.stx +100 -0
- package/src/ui/sidebar/SidebarItem.stx +118 -0
- package/src/ui/sidebar/SidebarSection.stx +108 -0
- package/src/ui/sidebar/index.ts +175 -0
- package/src/ui/skeleton/Skeleton.stx +48 -0
- package/src/ui/skeleton/index.ts +11 -0
- package/src/ui/spinner/Spinner.stx +109 -0
- package/src/ui/spinner/index.ts +9 -0
- package/src/ui/stepper/README.md +36 -0
- package/src/ui/stepper/Stepper.stx +17 -0
- package/src/ui/stepper/StepperStep.stx +53 -0
- package/src/ui/stepper/index.ts +19 -0
- package/src/ui/storage/README.md +193 -0
- package/src/ui/storage/index.ts +347 -0
- package/src/ui/switch/README.md +133 -0
- package/src/ui/switch/Switch.stx +74 -0
- package/src/ui/switch/index.ts +10 -0
- package/src/ui/table/README.md +39 -0
- package/src/ui/table/Table.stx +14 -0
- package/src/ui/table/TableBody.stx +10 -0
- package/src/ui/table/TableCell.stx +10 -0
- package/src/ui/table/TableHead.stx +10 -0
- package/src/ui/table/TableHeader.stx +13 -0
- package/src/ui/table/TableRow.stx +11 -0
- package/src/ui/table/index.ts +35 -0
- package/src/ui/tabs/Tabs.stx +145 -0
- package/src/ui/tabs/index.ts +16 -0
- package/src/ui/teleport/Teleport.stx +101 -0
- package/src/ui/teleport/index.ts +7 -0
- package/src/ui/textarea/Textarea.stx +119 -0
- package/src/ui/textarea/index.ts +20 -0
- package/src/ui/tooltip/Tooltip.stx +76 -0
- package/src/ui/tooltip/index.ts +10 -0
- package/src/ui/transition/README.md +30 -0
- package/src/ui/transition/Transition.stx +174 -0
- package/src/ui/transition/index.ts +25 -0
- package/src/ui/video/README.md +48 -0
- package/src/ui/video/Video.stx +45 -0
- package/src/ui/video/index.ts +16 -0
- package/src/ui/virtual-list/VirtualList.stx +83 -0
- package/src/ui/virtual-list/index.ts +10 -0
- package/src/ui/virtual-table/VirtualTable.stx +113 -0
- package/src/ui/virtual-table/index.ts +21 -0
- package/src/utils/PROP_VALIDATION.md +458 -0
- package/src/utils/THEME_SYSTEM.md +702 -0
- package/src/utils/accessibility.ts +841 -0
- package/src/utils/animation/easing.ts +368 -0
- package/src/utils/animation/helpers.ts +223 -0
- package/src/utils/animation/index.ts +5 -0
- package/src/utils/animation/keyframes.ts +230 -0
- package/src/utils/error-handling.ts +473 -0
- package/src/utils/highlighter.ts +93 -0
- package/src/utils/i18n.ts +634 -0
- package/src/utils/keep-alive.ts +818 -0
- package/src/utils/lazy-loading.ts +530 -0
- package/src/utils/prop-validation.ts +478 -0
- 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>
|