@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,24 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const className = $props.className || ''
|
|
3
|
+
export const as = $props.as || 'div'
|
|
4
|
+
export let isOpen = false
|
|
5
|
+
|
|
6
|
+
export function toggle() {
|
|
7
|
+
isOpen = !isOpen
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function close() {
|
|
11
|
+
isOpen = false
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function open() {
|
|
15
|
+
isOpen = true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const baseClasses = 'relative inline-block text-left'
|
|
19
|
+
const dropdownClasses = `${baseClasses} ${className}`.trim()
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<component :is="{{ as }}" class="{{ dropdownClasses }}">
|
|
23
|
+
<slot :isOpen="isOpen" :toggle="toggle" :close="close" :open="open" />
|
|
24
|
+
</component>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const className = $props.className || ''
|
|
3
|
+
export const as = $props.as || 'button'
|
|
4
|
+
export const disabled = $props.disabled || false
|
|
5
|
+
export const onClick = $props.onClick
|
|
6
|
+
|
|
7
|
+
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()
|
|
9
|
+
|
|
10
|
+
export function handleClick(e) {
|
|
11
|
+
if (disabled) return
|
|
12
|
+
onClick?.(e)
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<component
|
|
17
|
+
:is="{{ as }}"
|
|
18
|
+
class="{{ buttonClasses }}"
|
|
19
|
+
@if(disabled)disabled aria-disabled="true"@endif
|
|
20
|
+
@click="handleClick"
|
|
21
|
+
>
|
|
22
|
+
<slot />
|
|
23
|
+
</component>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const className = $props.className || ''
|
|
3
|
+
export const as = $props.as || 'div'
|
|
4
|
+
export const disabled = $props.disabled || false
|
|
5
|
+
export const onClick = $props.onClick
|
|
6
|
+
|
|
7
|
+
export let active = false
|
|
8
|
+
|
|
9
|
+
const baseClasses = 'group flex w-full items-center px-2 py-2 text-sm transition-colors'
|
|
10
|
+
const stateClasses = active
|
|
11
|
+
? 'bg-blue-500 text-white dark:bg-blue-600'
|
|
12
|
+
: 'text-neutral-900 dark:text-neutral-100'
|
|
13
|
+
const disabledClasses = disabled
|
|
14
|
+
? 'opacity-50 cursor-not-allowed'
|
|
15
|
+
: 'cursor-pointer'
|
|
16
|
+
|
|
17
|
+
const itemClasses = `${baseClasses} ${stateClasses} ${disabledClasses} ${className}`.trim()
|
|
18
|
+
|
|
19
|
+
export function handleClick(e) {
|
|
20
|
+
if (disabled) return
|
|
21
|
+
onClick?.(e)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function handleMouseEnter() {
|
|
25
|
+
if (!disabled) active = true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function handleMouseLeave() {
|
|
29
|
+
active = false
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<component
|
|
34
|
+
:is="{{ as }}"
|
|
35
|
+
class="{{ itemClasses }}"
|
|
36
|
+
role="menuitem"
|
|
37
|
+
@if(disabled)aria-disabled="true"@endif
|
|
38
|
+
@click="handleClick"
|
|
39
|
+
@mouseenter="handleMouseEnter"
|
|
40
|
+
@mouseleave="handleMouseLeave"
|
|
41
|
+
>
|
|
42
|
+
<slot :active="active" :disabled="disabled" />
|
|
43
|
+
</component>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const className = $props.className || ''
|
|
3
|
+
export const as = $props.as || 'div'
|
|
4
|
+
export const static_ = $props.static || false
|
|
5
|
+
|
|
6
|
+
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()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<component
|
|
11
|
+
:is="{{ as }}"
|
|
12
|
+
class="{{ itemsClasses }}"
|
|
13
|
+
role="menu"
|
|
14
|
+
aria-orientation="vertical"
|
|
15
|
+
tabindex="-1"
|
|
16
|
+
>
|
|
17
|
+
<slot />
|
|
18
|
+
</component>
|
|
@@ -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,66 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const author = $props.author || '@chrisbbreuer'
|
|
3
|
+
export const authorUrl = $props.authorUrl || 'https://github.com/chrisbbreuer'
|
|
4
|
+
export const project = $props.project || '@stacksjs'
|
|
5
|
+
export const projectUrl = $props.projectUrl || 'https://github.com/stacksjs/stacks'
|
|
6
|
+
export const twitterUrl = $props.twitterUrl || 'https://twitter.com/chrisbbreuer'
|
|
7
|
+
export const sponsorUrl = $props.sponsorUrl || 'https://github.com/sponsors/chrisbbreuer'
|
|
8
|
+
export const coffeeUrl = $props.coffeeUrl || 'https://www.buymeacoffee.com/xlbd'
|
|
9
|
+
|
|
10
|
+
export const currentYear = new Date().getFullYear()
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<footer class="flex items-center justify-center mt-16 w-full pb-4 text-neutral-600 dark:text-neutral-400 opacity-60">
|
|
14
|
+
<div class="flex flex-col items-center justify-center">
|
|
15
|
+
<p class="text-sm">
|
|
16
|
+
With 💙 by
|
|
17
|
+
<a href="{{ authorUrl }}" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors">
|
|
18
|
+
{{ author }}
|
|
19
|
+
</a>
|
|
20
|
+
&
|
|
21
|
+
<a href="{{ projectUrl }}" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors">
|
|
22
|
+
{{ project }}
|
|
23
|
+
</a>
|
|
24
|
+
<span> © {{ currentYear }}</span>
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
<div class="mt-2 flex flex-col items-center sm:flex-row space-x-1 space-y-2 sm:space-y-0">
|
|
28
|
+
<div class="flex items-center space-x-1">
|
|
29
|
+
<svg class="w-4 h-4 text-blue-500" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
30
|
+
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
|
|
31
|
+
</svg>
|
|
32
|
+
<span class="text-sm">
|
|
33
|
+
<a href="{{ twitterUrl }}" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors">
|
|
34
|
+
Follow me on Twitter
|
|
35
|
+
</a>
|
|
36
|
+
</span>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<span class="hidden px-2 text-neutral-400 sm:block">|</span>
|
|
40
|
+
|
|
41
|
+
<div class="flex items-center space-x-1">
|
|
42
|
+
<svg class="w-4 h-4 text-blue-500" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
43
|
+
<path d="M20.216 6.415l-.132-.666c-.119-.598-.388-1.163-1.001-1.379-.197-.069-.42-.098-.57-.241-.152-.143-.196-.366-.231-.572-.065-.378-.125-.756-.192-1.133-.057-.325-.102-.69-.25-.987-.195-.4-.597-.634-.996-.788a5.723 5.723 0 00-.626-.194c-1-.263-2.05-.36-3.077-.416a25.834 25.834 0 00-3.7.062c-.915.083-1.88.184-2.75.5-.318.116-.646.256-.888.501-.297.302-.393.77-.177 1.146.154.267.415.456.692.58.36.162.737.284 1.123.366 1.075.238 2.189.331 3.287.37 1.218.05 2.437.01 3.65-.118.299-.033.598-.073.896-.119.352-.054.578-.513.474-.834-.124-.383-.457-.531-.834-.473-.466.074-.96.108-1.382.146-1.177.08-2.358.082-3.536.006a22.228 22.228 0 01-1.157-.107c-.086-.01-.18-.025-.258-.036-.243-.036-.484-.08-.724-.13-.111-.027-.111-.185 0-.212h.005c.277-.06.557-.108.838-.147h.002c.131-.009.263-.032.394-.048a25.076 25.076 0 013.426-.12c.674.019 1.347.067 2.017.144l.228.031c.267.04.533.088.798.145.392.085.895.113 1.07.542.055.137.08.288.111.431l.319 1.484a.237.237 0 01-.199.284h-.003c-.037.006-.075.01-.112.015a36.704 36.704 0 01-4.743.295 37.059 37.059 0 01-4.699-.304c-.14-.017-.293-.042-.417-.06-.326-.048-.649-.108-.973-.161-.393-.065-.768-.032-1.123.161-.29.16-.527.404-.675.701-.154.316-.199.66-.267 1-.069.34-.176.707-.135 1.056.087.753.613 1.365 1.37 1.502a39.69 39.69 0 0011.343.376.483.483 0 01.535.53l-.071.697-1.018 9.907c-.041.41-.047.832-.125 1.237-.122.637-.553 1.028-1.182 1.171-.577.131-1.165.2-1.756.205-.656.004-1.31-.025-1.966-.022-.699.004-1.556-.06-2.095-.58-.475-.458-.54-1.174-.605-1.793l-.731-7.013-.322-3.094c-.037-.351-.286-.695-.678-.678-.336.015-.718.3-.678.679l.228 2.185.949 9.112c.147 1.344 1.174 2.068 2.446 2.272.742.12 1.503.144 2.257.156.966.016 1.942.053 2.892-.122 1.408-.258 2.465-1.198 2.616-2.657.34-3.332.683-6.663 1.024-9.995l.215-2.087a.484.484 0 01.39-.426c.402-.078.787-.212 1.074-.518.455-.488.546-1.124.385-1.766zm-1.478.772c-.145.137-.363.201-.578.233-2.416.359-4.866.54-7.308.46-1.748-.06-3.477-.254-5.207-.498-.17-.024-.353-.055-.47-.18-.22-.236-.111-.71-.054-.995.052-.26.152-.609.463-.646.484-.057 1.046.148 1.526.22.577.088 1.156.16 1.737.212 2.48.226 5.002.19 7.472-.14.45-.06.899-.13 1.345-.21.399-.072.84-.206 1.08.206.166.281.188.657.162.974a.544.544 0 01-.169.364zm-6.159 3.9c-.862.37-1.84.788-3.109.788a5.884 5.884 0 01-1.569-.217l.877 9.004c.065.78.717 1.38 1.5 1.38 0 0 1.243.065 1.658.065.447 0 1.786-.065 1.786-.065.783 0 1.434-.6 1.499-1.38l.94-9.95a3.996 3.996 0 00-1.322-.238c-.826 0-1.491.284-2.26.613z" />
|
|
44
|
+
</svg>
|
|
45
|
+
<span class="text-sm">
|
|
46
|
+
<a href="{{ coffeeUrl }}" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors">
|
|
47
|
+
Buy me a coffee
|
|
48
|
+
</a>
|
|
49
|
+
</span>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<span class="hidden px-2 text-neutral-400 sm:block">|</span>
|
|
53
|
+
|
|
54
|
+
<div class="flex items-center space-x-1">
|
|
55
|
+
<svg class="w-4 h-4 text-blue-500" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
56
|
+
<path d="m11.645 20.91-.007-.003-.022-.012a15.247 15.247 0 0 1-.383-.218 25.18 25.18 0 0 1-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0 1 12 5.052 5.5 5.5 0 0 1 16.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 0 1-4.244 3.17 15.247 15.247 0 0 1-.383.219l-.022.012-.007.004-.003.001a.752.752 0 0 1-.704 0l-.003-.001Z" />
|
|
57
|
+
</svg>
|
|
58
|
+
<span class="text-sm">
|
|
59
|
+
<a href="{{ sponsorUrl }}" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors">
|
|
60
|
+
Sponsor me on GitHub
|
|
61
|
+
</a>
|
|
62
|
+
</span>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</footer>
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const action = $props.action || ''
|
|
3
|
+
export const method = $props.method || 'POST'
|
|
4
|
+
export const validationSchema = $props.validationSchema || {}
|
|
5
|
+
export const initialValues = $props.initialValues || {}
|
|
6
|
+
export const validateOnChange = $props.validateOnChange ?? true
|
|
7
|
+
export const validateOnBlur = $props.validateOnBlur ?? true
|
|
8
|
+
export const onSubmit = $props.onSubmit
|
|
9
|
+
export const onError = $props.onError
|
|
10
|
+
export const className = $props.className || ''
|
|
11
|
+
|
|
12
|
+
let values = { ...initialValues }
|
|
13
|
+
let errors = {}
|
|
14
|
+
let touched = {}
|
|
15
|
+
let isSubmitting = false
|
|
16
|
+
let isValidating = false
|
|
17
|
+
let submitCount = 0
|
|
18
|
+
|
|
19
|
+
// Validate a single field
|
|
20
|
+
function validateField(name, value) {
|
|
21
|
+
const fieldSchema = validationSchema[name]
|
|
22
|
+
if (!fieldSchema)
|
|
23
|
+
return null
|
|
24
|
+
|
|
25
|
+
// Required validation
|
|
26
|
+
if (fieldSchema.required && !value) {
|
|
27
|
+
return fieldSchema.requiredMessage || `${name} is required`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Min length validation
|
|
31
|
+
if (fieldSchema.minLength && value.length < fieldSchema.minLength) {
|
|
32
|
+
return fieldSchema.minLengthMessage || `${name} must be at least ${fieldSchema.minLength} characters`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Max length validation
|
|
36
|
+
if (fieldSchema.maxLength && value.length > fieldSchema.maxLength) {
|
|
37
|
+
return fieldSchema.maxLengthMessage || `${name} must be at most ${fieldSchema.maxLength} characters`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Pattern validation
|
|
41
|
+
if (fieldSchema.pattern && !fieldSchema.pattern.test(value)) {
|
|
42
|
+
return fieldSchema.patternMessage || `${name} is invalid`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Custom validation
|
|
46
|
+
if (fieldSchema.validate && typeof fieldSchema.validate === 'function') {
|
|
47
|
+
return fieldSchema.validate(value, values)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Validate all fields
|
|
54
|
+
function validateForm() {
|
|
55
|
+
const newErrors = {}
|
|
56
|
+
let isValid = true
|
|
57
|
+
|
|
58
|
+
Object.keys(validationSchema).forEach((name) => {
|
|
59
|
+
const error = validateField(name, values[name])
|
|
60
|
+
if (error) {
|
|
61
|
+
newErrors[name] = error
|
|
62
|
+
isValid = false
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
errors = newErrors
|
|
67
|
+
return isValid
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Handle field change
|
|
71
|
+
function handleChange(event) {
|
|
72
|
+
const { name, value } = event.target
|
|
73
|
+
values[name] = value
|
|
74
|
+
|
|
75
|
+
if (validateOnChange && touched[name]) {
|
|
76
|
+
const error = validateField(name, value)
|
|
77
|
+
if (error) {
|
|
78
|
+
errors[name] = error
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
delete errors[name]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Handle field blur
|
|
87
|
+
function handleBlur(event) {
|
|
88
|
+
const { name } = event.target
|
|
89
|
+
touched[name] = true
|
|
90
|
+
|
|
91
|
+
if (validateOnBlur) {
|
|
92
|
+
const error = validateField(name, values[name])
|
|
93
|
+
if (error) {
|
|
94
|
+
errors[name] = error
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
delete errors[name]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Handle form submission
|
|
103
|
+
async function handleSubmit(event) {
|
|
104
|
+
event.preventDefault()
|
|
105
|
+
submitCount++
|
|
106
|
+
|
|
107
|
+
// Mark all fields as touched
|
|
108
|
+
Object.keys(validationSchema).forEach((name) => {
|
|
109
|
+
touched[name] = true
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// Validate
|
|
113
|
+
isValidating = true
|
|
114
|
+
const isValid = validateForm()
|
|
115
|
+
isValidating = false
|
|
116
|
+
|
|
117
|
+
if (!isValid) {
|
|
118
|
+
if (onError) {
|
|
119
|
+
onError(errors)
|
|
120
|
+
}
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Submit
|
|
125
|
+
isSubmitting = true
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
if (onSubmit) {
|
|
129
|
+
await onSubmit(values, { setErrors, setValues, resetForm })
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
if (onError) {
|
|
134
|
+
onError(error)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
isSubmitting = false
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Helper functions
|
|
143
|
+
function setErrors(newErrors) {
|
|
144
|
+
errors = { ...errors, ...newErrors }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function setValues(newValues) {
|
|
148
|
+
values = { ...values, ...newValues }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function resetForm() {
|
|
152
|
+
values = { ...initialValues }
|
|
153
|
+
errors = {}
|
|
154
|
+
touched = {}
|
|
155
|
+
isSubmitting = false
|
|
156
|
+
submitCount = 0
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getFieldProps(name) {
|
|
160
|
+
return {
|
|
161
|
+
name,
|
|
162
|
+
value: values[name] || '',
|
|
163
|
+
error: errors[name],
|
|
164
|
+
touched: touched[name],
|
|
165
|
+
onChange: handleChange,
|
|
166
|
+
onBlur: handleBlur,
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const formClasses = `space-y-6 ${className}`.trim()
|
|
171
|
+
export const errorClasses = `mt-1 text-sm text-red-600 dark:text-red-400`.trim()
|
|
172
|
+
</script>
|
|
173
|
+
|
|
174
|
+
<form
|
|
175
|
+
class="{{ formClasses }}"
|
|
176
|
+
@if(action)action="{{ action }}"@endif
|
|
177
|
+
method="{{ method }}"
|
|
178
|
+
onsubmit="handleSubmit(event)"
|
|
179
|
+
novalidate
|
|
180
|
+
>
|
|
181
|
+
<slot
|
|
182
|
+
values="{{ values }}"
|
|
183
|
+
errors="{{ errors }}"
|
|
184
|
+
touched="{{ touched }}"
|
|
185
|
+
isSubmitting="{{ isSubmitting }}"
|
|
186
|
+
isValidating="{{ isValidating }}"
|
|
187
|
+
submitCount="{{ submitCount }}"
|
|
188
|
+
getFieldProps="{{ getFieldProps }}"
|
|
189
|
+
setErrors="{{ setErrors }}"
|
|
190
|
+
setValues="{{ setValues }}"
|
|
191
|
+
resetForm="{{ resetForm }}"
|
|
192
|
+
/>
|
|
193
|
+
</form>
|
|
194
|
+
|
|
195
|
+
<script>
|
|
196
|
+
// Export context for child components
|
|
197
|
+
if (typeof window !== 'undefined') {
|
|
198
|
+
window.__formContext = {
|
|
199
|
+
values,
|
|
200
|
+
errors,
|
|
201
|
+
touched,
|
|
202
|
+
isSubmitting,
|
|
203
|
+
isValidating,
|
|
204
|
+
getFieldProps,
|
|
205
|
+
handleChange,
|
|
206
|
+
handleBlur,
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
</script>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const title = $props.title || 'Component'
|
|
3
|
+
export const description = $props.description || 'A modern component built with STX'
|
|
4
|
+
export const githubUrl = $props.githubUrl || '#'
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<div class="flex flex-col items-center gap-3">
|
|
8
|
+
<h1 class="text-5xl font-bold mb-3 -mt-5 bg-gradient-to-r from-blue-500 to-purple-600 bg-clip-text text-transparent">
|
|
9
|
+
{{ title }}
|
|
10
|
+
</h1>
|
|
11
|
+
<p class="mb-3 mt-0 text-lg text-neutral-600 dark:text-neutral-400">
|
|
12
|
+
{{ description }}
|
|
13
|
+
</p>
|
|
14
|
+
<div class="flex gap-2">
|
|
15
|
+
<slot name="demo" />
|
|
16
|
+
<a
|
|
17
|
+
href="{{ githubUrl }}"
|
|
18
|
+
target="_blank"
|
|
19
|
+
rel="noopener noreferrer"
|
|
20
|
+
class="inline-flex items-center justify-center h-10 px-8 rounded-md text-sm font-semibold bg-gradient-to-b from-white to-neutral-100 dark:from-neutral-800 dark:to-neutral-900 text-neutral-900 dark:text-white shadow-md hover:shadow-lg transition-all duration-200"
|
|
21
|
+
>
|
|
22
|
+
GitHub
|
|
23
|
+
</a>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
@@ -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>
|