@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,59 @@
|
|
|
1
|
+
export { default as Checkout } from './Checkout.stx'
|
|
2
|
+
export { default as DefaultPaymentMethod } from './DefaultPaymentMethod.stx'
|
|
3
|
+
export { default as PaymentMethods } from './PaymentMethods.stx'
|
|
4
|
+
export { default as SubscriptionCheckout } from './SubscriptionCheckout.stx'
|
|
5
|
+
|
|
6
|
+
export interface PaymentMethod {
|
|
7
|
+
id: string | number
|
|
8
|
+
brand: string
|
|
9
|
+
last_four: string
|
|
10
|
+
exp_month: number
|
|
11
|
+
exp_year: number
|
|
12
|
+
is_default?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PaymentMethodsProps {
|
|
16
|
+
paymentMethods: PaymentMethod[]
|
|
17
|
+
onDeletePaymentMethod?: (id: string | number) => void
|
|
18
|
+
onMakeDefault?: (id: string | number) => void
|
|
19
|
+
isLoading?: boolean
|
|
20
|
+
className?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DefaultPaymentMethodProps {
|
|
24
|
+
paymentMethod: PaymentMethod | null
|
|
25
|
+
isLoading?: boolean
|
|
26
|
+
className?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Product {
|
|
30
|
+
id: string | number
|
|
31
|
+
name: string
|
|
32
|
+
price: number // in cents
|
|
33
|
+
images?: string
|
|
34
|
+
quantity?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SubscriptionCheckoutProps {
|
|
38
|
+
products: Product[]
|
|
39
|
+
stripePublicKey: string
|
|
40
|
+
apiUrl: string
|
|
41
|
+
onSubmit?: (result: { success: boolean }) => void
|
|
42
|
+
onRemoveProduct?: (id: string | number) => void
|
|
43
|
+
shipping?: number
|
|
44
|
+
taxes?: number
|
|
45
|
+
className?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface CheckoutProps {
|
|
49
|
+
products: Product[]
|
|
50
|
+
redirectUrl: string
|
|
51
|
+
mode: 'one-time' | 'subscription'
|
|
52
|
+
stripePublicKey: string
|
|
53
|
+
apiUrl: string
|
|
54
|
+
onSubmit?: (result: { success: boolean }) => void
|
|
55
|
+
onRemoveProduct?: (id: string | number) => void
|
|
56
|
+
shipping?: number
|
|
57
|
+
taxes?: number
|
|
58
|
+
className?: string
|
|
59
|
+
}
|
|
@@ -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'
|
|
19
|
+
const popoverClasses = `${baseClasses} ${className}`.trim()
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<component :is="{{ as }}" class="{{ popoverClasses }}">
|
|
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 items-center gap-2 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,23 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const className = $props.className || ''
|
|
3
|
+
export const as = $props.as || 'div'
|
|
4
|
+
export const static_ = $props.static || false
|
|
5
|
+
export const position = $props.position || 'bottom'
|
|
6
|
+
|
|
7
|
+
const positionClasses = {
|
|
8
|
+
'top': 'bottom-full mb-2',
|
|
9
|
+
'bottom': 'top-full mt-2',
|
|
10
|
+
'left': 'right-full mr-2',
|
|
11
|
+
'right': 'left-full ml-2',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const baseClasses = 'absolute z-10 rounded-lg bg-white dark:bg-neutral-800 p-4 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none'
|
|
15
|
+
const panelClasses = `${baseClasses} ${positionClasses[position]} ${className}`.trim()
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<component
|
|
19
|
+
:is="{{ as }}"
|
|
20
|
+
class="{{ panelClasses }}"
|
|
21
|
+
>
|
|
22
|
+
<slot />
|
|
23
|
+
</component>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Popover Component
|
|
2
|
+
|
|
3
|
+
A floating panel that displays rich content anchored to a button or element.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @stacksjs/components
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```stx
|
|
14
|
+
@component('Popover')
|
|
15
|
+
@component('PopoverButton', { className: 'px-4 py-2 bg-blue-500 text-white rounded' })
|
|
16
|
+
Solutions
|
|
17
|
+
@endcomponent
|
|
18
|
+
|
|
19
|
+
@component('PopoverPanel', { className: 'w-64' })
|
|
20
|
+
<div class="grid gap-4">
|
|
21
|
+
<a href="/analytics">Analytics</a>
|
|
22
|
+
<a href="/engagement">Engagement</a>
|
|
23
|
+
<a href="/security">Security</a>
|
|
24
|
+
</div>
|
|
25
|
+
@endcomponent
|
|
26
|
+
@endcomponent
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Props
|
|
30
|
+
|
|
31
|
+
### Popover
|
|
32
|
+
| Prop | Type | Default | Description |
|
|
33
|
+
|------|------|---------|-------------|
|
|
34
|
+
| `className` | `string` | `''` | Additional CSS classes |
|
|
35
|
+
| `as` | `string` | `'div'` | HTML element to render |
|
|
36
|
+
|
|
37
|
+
### PopoverButton
|
|
38
|
+
| Prop | Type | Default | Description |
|
|
39
|
+
|------|------|---------|-------------|
|
|
40
|
+
| `className` | `string` | `''` | Additional CSS classes |
|
|
41
|
+
| `as` | `string` | `'button'` | HTML element to render |
|
|
42
|
+
| `disabled` | `boolean` | `false` | Disable the button |
|
|
43
|
+
| `onClick` | `(e: Event) => void` | - | Click handler |
|
|
44
|
+
|
|
45
|
+
### PopoverPanel
|
|
46
|
+
| Prop | Type | Default | Description |
|
|
47
|
+
|------|------|---------|-------------|
|
|
48
|
+
| `className` | `string` | `''` | Additional CSS classes |
|
|
49
|
+
| `as` | `string` | `'div'` | HTML element to render |
|
|
50
|
+
| `static` | `boolean` | `false` | Keep visible regardless of state |
|
|
51
|
+
| `position` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'` | Panel position |
|
|
52
|
+
|
|
53
|
+
## Examples
|
|
54
|
+
|
|
55
|
+
### Solutions Menu
|
|
56
|
+
|
|
57
|
+
```stx
|
|
58
|
+
@component('PopoverPanel', { className: 'w-80' })
|
|
59
|
+
<div class="grid gap-8">
|
|
60
|
+
<a href="/analytics" class="flex items-start gap-4 group">
|
|
61
|
+
<div class="shrink-0 text-blue-500">
|
|
62
|
+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
63
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
64
|
+
</svg>
|
|
65
|
+
</div>
|
|
66
|
+
<div>
|
|
67
|
+
<p class="font-semibold text-neutral-900 dark:text-white group-hover:text-blue-500">Analytics</p>
|
|
68
|
+
<p class="text-sm text-neutral-500 dark:text-neutral-400">Get a better understanding of your traffic</p>
|
|
69
|
+
</div>
|
|
70
|
+
</a>
|
|
71
|
+
</div>
|
|
72
|
+
@endcomponent
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Different Positions
|
|
76
|
+
|
|
77
|
+
```stx
|
|
78
|
+
@component('PopoverPanel', { position: 'top' })
|
|
79
|
+
Content appears above
|
|
80
|
+
@endcomponent
|
|
81
|
+
|
|
82
|
+
@component('PopoverPanel', { position: 'left' })
|
|
83
|
+
Content appears to the left
|
|
84
|
+
@endcomponent
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Features
|
|
88
|
+
|
|
89
|
+
- Multiple positioning options
|
|
90
|
+
- Auto-positioning
|
|
91
|
+
- Dark mode support
|
|
92
|
+
- Accessible (WAI-ARIA)
|
|
93
|
+
- Keyboard support
|
|
94
|
+
- Focus management
|
|
95
|
+
- Customizable styling
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { default as Popover } from './Popover.stx'
|
|
2
|
+
export { default as PopoverButton } from './PopoverButton.stx'
|
|
3
|
+
export { default as PopoverPanel } from './PopoverPanel.stx'
|
|
4
|
+
|
|
5
|
+
export interface PopoverProps {
|
|
6
|
+
className?: string
|
|
7
|
+
as?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface PopoverButtonProps {
|
|
11
|
+
className?: string
|
|
12
|
+
as?: string
|
|
13
|
+
disabled?: boolean
|
|
14
|
+
onClick?: (e: Event) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PopoverPanelProps {
|
|
18
|
+
className?: string
|
|
19
|
+
as?: string
|
|
20
|
+
static?: boolean
|
|
21
|
+
position?: 'top' | 'bottom' | 'left' | 'right'
|
|
22
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const target = $props.target || 'body'
|
|
3
|
+
export const disabled = $props.disabled || false
|
|
4
|
+
|
|
5
|
+
let portalContainer = null
|
|
6
|
+
let mounted = false
|
|
7
|
+
|
|
8
|
+
// Create portal container
|
|
9
|
+
function createPortal() {
|
|
10
|
+
if (typeof window === 'undefined' || disabled)
|
|
11
|
+
return
|
|
12
|
+
|
|
13
|
+
// Get target element
|
|
14
|
+
const targetElement = typeof target === 'string'
|
|
15
|
+
? document.querySelector(target)
|
|
16
|
+
: target
|
|
17
|
+
|
|
18
|
+
if (!targetElement) {
|
|
19
|
+
console.warn(`Portal target "${target}" not found`)
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Create portal container
|
|
24
|
+
portalContainer = document.createElement('div')
|
|
25
|
+
portalContainer.setAttribute('data-portal', 'true')
|
|
26
|
+
|
|
27
|
+
// Move portal content to target
|
|
28
|
+
const portalContent = document.getElementById('portal-content')
|
|
29
|
+
if (portalContent) {
|
|
30
|
+
while (portalContent.firstChild) {
|
|
31
|
+
portalContainer.appendChild(portalContent.firstChild)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
targetElement.appendChild(portalContainer)
|
|
36
|
+
mounted = true
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Cleanup portal
|
|
40
|
+
function cleanupPortal() {
|
|
41
|
+
if (portalContainer && portalContainer.parentNode) {
|
|
42
|
+
portalContainer.parentNode.removeChild(portalContainer)
|
|
43
|
+
portalContainer = null
|
|
44
|
+
mounted = false
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Initialize portal on mount
|
|
49
|
+
if (typeof window !== 'undefined') {
|
|
50
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
51
|
+
setTimeout(createPortal, 0)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// Cleanup on page unload
|
|
55
|
+
window.addEventListener('beforeunload', cleanupPortal)
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
@if(disabled)
|
|
60
|
+
<!-- Render in place if disabled -->
|
|
61
|
+
<slot />
|
|
62
|
+
@else
|
|
63
|
+
<!-- Hidden container that will be moved to portal -->
|
|
64
|
+
<div id="portal-content" style="display: none;">
|
|
65
|
+
<slot />
|
|
66
|
+
</div>
|
|
67
|
+
@endif
|
|
68
|
+
|
|
69
|
+
<script>
|
|
70
|
+
// Trigger portal creation after render
|
|
71
|
+
if (typeof window !== 'undefined' && document.readyState === 'complete') {
|
|
72
|
+
setTimeout(createPortal, 0)
|
|
73
|
+
}
|
|
74
|
+
</script>
|