@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,353 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const products = $props.products || []
|
|
3
|
+
export const stripePublicKey = $props.stripePublicKey || ''
|
|
4
|
+
export const apiUrl = $props.apiUrl || ''
|
|
5
|
+
export const onSubmit = $props.onSubmit
|
|
6
|
+
export const onRemoveProduct = $props.onRemoveProduct
|
|
7
|
+
export const shipping = $props.shipping || 0
|
|
8
|
+
export const taxes = $props.taxes || 0
|
|
9
|
+
export const className = $props.className || ''
|
|
10
|
+
|
|
11
|
+
// Calculate totals
|
|
12
|
+
export const calculateSubtotal = () => {
|
|
13
|
+
return products.reduce((sum, product) => sum + (product.price * (product.quantity || 1)), 0)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const total = calculateSubtotal() + shipping + taxes
|
|
17
|
+
|
|
18
|
+
export const containerClasses = `bg-gray-50 dark:bg-blue-gray-900 ${className}`.trim()
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<div class="{{ containerClasses }}">
|
|
22
|
+
<div class="mx-auto max-w-2xl px-4 pb-24 pt-16 lg:max-w-7xl lg:px-8 sm:px-6">
|
|
23
|
+
<h2 class="sr-only">Checkout</h2>
|
|
24
|
+
|
|
25
|
+
<form class="lg:grid lg:grid-cols-2 lg:gap-x-12 xl:gap-x-16" onsubmit="event.preventDefault(); handleCheckoutSubmit(event)">
|
|
26
|
+
<!-- Left Column - Contact & Payment -->
|
|
27
|
+
<div>
|
|
28
|
+
<!-- Contact Information -->
|
|
29
|
+
<div>
|
|
30
|
+
<h2 class="text-lg text-gray-900 dark:text-gray-100 font-medium">
|
|
31
|
+
Contact information
|
|
32
|
+
</h2>
|
|
33
|
+
|
|
34
|
+
<div class="mt-4">
|
|
35
|
+
<label for="email-address" class="block text-sm text-gray-700 dark:text-gray-300 font-medium">
|
|
36
|
+
Email address
|
|
37
|
+
</label>
|
|
38
|
+
<div class="mt-1">
|
|
39
|
+
<input
|
|
40
|
+
id="email-address"
|
|
41
|
+
type="email"
|
|
42
|
+
name="email-address"
|
|
43
|
+
autocomplete="email"
|
|
44
|
+
required
|
|
45
|
+
class="block w-full border border-gray-300 dark:border-gray-600 rounded-md p-2 shadow-sm bg-white dark:bg-blue-gray-700 text-gray-900 dark:text-gray-100 focus:border-indigo-500 dark:focus:border-indigo-400 sm:text-sm focus:ring-indigo-500 dark:focus:ring-indigo-400"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<!-- Billing Information -->
|
|
52
|
+
<div class="mt-10 border-t border-gray-200 dark:border-gray-700 pt-10">
|
|
53
|
+
<h2 class="text-lg text-gray-900 dark:text-gray-100 font-medium">
|
|
54
|
+
Billing information
|
|
55
|
+
</h2>
|
|
56
|
+
|
|
57
|
+
<div class="mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:gap-x-4">
|
|
58
|
+
<div class="sm:col-span-2">
|
|
59
|
+
<label for="card-name" class="block text-sm text-gray-700 dark:text-gray-300 font-medium">
|
|
60
|
+
Name on card
|
|
61
|
+
</label>
|
|
62
|
+
<div class="mt-1">
|
|
63
|
+
<input
|
|
64
|
+
type="text"
|
|
65
|
+
id="card-name"
|
|
66
|
+
name="card-name"
|
|
67
|
+
autocomplete="cc-name"
|
|
68
|
+
required
|
|
69
|
+
class="block w-full border border-gray-300 dark:border-gray-600 rounded-md p-2 shadow-sm bg-white dark:bg-blue-gray-700 text-gray-900 dark:text-gray-100 focus:border-indigo-500 sm:text-sm focus:ring-indigo-500"
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<!-- Stripe Element Mount Point -->
|
|
75
|
+
<div class="sm:col-span-2">
|
|
76
|
+
<div id="payment-element" class="mt-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-blue-gray-700 p-3"></div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<!-- Payment Type -->
|
|
82
|
+
<div class="mt-10 border-t border-gray-200 dark:border-gray-700 pt-10">
|
|
83
|
+
<h2 class="text-lg text-gray-900 dark:text-gray-100 font-medium">
|
|
84
|
+
Payment
|
|
85
|
+
</h2>
|
|
86
|
+
|
|
87
|
+
<fieldset class="mt-4">
|
|
88
|
+
<legend class="sr-only">Payment type</legend>
|
|
89
|
+
<div class="sm:flex sm:items-center space-y-4 sm:space-x-10 sm:space-y-0">
|
|
90
|
+
<div class="flex items-center">
|
|
91
|
+
<input
|
|
92
|
+
id="credit-card"
|
|
93
|
+
name="payment-type"
|
|
94
|
+
type="radio"
|
|
95
|
+
checked
|
|
96
|
+
class="h-4 w-4 border-gray-300 dark:border-gray-600 text-indigo-600 dark:text-indigo-400 focus:ring-indigo-500 dark:focus:ring-indigo-400"
|
|
97
|
+
/>
|
|
98
|
+
<label for="credit-card" class="ml-3 block text-sm text-gray-700 dark:text-gray-300 font-medium">
|
|
99
|
+
Credit card
|
|
100
|
+
</label>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</fieldset>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<!-- Right Column - Order Summary -->
|
|
108
|
+
<div class="mt-10 lg:mt-0">
|
|
109
|
+
<h2 class="text-lg text-gray-900 dark:text-gray-100 font-medium">
|
|
110
|
+
Order summary
|
|
111
|
+
</h2>
|
|
112
|
+
|
|
113
|
+
<div class="mt-4 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-blue-gray-800 shadow-sm">
|
|
114
|
+
<h3 class="sr-only">Items in your cart</h3>
|
|
115
|
+
|
|
116
|
+
<!-- Product List -->
|
|
117
|
+
<ul role="list" class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
118
|
+
@foreach(product in products)
|
|
119
|
+
<li class="flex px-4 py-6 sm:px-6">
|
|
120
|
+
<div class="flex-shrink-0">
|
|
121
|
+
@if(product.images)
|
|
122
|
+
<img src="{{ product.images }}" alt="{{ product.name }}" class="w-20 rounded-md" />
|
|
123
|
+
@else
|
|
124
|
+
<div class="w-20 h-20 bg-gray-200 dark:bg-gray-700 rounded-md flex items-center justify-center">
|
|
125
|
+
<svg class="h-8 w-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
126
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
127
|
+
</svg>
|
|
128
|
+
</div>
|
|
129
|
+
@endif
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div class="ml-6 flex flex-1 flex-col">
|
|
133
|
+
<div class="flex">
|
|
134
|
+
<div class="min-w-0 flex-1">
|
|
135
|
+
<h4 class="text-sm">
|
|
136
|
+
<a href="#" class="text-gray-700 dark:text-gray-300 font-medium hover:text-gray-800 dark:hover:text-gray-200">
|
|
137
|
+
{{ product.name }}
|
|
138
|
+
</a>
|
|
139
|
+
</h4>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
@if(onRemoveProduct)
|
|
143
|
+
<div class="ml-4 flow-root flex-shrink-0">
|
|
144
|
+
<button
|
|
145
|
+
type="button"
|
|
146
|
+
class="flex items-center justify-center bg-white dark:bg-blue-gray-700 p-2.5 text-gray-400 dark:text-gray-500 -m-2.5 hover:text-gray-500 dark:hover:text-gray-400"
|
|
147
|
+
onclick="handleRemoveProduct('{{ product.id }}')"
|
|
148
|
+
title="Remove"
|
|
149
|
+
>
|
|
150
|
+
<span class="sr-only">Remove</span>
|
|
151
|
+
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
152
|
+
<path fill-rule="evenodd" d="M8.75 1A2.75 2.75 0 0 0 6 3.75v.443c-.795.077-1.584.176-2.365.298a.75.75 0 1 0 .23 1.482l.149-.022.841 10.518A2.75 2.75 0 0 0 7.596 19h4.807a2.75 2.75 0 0 0 2.742-2.53l.841-10.52.149.023a.75.75 0 0 0 .23-1.482A41.03 41.03 0 0 0 14 4.193V3.75A2.75 2.75 0 0 0 11.25 1h-2.5ZM10 4c.84 0 1.673.025 2.5.075V3.75c0-.69-.56-1.25-1.25-1.25h-2.5c-.69 0-1.25.56-1.25 1.25v.325C8.327 4.025 9.16 4 10 4ZM8.58 7.72a.75.75 0 0 0-1.5.06l.3 7.5a.75.75 0 1 0 1.5-.06l-.3-7.5Zm4.34.06a.75.75 0 1 0-1.5-.06l-.3 7.5a.75.75 0 1 0 1.5.06l.3-7.5Z" clip-rule="evenodd" />
|
|
153
|
+
</svg>
|
|
154
|
+
</button>
|
|
155
|
+
</div>
|
|
156
|
+
@endif
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<div class="flex flex-1 items-end justify-between pt-2">
|
|
160
|
+
<p class="mt-1 text-sm text-gray-900 dark:text-gray-100 font-medium">
|
|
161
|
+
${{ (product.price / 100).toFixed(2) }}
|
|
162
|
+
</p>
|
|
163
|
+
|
|
164
|
+
<div class="ml-4">
|
|
165
|
+
<label for="quantity-{{ product.id }}" class="sr-only">Quantity</label>
|
|
166
|
+
<select
|
|
167
|
+
id="quantity-{{ product.id }}"
|
|
168
|
+
name="quantity"
|
|
169
|
+
class="border border-gray-300 dark:border-gray-600 rounded-md text-left text-base text-gray-700 dark:text-gray-300 font-medium shadow-sm bg-white dark:bg-blue-gray-700 focus:border-indigo-500 sm:text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
170
|
+
>
|
|
171
|
+
<option value="1" {{ (product.quantity || 1) === 1 ? 'selected' : '' }}>1</option>
|
|
172
|
+
<option value="2" {{ (product.quantity || 1) === 2 ? 'selected' : '' }}>2</option>
|
|
173
|
+
<option value="3" {{ (product.quantity || 1) === 3 ? 'selected' : '' }}>3</option>
|
|
174
|
+
<option value="4" {{ (product.quantity || 1) === 4 ? 'selected' : '' }}>4</option>
|
|
175
|
+
<option value="5" {{ (product.quantity || 1) === 5 ? 'selected' : '' }}>5</option>
|
|
176
|
+
<option value="6" {{ (product.quantity || 1) === 6 ? 'selected' : '' }}>6</option>
|
|
177
|
+
<option value="7" {{ (product.quantity || 1) === 7 ? 'selected' : '' }}>7</option>
|
|
178
|
+
<option value="8" {{ (product.quantity || 1) === 8 ? 'selected' : '' }}>8</option>
|
|
179
|
+
</select>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</li>
|
|
184
|
+
@endforeach
|
|
185
|
+
</ul>
|
|
186
|
+
|
|
187
|
+
<!-- Summary Totals -->
|
|
188
|
+
<dl class="border-t border-gray-200 dark:border-gray-700 px-4 py-6 space-y-6 sm:px-6">
|
|
189
|
+
<div class="flex items-center justify-between">
|
|
190
|
+
<dt class="text-sm text-gray-600 dark:text-gray-400">Subtotal</dt>
|
|
191
|
+
<dd class="text-sm text-gray-900 dark:text-gray-100 font-medium">
|
|
192
|
+
${{ (calculateSubtotal() / 100).toFixed(2) }}
|
|
193
|
+
</dd>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
@if(shipping > 0)
|
|
197
|
+
<div class="flex items-center justify-between">
|
|
198
|
+
<dt class="text-sm text-gray-600 dark:text-gray-400">Shipping</dt>
|
|
199
|
+
<dd class="text-sm text-gray-900 dark:text-gray-100 font-medium">
|
|
200
|
+
${{ (shipping / 100).toFixed(2) }}
|
|
201
|
+
</dd>
|
|
202
|
+
</div>
|
|
203
|
+
@endif
|
|
204
|
+
|
|
205
|
+
@if(taxes > 0)
|
|
206
|
+
<div class="flex items-center justify-between">
|
|
207
|
+
<dt class="text-sm text-gray-600 dark:text-gray-400">Taxes</dt>
|
|
208
|
+
<dd class="text-sm text-gray-900 dark:text-gray-100 font-medium">
|
|
209
|
+
${{ (taxes / 100).toFixed(2) }}
|
|
210
|
+
</dd>
|
|
211
|
+
</div>
|
|
212
|
+
@endif
|
|
213
|
+
|
|
214
|
+
<div class="flex items-center justify-between border-t border-gray-200 dark:border-gray-700 pt-6">
|
|
215
|
+
<dt class="text-base text-gray-900 dark:text-gray-100 font-medium">Total</dt>
|
|
216
|
+
<dd class="text-base text-gray-900 dark:text-gray-100 font-medium">
|
|
217
|
+
${{ (total / 100).toFixed(2) }}
|
|
218
|
+
</dd>
|
|
219
|
+
</div>
|
|
220
|
+
</dl>
|
|
221
|
+
|
|
222
|
+
<!-- Submit Button -->
|
|
223
|
+
<div class="border-t border-gray-200 dark:border-gray-700 px-4 py-6 sm:px-6">
|
|
224
|
+
<button
|
|
225
|
+
type="submit"
|
|
226
|
+
class="w-full border border-transparent rounded-md bg-indigo-600 dark:bg-indigo-500 px-4 py-3 text-base text-white font-medium shadow-sm hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 focus:ring-offset-gray-50 dark:focus:ring-offset-gray-900"
|
|
227
|
+
>
|
|
228
|
+
Confirm order
|
|
229
|
+
</button>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</form>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<script>
|
|
238
|
+
// Stripe integration
|
|
239
|
+
let stripe = null
|
|
240
|
+
let elements = null
|
|
241
|
+
let paymentElement = null
|
|
242
|
+
|
|
243
|
+
function handleRemoveProduct(productId) {
|
|
244
|
+
if (onRemoveProduct) {
|
|
245
|
+
onRemoveProduct(productId)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function initializeStripe() {
|
|
250
|
+
if (!stripePublicKey) {
|
|
251
|
+
console.error('Stripe public key is required')
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
// Load Stripe.js
|
|
257
|
+
if (typeof window !== 'undefined' && window.Stripe) {
|
|
258
|
+
stripe = window.Stripe(stripePublicKey)
|
|
259
|
+
} else {
|
|
260
|
+
// Dynamically load Stripe.js
|
|
261
|
+
const script = document.createElement('script')
|
|
262
|
+
script.src = 'https://js.stripe.com/v3/'
|
|
263
|
+
script.async = true
|
|
264
|
+
script.onload = () => {
|
|
265
|
+
stripe = window.Stripe(stripePublicKey)
|
|
266
|
+
setupStripeElements()
|
|
267
|
+
}
|
|
268
|
+
document.head.appendChild(script)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (stripe) {
|
|
272
|
+
await setupStripeElements()
|
|
273
|
+
}
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error('Error initializing Stripe:', error)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function setupStripeElements() {
|
|
280
|
+
if (!stripe || !apiUrl) return
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
// Create payment intent for subscription
|
|
284
|
+
const response = await fetch(`${apiUrl}/stripe/create-subscription`, {
|
|
285
|
+
method: 'POST',
|
|
286
|
+
headers: {
|
|
287
|
+
'Content-Type': 'application/json',
|
|
288
|
+
'Accept': 'application/json',
|
|
289
|
+
},
|
|
290
|
+
body: JSON.stringify({
|
|
291
|
+
amount: total,
|
|
292
|
+
quantity: products.reduce((sum, p) => sum + (p.quantity || 1), 0),
|
|
293
|
+
}),
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
const paymentIntent = await response.json()
|
|
297
|
+
const { clientSecret } = paymentIntent
|
|
298
|
+
if (!clientSecret) {
|
|
299
|
+
console.error('No client secret returned from API')
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Create elements instance
|
|
304
|
+
elements = stripe.elements({
|
|
305
|
+
clientSecret,
|
|
306
|
+
appearance: {
|
|
307
|
+
theme: document.documentElement.classList.contains('dark') ? 'night' : 'stripe',
|
|
308
|
+
},
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
// Create and mount payment element
|
|
312
|
+
paymentElement = elements.create('payment')
|
|
313
|
+
paymentElement.mount('#payment-element')
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error('Error setting up Stripe elements:', error)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function handleCheckoutSubmit(event) {
|
|
320
|
+
if (!stripe || !elements) {
|
|
321
|
+
console.error('Stripe not initialized')
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
const { error } = await stripe.confirmPayment({
|
|
327
|
+
elements,
|
|
328
|
+
redirect: 'if_required',
|
|
329
|
+
confirmParams: {
|
|
330
|
+
return_url: window.location.href,
|
|
331
|
+
},
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
if (error) {
|
|
335
|
+
console.error('Payment error:', error)
|
|
336
|
+
alert(error.message)
|
|
337
|
+
} else {
|
|
338
|
+
// Success!
|
|
339
|
+
if (onSubmit) {
|
|
340
|
+
onSubmit({ success: true })
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.error('Checkout error:', error)
|
|
345
|
+
alert('An error occurred during checkout')
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Initialize on component mount
|
|
350
|
+
if (typeof window !== 'undefined') {
|
|
351
|
+
initializeStripe()
|
|
352
|
+
}
|
|
353
|
+
</script>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const checked = $props.checked || false
|
|
3
|
+
export const disabled = $props.disabled || false
|
|
4
|
+
export const size = $props.size || 'md'
|
|
5
|
+
export const className = $props.className || ''
|
|
6
|
+
export const onChange = $props.onChange
|
|
7
|
+
export const label = $props.label || ''
|
|
8
|
+
|
|
9
|
+
// Size configurations using headwind
|
|
10
|
+
const sizeConfig = {
|
|
11
|
+
sm: {
|
|
12
|
+
track: 'h-5 w-9',
|
|
13
|
+
thumb: 'h-4 w-4',
|
|
14
|
+
translate: 'translate-x-4',
|
|
15
|
+
},
|
|
16
|
+
md: {
|
|
17
|
+
track: 'h-[38px] w-[74px]',
|
|
18
|
+
thumb: 'h-[34px] w-[34px]',
|
|
19
|
+
translate: 'translate-x-9',
|
|
20
|
+
},
|
|
21
|
+
lg: {
|
|
22
|
+
track: 'h-12 w-24',
|
|
23
|
+
thumb: 'h-11 w-11',
|
|
24
|
+
translate: 'translate-x-12',
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const config = sizeConfig[size]
|
|
29
|
+
|
|
30
|
+
// Track classes
|
|
31
|
+
const trackBaseClasses = 'relative inline-flex shrink-0 cursor-pointer border-2 border-transparent rounded-full transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-300 dark:focus-visible:ring-offset-neutral-700'
|
|
32
|
+
const trackStateClasses = checked
|
|
33
|
+
? 'bg-blue-500 dark:bg-blue-600'
|
|
34
|
+
: 'bg-neutral-400 dark:bg-neutral-600'
|
|
35
|
+
const trackDisabledClasses = disabled
|
|
36
|
+
? 'opacity-50 cursor-not-allowed pointer-events-none'
|
|
37
|
+
: ''
|
|
38
|
+
|
|
39
|
+
export const trackClasses = `${trackBaseClasses} ${trackStateClasses} ${trackDisabledClasses} ${config.track} ${className}`.trim()
|
|
40
|
+
|
|
41
|
+
// Thumb classes
|
|
42
|
+
const thumbBaseClasses = 'pointer-events-none inline-block transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out'
|
|
43
|
+
const thumbPositionClasses = checked ? config.translate : 'translate-x-0'
|
|
44
|
+
export const thumbClasses = `${thumbBaseClasses} ${thumbPositionClasses} ${config.thumb}`.trim()
|
|
45
|
+
|
|
46
|
+
// Toggle function
|
|
47
|
+
export function toggle() {
|
|
48
|
+
if (disabled) return
|
|
49
|
+
onChange?.(!checked)
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<label class="inline-flex items-center gap-3 cursor-pointer">
|
|
54
|
+
@if(label)
|
|
55
|
+
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">
|
|
56
|
+
{{ label }}
|
|
57
|
+
</span>
|
|
58
|
+
@endif
|
|
59
|
+
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
role="switch"
|
|
63
|
+
aria-checked="{{ checked ? 'true' : 'false' }}"
|
|
64
|
+
class="{{ trackClasses }}"
|
|
65
|
+
@if(disabled)disabled aria-disabled="true"@endif
|
|
66
|
+
@click="toggle"
|
|
67
|
+
>
|
|
68
|
+
<span class="sr-only">@if(label){{ label }}@else Toggle switch@endif</span>
|
|
69
|
+
<span
|
|
70
|
+
aria-hidden="true"
|
|
71
|
+
class="{{ thumbClasses }}"
|
|
72
|
+
/>
|
|
73
|
+
</button>
|
|
74
|
+
</label>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const className = $props.className || ''
|
|
3
|
+
export const striped = $props.striped || false
|
|
4
|
+
export const hoverable = $props.hoverable !== false
|
|
5
|
+
|
|
6
|
+
const baseClasses = 'min-w-full divide-y divide-neutral-200 dark:divide-neutral-700'
|
|
7
|
+
const tableClasses = `${baseClasses} ${className}`.trim()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<div class="overflow-x-auto">
|
|
11
|
+
<table class="{{ tableClasses }}">
|
|
12
|
+
<slot :striped="striped" :hoverable="hoverable" />
|
|
13
|
+
</table>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const className = $props.className || ''
|
|
3
|
+
|
|
4
|
+
const baseClasses = 'bg-white divide-y divide-neutral-200 dark:bg-neutral-900 dark:divide-neutral-700'
|
|
5
|
+
const bodyClasses = `${baseClasses} ${className}`.trim()
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<tbody class="{{ bodyClasses }}">
|
|
9
|
+
<slot />
|
|
10
|
+
</tbody>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const className = $props.className || ''
|
|
3
|
+
|
|
4
|
+
const baseClasses = 'px-6 py-4 whitespace-nowrap text-sm text-neutral-900 dark:text-neutral-100'
|
|
5
|
+
const cellClasses = `${baseClasses} ${className}`.trim()
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<td class="{{ cellClasses }}">
|
|
9
|
+
<slot />
|
|
10
|
+
</td>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const className = $props.className || ''
|
|
3
|
+
export const sortable = $props.sortable || false
|
|
4
|
+
export const onClick = $props.onClick
|
|
5
|
+
|
|
6
|
+
const baseClasses = 'px-6 py-3 text-left text-xs font-medium text-neutral-500 uppercase tracking-wider dark:text-neutral-400'
|
|
7
|
+
const sortableClasses = sortable ? 'cursor-pointer hover:text-neutral-700 dark:hover:text-neutral-200' : ''
|
|
8
|
+
const headerClasses = `${baseClasses} ${sortableClasses} ${className}`.trim()
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<th class="{{ headerClasses }}" @if(sortable)@click="onClick"@endif>
|
|
12
|
+
<slot />
|
|
13
|
+
</th>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const className = $props.className || ''
|
|
3
|
+
export const hoverable = $props.hoverable !== false
|
|
4
|
+
|
|
5
|
+
const hoverClasses = hoverable ? 'hover:bg-neutral-50 dark:hover:bg-neutral-800' : ''
|
|
6
|
+
const rowClasses = `${hoverClasses} ${className}`.trim()
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<tr class="{{ rowClasses }}">
|
|
10
|
+
<slot />
|
|
11
|
+
</tr>
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const tabs = $props.tabs || []
|
|
3
|
+
export const defaultTab = $props.defaultTab || 0
|
|
4
|
+
export const orientation = $props.orientation || 'horizontal'
|
|
5
|
+
export const variant = $props.variant || 'line'
|
|
6
|
+
export const className = $props.className || ''
|
|
7
|
+
export const onChange = $props.onChange
|
|
8
|
+
|
|
9
|
+
let activeTab = defaultTab
|
|
10
|
+
|
|
11
|
+
function selectTab(index) {
|
|
12
|
+
activeTab = index
|
|
13
|
+
if (onChange) {
|
|
14
|
+
onChange(index, tabs[index])
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Orientation classes
|
|
19
|
+
const orientationClasses = {
|
|
20
|
+
horizontal: 'flex-col',
|
|
21
|
+
vertical: 'flex-row',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Tab list orientation classes
|
|
25
|
+
const tabListOrientationClasses = {
|
|
26
|
+
horizontal: 'flex-row border-b',
|
|
27
|
+
vertical: 'flex-col border-r',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Variant classes for tab list
|
|
31
|
+
const variantClasses = {
|
|
32
|
+
line: 'border-gray-200 dark:border-gray-700',
|
|
33
|
+
pills: 'gap-2 border-0 bg-gray-100 dark:bg-gray-800 p-1 rounded-lg',
|
|
34
|
+
enclosed: 'border-gray-200 dark:border-gray-700',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Tab button classes based on variant
|
|
38
|
+
function getTabClasses(index, variant, orientation) {
|
|
39
|
+
const isActive = index === activeTab
|
|
40
|
+
const baseClasses = 'px-4 py-2 font-medium text-sm transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900'
|
|
41
|
+
|
|
42
|
+
if (variant === 'line') {
|
|
43
|
+
const borderPosition = orientation === 'horizontal' ? 'border-b-2' : 'border-r-2'
|
|
44
|
+
return `${baseClasses} ${borderPosition} ${isActive ? 'border-blue-500 text-blue-600 dark:text-blue-400' : 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:border-gray-300 dark:hover:border-gray-600'}`
|
|
45
|
+
} else if (variant === 'pills') {
|
|
46
|
+
return `${baseClasses} rounded-md ${isActive ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'}`
|
|
47
|
+
} else if (variant === 'enclosed') {
|
|
48
|
+
const borderClasses = orientation === 'horizontal' ? 'border-t border-l border-r rounded-t-md' : 'border-t border-l border-b rounded-l-md'
|
|
49
|
+
return `${baseClasses} ${borderClasses} ${isActive ? 'bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 -mb-px' : 'bg-gray-50 dark:bg-gray-900 border-transparent hover:border-gray-200 dark:hover:border-gray-700'}`
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const containerClasses = `flex ${orientationClasses[orientation]} ${className}`.trim()
|
|
54
|
+
export const tabListClasses = `flex ${tabListOrientationClasses[orientation]} ${variantClasses[variant]}`.trim()
|
|
55
|
+
export const tabPanelClasses = `p-4 focus:outline-none`.trim()
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<div class="{{ containerClasses }}">
|
|
59
|
+
<!-- Tab List -->
|
|
60
|
+
<div
|
|
61
|
+
class="{{ tabListClasses }}"
|
|
62
|
+
role="tablist"
|
|
63
|
+
aria-orientation="{{ orientation }}"
|
|
64
|
+
>
|
|
65
|
+
@foreach(tab in tabs)
|
|
66
|
+
<button
|
|
67
|
+
type="button"
|
|
68
|
+
class="{{ getTabClasses($loop.index, variant, orientation) }}"
|
|
69
|
+
role="tab"
|
|
70
|
+
aria-selected="{{ activeTab === $loop.index ? 'true' : 'false' }}"
|
|
71
|
+
aria-controls="tab-panel-{{ $loop.index }}"
|
|
72
|
+
id="tab-{{ $loop.index }}"
|
|
73
|
+
tabindex="{{ activeTab === $loop.index ? '0' : '-1' }}"
|
|
74
|
+
onclick="selectTab({{ $loop.index }})"
|
|
75
|
+
onkeydown="handleKeyDown(event, {{ $loop.index }})"
|
|
76
|
+
>
|
|
77
|
+
@if(tab.icon)
|
|
78
|
+
<span class="inline-flex items-center gap-2">
|
|
79
|
+
<span innerHTML="{!! tab.icon !!}"></span>
|
|
80
|
+
<span>{{ tab.label }}</span>
|
|
81
|
+
</span>
|
|
82
|
+
@else
|
|
83
|
+
{{ tab.label }}
|
|
84
|
+
@endif
|
|
85
|
+
</button>
|
|
86
|
+
@endforeach
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<!-- Tab Panels -->
|
|
90
|
+
@foreach(tab in tabs)
|
|
91
|
+
@if(activeTab === $loop.index)
|
|
92
|
+
<div
|
|
93
|
+
id="tab-panel-{{ $loop.index }}"
|
|
94
|
+
class="{{ tabPanelClasses }}"
|
|
95
|
+
role="tabpanel"
|
|
96
|
+
aria-labelledby="tab-{{ $loop.index }}"
|
|
97
|
+
tabindex="0"
|
|
98
|
+
>
|
|
99
|
+
{!! tab.content !!}
|
|
100
|
+
</div>
|
|
101
|
+
@endif
|
|
102
|
+
@endforeach
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<script>
|
|
106
|
+
function handleKeyDown(event, currentIndex) {
|
|
107
|
+
const tabCount = tabs.length
|
|
108
|
+
let newIndex = currentIndex
|
|
109
|
+
|
|
110
|
+
if (orientation === 'horizontal') {
|
|
111
|
+
if (event.key === 'ArrowRight') {
|
|
112
|
+
event.preventDefault()
|
|
113
|
+
newIndex = (currentIndex + 1) % tabCount
|
|
114
|
+
} else if (event.key === 'ArrowLeft') {
|
|
115
|
+
event.preventDefault()
|
|
116
|
+
newIndex = (currentIndex - 1 + tabCount) % tabCount
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
if (event.key === 'ArrowDown') {
|
|
120
|
+
event.preventDefault()
|
|
121
|
+
newIndex = (currentIndex + 1) % tabCount
|
|
122
|
+
} else if (event.key === 'ArrowUp') {
|
|
123
|
+
event.preventDefault()
|
|
124
|
+
newIndex = (currentIndex - 1 + tabCount) % tabCount
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (event.key === 'Home') {
|
|
129
|
+
event.preventDefault()
|
|
130
|
+
newIndex = 0
|
|
131
|
+
} else if (event.key === 'End') {
|
|
132
|
+
event.preventDefault()
|
|
133
|
+
newIndex = tabCount - 1
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (newIndex !== currentIndex) {
|
|
137
|
+
selectTab(newIndex)
|
|
138
|
+
// Focus the new tab
|
|
139
|
+
if (typeof window !== 'undefined') {
|
|
140
|
+
const newTab = document.getElementById(`tab-${newIndex}`)
|
|
141
|
+
if (newTab) newTab.focus()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
</script>
|