@stacksjs/components 0.2.27 → 0.2.53
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/dist/Accordion-19x8q4rj.stx +88 -0
- package/dist/{Audio-vdmg1258.stx → Audio-hjywk3qc.stx} +4 -4
- package/dist/{Avatar-365cvrh4.stx → Avatar-h3jmwcq0.stx} +23 -34
- package/dist/{Badge-8m3qete4.stx → Badge-k8dy3d53.stx} +11 -19
- package/dist/{Button-3tt4vsqt.stx → Button-1vknasy3.stx} +18 -33
- package/dist/Calendar-reny0et7.stx +189 -0
- package/dist/{Card-6wdbexqr.stx → Card-c1g1fd1w.stx} +27 -33
- package/dist/{Checkbox-p3q3e3nf.stx → Checkbox-zgdmy3p5.stx} +36 -44
- package/dist/{Checkout-e4037tyy.stx → Checkout-agt0ngpw.stx} +8 -16
- package/dist/Combobox-fdkr6cdq.stx +96 -0
- package/dist/{ComboboxButton-8gxwkhdx.stx → ComboboxButton-g76stp1e.stx} +14 -2
- package/dist/{ComboboxInput-mk99z65n.stx → ComboboxInput-z1293bbh.stx} +12 -5
- package/dist/ComboboxOption-hmxt3gte.stx +49 -0
- package/dist/{ListboxOptions-8kqvfp7z.stx → ComboboxOptions-sq5360ae.stx} +2 -2
- package/dist/CommandPalette-36mxf05w.stx +81 -0
- package/dist/CommandPaletteItem-mvk6q7a2.stx +27 -0
- package/dist/Dialog-hczpexs4.stx +37 -0
- package/dist/{Drawer-p7m351bn.stx → Drawer-xhevp5cw.stx} +29 -49
- package/dist/Dropdown-vvhemtc0.stx +82 -0
- package/dist/{DropdownButton-9w8zkfwj.stx → DropdownButton-wcz67dxy.stx} +12 -6
- package/dist/DropdownItem-c7rz08e5.stx +35 -0
- package/dist/{DropdownItems-aygjpxkx.stx → DropdownItems-1sxnd5qn.stx} +2 -2
- package/dist/EmailInput-vak3kkmk.stx +30 -0
- package/dist/Form-mas630hy.stx +95 -0
- package/dist/{Image-jhkm6w1f.stx → Image-yhxcvgzs.stx} +68 -100
- package/dist/Listbox-q49tnr90.stx +101 -0
- package/dist/{ListboxButton-spwx7513.stx → ListboxButton-keb8gm0q.stx} +12 -1
- package/dist/ListboxOption-e0dtv1gc.stx +49 -0
- package/dist/{ComboboxOptions-6b5ywc5x.stx → ListboxOptions-ejkatcfg.stx} +7 -3
- package/dist/{Login-rx9gyqhx.stx → Login-gepemsth.stx} +17 -19
- package/dist/Navigator-rk2a5wvk.stx +113 -0
- package/dist/Notification-tb9b525s.stx +78 -0
- package/dist/{NumberInput-nzpkx1ry.stx → NumberInput-drnvtqej.stx} +40 -53
- package/dist/{Pagination-h61eq390.stx → Pagination-n3d31cns.stx} +73 -108
- package/dist/PasswordInput-n02x6053.stx +142 -0
- package/dist/{PaymentMethods-j42jsk5x.stx → PaymentMethods-x4vrx9yb.stx} +21 -30
- package/dist/Popover-et2q44m7.stx +60 -0
- package/dist/{PopoverButton-fnaqc77g.stx → PopoverButton-mffzc9pr.stx} +12 -6
- package/dist/{PopoverPanel-y0vtf5w9.stx → PopoverPanel-054yh38h.stx} +6 -6
- package/dist/{Portal-s2q37yp0.stx → Portal-1vf14s4x.stx} +20 -37
- package/dist/{Progress-vmdkm06p.stx → Progress-yxcntr4x.stx} +36 -48
- package/dist/{Radio-04bs3d21.stx → Radio-cgjv2hb2.stx} +29 -34
- package/dist/RadioGroup-qxmnm3xm.stx +60 -0
- package/dist/RadioGroupOption-s1d6r221.stx +58 -0
- package/dist/SearchInput-pdx7agn0.stx +44 -0
- package/dist/{Select-g4mmvfv8.stx → Select-28y0pg83.stx} +23 -31
- package/dist/Sidebar-73vc78as.stx +236 -0
- package/dist/SidebarFooter-zsfsgcxb.stx +109 -0
- package/dist/SidebarHeader-7v174a9c.stx +122 -0
- package/dist/SidebarItem-3y3e8g2t.stx +92 -0
- package/dist/SidebarSection-z5ey6awe.stx +113 -0
- package/dist/{Signup-c4dyv6f3.stx → Signup-hxm5hwkr.stx} +26 -28
- package/dist/{StepperStep-en4r00zv.stx → StepperStep-wxgembat.stx} +9 -5
- package/dist/{SubscriptionCheckout-gawjzf3q.stx → SubscriptionCheckout-zy2ycrmw.stx} +99 -129
- package/dist/Switch-r5t5w04w.stx +70 -0
- package/dist/{TableHeader-ydys9rzz.stx → TableHeader-0eyzbqpz.stx} +10 -3
- package/dist/Tabs-bzvvn4ke.stx +126 -0
- package/dist/Teleport-tsx4bt4n.stx +65 -0
- package/dist/{TextInput-k2kfkgy1.stx → TextInput-kf084763.stx} +42 -59
- package/dist/{Textarea-szad57dz.stx → Textarea-xg4rr2xz.stx} +42 -54
- package/dist/{Tooltip-em7t551n.stx → Tooltip-m0tqd72e.stx} +34 -40
- package/dist/Transition-7smenaz8.stx +102 -0
- package/dist/{TwoFactorChallenge-an9yvxvq.stx → TwoFactorChallenge-y5m3nk79.stx} +49 -63
- package/dist/{Video-crxva65p.stx → Video-84j4pv6h.stx} +4 -4
- package/dist/VirtualList-fa7v5a3h.stx +67 -0
- package/dist/VirtualTable-f9crbfjr.stx +103 -0
- package/dist/components/CodeBlock.stx +1 -6
- package/dist/index.js +13 -13
- package/dist/ui/accordion/Accordion.stx +1 -3
- package/dist/ui/auth/Login.stx +0 -1
- package/dist/ui/auth/Signup.stx +0 -1
- package/dist/ui/auth/TwoFactorChallenge.stx +1 -3
- package/dist/ui/avatar/Avatar.stx +0 -2
- package/dist/ui/badge/Badge.stx +0 -1
- package/dist/ui/button/Button.stx +1 -2
- package/dist/ui/calendar/Calendar.stx +12 -2
- package/dist/ui/card/Card.stx +0 -2
- package/dist/ui/checkbox/Checkbox.stx +2 -2
- package/dist/ui/combobox/Combobox.stx +2 -1
- package/dist/ui/combobox/ComboboxButton.stx +1 -0
- package/dist/ui/combobox/ComboboxInput.stx +1 -4
- package/dist/ui/combobox/ComboboxOption.stx +2 -9
- package/dist/ui/combobox/ComboboxOptions.stx +1 -1
- package/dist/ui/command-palette/CommandPalette.stx +3 -6
- package/dist/ui/command-palette/CommandPaletteItem.stx +2 -7
- package/dist/ui/dialog/Dialog.stx +1 -3
- package/dist/ui/drawer/Drawer.stx +1 -3
- package/dist/ui/dropdown/Dropdown.stx +2 -5
- package/dist/ui/dropdown/DropdownButton.stx +1 -3
- package/dist/ui/dropdown/DropdownItem.stx +2 -7
- package/dist/ui/dropdown/DropdownItems.stx +1 -1
- package/dist/ui/form/Form.stx +0 -5
- package/dist/ui/image/Image.stx +1 -6
- package/dist/ui/input/EmailInput.stx +1 -1
- package/dist/ui/input/NumberInput.stx +0 -1
- package/dist/ui/input/PasswordInput.stx +1 -4
- package/dist/ui/input/SearchInput.stx +2 -2
- package/dist/ui/input/TextInput.stx +1 -2
- package/dist/ui/listbox/Listbox.stx +2 -1
- package/dist/ui/listbox/ListboxButton.stx +1 -0
- package/dist/ui/listbox/ListboxOption.stx +2 -9
- package/dist/ui/listbox/ListboxOptions.stx +1 -1
- package/dist/ui/navigator/Navigator.stx +4 -4
- package/dist/ui/notification/Notification.stx +1 -5
- package/dist/ui/pagination/Pagination.stx +1 -2
- package/dist/ui/payment/Checkout.stx +0 -4
- package/dist/ui/payment/PaymentMethods.stx +15 -2
- package/dist/ui/payment/SubscriptionCheckout.stx +3 -184
- package/dist/ui/popover/Popover.stx +2 -5
- package/dist/ui/popover/PopoverButton.stx +1 -3
- package/dist/ui/popover/PopoverPanel.stx +1 -1
- package/dist/ui/portal/Portal.stx +1 -0
- package/dist/ui/progress/Progress.stx +6 -1
- package/dist/ui/radio/Radio.stx +1 -1
- package/dist/ui/radio-group/RadioGroup.stx +2 -1
- package/dist/ui/radio-group/RadioGroupOption.stx +2 -9
- package/dist/ui/select/Select.stx +1 -1
- package/dist/ui/sidebar/Sidebar.stx +9 -26
- package/dist/ui/sidebar/SidebarFooter.stx +6 -21
- package/dist/ui/sidebar/SidebarHeader.stx +7 -18
- package/dist/ui/sidebar/SidebarItem.stx +4 -20
- package/dist/ui/sidebar/SidebarSection.stx +5 -18
- package/dist/ui/sidebar/index.d.ts +13 -1
- package/dist/ui/stepper/StepperStep.stx +1 -5
- package/dist/ui/switch/Switch.stx +4 -10
- package/dist/ui/table/TableHeader.stx +1 -1
- package/dist/ui/tabs/Tabs.stx +0 -1
- package/dist/ui/teleport/Teleport.stx +1 -0
- package/dist/ui/textarea/Textarea.stx +1 -1
- package/dist/ui/tooltip/Tooltip.stx +1 -2
- package/dist/ui/transition/Transition.stx +9 -16
- package/dist/ui/virtual-list/VirtualList.stx +2 -4
- package/dist/ui/virtual-table/VirtualTable.stx +1 -1
- package/package.json +3 -3
- package/src/components/CodeBlock.stx +26 -29
- package/src/components/Footer.stx +1 -1
- package/src/components/Hero.stx +1 -1
- package/src/components/Installation.stx +5 -7
- package/src/ui/accordion/Accordion.stx +48 -68
- package/src/ui/audio/Audio.stx +4 -4
- package/src/ui/auth/Login.stx +17 -19
- package/src/ui/auth/Signup.stx +26 -28
- package/src/ui/auth/TwoFactorChallenge.stx +49 -63
- package/src/ui/avatar/Avatar.stx +23 -34
- package/src/ui/badge/Badge.stx +11 -19
- package/src/ui/button/Button.stx +18 -33
- package/src/ui/calendar/Calendar.stx +93 -136
- package/src/ui/card/Card.stx +27 -33
- package/src/ui/checkbox/Checkbox.stx +36 -44
- package/src/ui/combobox/Combobox.stx +87 -4
- package/src/ui/combobox/ComboboxButton.stx +14 -2
- package/src/ui/combobox/ComboboxInput.stx +12 -5
- package/src/ui/combobox/ComboboxOption.stx +29 -32
- package/src/ui/combobox/ComboboxOptions.stx +7 -3
- package/src/ui/command-palette/CommandPalette.stx +70 -28
- package/src/ui/command-palette/CommandPaletteItem.stx +15 -20
- package/src/ui/dialog/Dialog.stx +28 -10
- package/src/ui/drawer/Drawer.stx +29 -49
- package/src/ui/dropdown/Dropdown.stx +69 -11
- package/src/ui/dropdown/DropdownButton.stx +12 -6
- package/src/ui/dropdown/DropdownItem.stx +18 -26
- package/src/ui/dropdown/DropdownItems.stx +2 -2
- package/src/ui/form/Form.stx +50 -164
- package/src/ui/heatmap/Heatmap.stx +16 -37
- package/src/ui/image/Image.stx +68 -100
- package/src/ui/input/EmailInput.stx +16 -27
- package/src/ui/input/NumberInput.stx +40 -53
- package/src/ui/input/PasswordInput.stx +64 -77
- package/src/ui/input/SearchInput.stx +27 -46
- package/src/ui/input/TextInput.stx +42 -59
- package/src/ui/listbox/Listbox.stx +91 -4
- package/src/ui/listbox/ListboxButton.stx +12 -1
- package/src/ui/listbox/ListboxOption.stx +30 -38
- package/src/ui/listbox/ListboxOptions.stx +2 -2
- package/src/ui/navigator/Navigator.stx +52 -77
- package/src/ui/notification/Notification.stx +46 -39
- package/src/ui/pagination/Pagination.stx +73 -108
- package/src/ui/payment/Checkout.stx +8 -16
- package/src/ui/payment/PaymentMethods.stx +21 -30
- package/src/ui/payment/SubscriptionCheckout.stx +99 -129
- package/src/ui/popover/Popover.stx +50 -14
- package/src/ui/popover/PopoverButton.stx +12 -6
- package/src/ui/popover/PopoverPanel.stx +6 -6
- package/src/ui/portal/Portal.stx +20 -37
- package/src/ui/progress/Progress.stx +36 -48
- package/src/ui/radio/Radio.stx +29 -34
- package/src/ui/radio-group/RadioGroup.stx +51 -4
- package/src/ui/radio-group/RadioGroupOption.stx +37 -30
- package/src/ui/select/Select.stx +23 -31
- package/src/ui/sidebar/Sidebar.stx +136 -188
- package/src/ui/sidebar/SidebarFooter.stx +49 -35
- package/src/ui/sidebar/SidebarHeader.stx +52 -30
- package/src/ui/sidebar/SidebarItem.stx +52 -78
- package/src/ui/sidebar/SidebarSection.stx +56 -51
- package/src/ui/sidebar/index.ts +21 -1
- package/src/ui/stepper/StepperStep.stx +9 -5
- package/src/ui/switch/Switch.stx +38 -42
- package/src/ui/table/TableHeader.stx +10 -3
- package/src/ui/tabs/Tabs.stx +68 -87
- package/src/ui/teleport/Teleport.stx +23 -59
- package/src/ui/textarea/Textarea.stx +42 -54
- package/src/ui/tooltip/Tooltip.stx +34 -40
- package/src/ui/transition/Transition.stx +76 -148
- package/src/ui/video/Video.stx +4 -4
- package/src/ui/virtual-list/VirtualList.stx +45 -61
- package/src/ui/virtual-table/VirtualTable.stx +62 -72
- package/dist/Accordion-sbfra0mz.stx +0 -108
- package/dist/Calendar-58nnkdh8.stx +0 -232
- package/dist/Combobox-th5ebw62.stx +0 -13
- package/dist/ComboboxOption-an9zyan6.stx +0 -52
- package/dist/CommandPalette-kwmdf2np.stx +0 -39
- package/dist/CommandPaletteItem-zx2fbpts.stx +0 -32
- package/dist/Dialog-rgmj8q2j.stx +0 -19
- package/dist/Dropdown-jz4jy0n3.stx +0 -24
- package/dist/DropdownItem-wagdspb8.stx +0 -43
- package/dist/EmailInput-ajsp9vw6.stx +0 -41
- package/dist/Form-1mdmx9b6.stx +0 -209
- package/dist/Listbox-pqqh72g8.stx +0 -14
- package/dist/ListboxOption-7nxzaexq.stx +0 -57
- package/dist/Navigator-aay8bjab.stx +0 -138
- package/dist/Notification-gk33gv7b.stx +0 -71
- package/dist/PasswordInput-zv4hm43j.stx +0 -155
- package/dist/Popover-469gbt6r.stx +0 -24
- package/dist/RadioGroup-bvb1mrhd.stx +0 -13
- package/dist/RadioGroupOption-cpdwgnhw.stx +0 -51
- package/dist/SearchInput-2521m475.stx +0 -63
- package/dist/Sidebar-40ckbzh8.stx +0 -288
- package/dist/SidebarFooter-hnth56mg.stx +0 -95
- package/dist/SidebarHeader-3wrte2p2.stx +0 -100
- package/dist/SidebarItem-zqtvvzpc.stx +0 -118
- package/dist/SidebarSection-3etwnbjr.stx +0 -108
- package/dist/Switch-036j9vm1.stx +0 -74
- package/dist/Tabs-svh721m0.stx +0 -145
- package/dist/Teleport-qfch3w0b.stx +0 -101
- package/dist/Transition-0hmr6zxb.stx +0 -174
- package/dist/VirtualList-cndhjejh.stx +0 -83
- package/dist/VirtualTable-ng0b48yp.stx +0 -113
|
@@ -1,12 +1,36 @@
|
|
|
1
1
|
<script server>
|
|
2
2
|
export const headingText = $props.headingText || 'Sign up for Stacks'
|
|
3
3
|
export const showSocialSignup = $props.showSocialSignup ?? true
|
|
4
|
-
export const onSubmit = $props.onSubmit || (() => {})
|
|
5
4
|
export const className = $props.className || ''
|
|
6
5
|
|
|
7
6
|
export const containerClasses = `min-h-full flex flex-col justify-center py-12 lg:px-8 sm:px-6 dark:bg-blue-gray-900 ${className}`.trim()
|
|
8
7
|
</script>
|
|
9
8
|
|
|
9
|
+
<script client>
|
|
10
|
+
const emit = defineEmits()
|
|
11
|
+
|
|
12
|
+
function onSubmit(event) {
|
|
13
|
+
event.preventDefault()
|
|
14
|
+
const formData = new FormData(event.target)
|
|
15
|
+
const name = formData.get('name')
|
|
16
|
+
const email = formData.get('email')
|
|
17
|
+
const confirmEmail = formData.get('confirm_email')
|
|
18
|
+
const password = formData.get('password')
|
|
19
|
+
|
|
20
|
+
if (email !== confirmEmail) {
|
|
21
|
+
emit('error', { message: 'Emails do not match' })
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
emit('submit', { name, email, password })
|
|
26
|
+
|
|
27
|
+
setTimeout(() => {
|
|
28
|
+
event.target.querySelector('#password')?.setAttribute('value', '')
|
|
29
|
+
event.target.querySelector('#confirm_email')?.setAttribute('value', '')
|
|
30
|
+
}, 1000)
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
10
34
|
<div class="{{ containerClasses }}">
|
|
11
35
|
<div class="sm:mx-auto sm:max-w-md sm:w-full">
|
|
12
36
|
<h2 class="mt-6 text-center text-2xl text-gray-900 dark:text-gray-100 font-bold leading-9 tracking-tight">
|
|
@@ -16,7 +40,7 @@ export const containerClasses = `min-h-full flex flex-col justify-center py-12 l
|
|
|
16
40
|
|
|
17
41
|
<div class="mt-10 sm:mx-auto sm:max-w-[480px] sm:w-full">
|
|
18
42
|
<div class="bg-white dark:bg-blue-gray-800 px-6 py-12 shadow sm:rounded-lg sm:px-12">
|
|
19
|
-
<form class="space-y-6"
|
|
43
|
+
<form class="space-y-6" @submit="onSubmit($event)">
|
|
20
44
|
<div>
|
|
21
45
|
<label for="name" class="block text-sm text-gray-900 dark:text-gray-100 font-medium leading-6">
|
|
22
46
|
Full Name
|
|
@@ -133,29 +157,3 @@ export const containerClasses = `min-h-full flex flex-col justify-center py-12 l
|
|
|
133
157
|
</div>
|
|
134
158
|
</div>
|
|
135
159
|
</div>
|
|
136
|
-
|
|
137
|
-
<script>
|
|
138
|
-
function handleSubmit(event) {
|
|
139
|
-
const formData = new FormData(event.target)
|
|
140
|
-
const name = formData.get('name')
|
|
141
|
-
const email = formData.get('email')
|
|
142
|
-
const confirmEmail = formData.get('confirm_email')
|
|
143
|
-
const password = formData.get('password')
|
|
144
|
-
|
|
145
|
-
// Basic email confirmation validation
|
|
146
|
-
if (email !== confirmEmail) {
|
|
147
|
-
alert('Emails do not match!')
|
|
148
|
-
return
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (onSubmit) {
|
|
152
|
-
onSubmit({ name, email, password })
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Clear sensitive fields after submission
|
|
156
|
-
setTimeout(() => {
|
|
157
|
-
event.target.querySelector('#password').value = ''
|
|
158
|
-
event.target.querySelector('#confirm_email').value = ''
|
|
159
|
-
}, 1000)
|
|
160
|
-
}
|
|
161
|
-
</script>
|
|
@@ -5,24 +5,28 @@ export const label = $props.label || ''
|
|
|
5
5
|
export const description = $props.description || ''
|
|
6
6
|
export const className = $props.className || ''
|
|
7
7
|
export const as = $props.as || 'div'
|
|
8
|
-
export const onClick = $props.onClick
|
|
9
8
|
|
|
10
9
|
export const isComplete = stepNumber < currentStep
|
|
11
10
|
export const isCurrent = stepNumber === currentStep
|
|
12
11
|
export const isUpcoming = stepNumber > currentStep
|
|
13
12
|
|
|
14
13
|
const baseClasses = 'flex items-center'
|
|
15
|
-
const stepClasses = `${baseClasses} ${className}`.trim()
|
|
14
|
+
export const stepClasses = `${baseClasses} ${className}`.trim()
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<script client>
|
|
18
|
+
const emit = defineEmits()
|
|
19
|
+
const stepNumber = {{ stepNumber }}
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
function onClick() {
|
|
22
|
+
emit('click', stepNumber)
|
|
19
23
|
}
|
|
20
24
|
</script>
|
|
21
25
|
|
|
22
26
|
<component :is="{{ as }}" class="{{ stepClasses }}">
|
|
23
27
|
<button
|
|
24
28
|
type="button"
|
|
25
|
-
@click="
|
|
29
|
+
@click="onClick()"
|
|
26
30
|
class="flex items-center gap-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 rounded-lg p-2"
|
|
27
31
|
>
|
|
28
32
|
<div class="{{ isComplete ? 'bg-blue-600 dark:bg-blue-500' : isCurrent ? 'bg-blue-600 dark:bg-blue-500 ring-4 ring-blue-200 dark:ring-blue-800' : 'bg-neutral-300 dark:bg-neutral-600' }} w-10 h-10 rounded-full flex items-center justify-center text-white font-semibold transition-all">
|
|
@@ -2,27 +2,115 @@
|
|
|
2
2
|
export const products = $props.products || []
|
|
3
3
|
export const stripePublicKey = $props.stripePublicKey || ''
|
|
4
4
|
export const apiUrl = $props.apiUrl || ''
|
|
5
|
-
export const
|
|
6
|
-
export const onRemoveProduct = $props.onRemoveProduct
|
|
5
|
+
export const showRemoveProduct = $props.showRemoveProduct ?? true
|
|
7
6
|
export const shipping = $props.shipping || 0
|
|
8
7
|
export const taxes = $props.taxes || 0
|
|
9
8
|
export const className = $props.className || ''
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
export const
|
|
13
|
-
|
|
10
|
+
export const subtotal = products.reduce((sum, product) => sum + (product.price * (product.quantity || 1)), 0)
|
|
11
|
+
export const total = subtotal + shipping + taxes
|
|
12
|
+
|
|
13
|
+
export const containerClasses = `bg-gray-50 dark:bg-blue-gray-900 ${className}`.trim()
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<script client>
|
|
17
|
+
const emit = defineEmits()
|
|
18
|
+
const stripePublicKey = {{ stripePublicKey }}
|
|
19
|
+
const apiUrl = {{ apiUrl }}
|
|
20
|
+
const products = {{ products }}
|
|
21
|
+
const total = {{ total }}
|
|
22
|
+
|
|
23
|
+
let stripe = null
|
|
24
|
+
let elements = null
|
|
25
|
+
let paymentElement = null
|
|
26
|
+
|
|
27
|
+
function onRemoveProduct(productId) {
|
|
28
|
+
emit('removeProduct', productId)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function setupStripeElements() {
|
|
32
|
+
if (!stripe || !apiUrl) return
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch(`${apiUrl}/stripe/create-subscription`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
37
|
+
body: JSON.stringify({
|
|
38
|
+
amount: total,
|
|
39
|
+
quantity: products.reduce((sum, p) => sum + (p.quantity || 1), 0),
|
|
40
|
+
}),
|
|
41
|
+
})
|
|
42
|
+
const paymentIntent = await response.json()
|
|
43
|
+
const { clientSecret } = paymentIntent
|
|
44
|
+
if (!clientSecret) {
|
|
45
|
+
console.error('No client secret returned from API')
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
elements = stripe.elements({
|
|
49
|
+
clientSecret,
|
|
50
|
+
appearance: {
|
|
51
|
+
theme: document.documentElement.classList.contains('dark') ? 'night' : 'stripe',
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
paymentElement = elements.create('payment')
|
|
55
|
+
paymentElement.mount('#payment-element')
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error('Error setting up Stripe elements:', error)
|
|
59
|
+
}
|
|
14
60
|
}
|
|
15
61
|
|
|
16
|
-
|
|
62
|
+
async function initializeStripe() {
|
|
63
|
+
if (!stripePublicKey) {
|
|
64
|
+
console.error('Stripe public key is required')
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
if (window.Stripe) {
|
|
68
|
+
stripe = window.Stripe(stripePublicKey)
|
|
69
|
+
await setupStripeElements()
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
const script = document.createElement('script')
|
|
73
|
+
script.src = 'https://js.stripe.com/v3/'
|
|
74
|
+
script.async = true
|
|
75
|
+
script.onload = async () => {
|
|
76
|
+
stripe = window.Stripe(stripePublicKey)
|
|
77
|
+
await setupStripeElements()
|
|
78
|
+
}
|
|
79
|
+
document.head.appendChild(script)
|
|
80
|
+
}
|
|
17
81
|
|
|
18
|
-
|
|
82
|
+
async function onSubmit(event) {
|
|
83
|
+
event.preventDefault()
|
|
84
|
+
if (!stripe || !elements) {
|
|
85
|
+
emit('error', { message: 'Stripe not initialized' })
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const { error } = await stripe.confirmPayment({
|
|
90
|
+
elements,
|
|
91
|
+
redirect: 'if_required',
|
|
92
|
+
confirmParams: { return_url: window.location.href },
|
|
93
|
+
})
|
|
94
|
+
if (error) {
|
|
95
|
+
emit('error', error)
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
emit('submit', { success: true })
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
emit('error', error)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
onMount(() => initializeStripe())
|
|
19
107
|
</script>
|
|
20
108
|
|
|
21
109
|
<div class="{{ containerClasses }}">
|
|
22
110
|
<div class="mx-auto max-w-2xl px-4 pb-24 pt-16 lg:max-w-7xl lg:px-8 sm:px-6">
|
|
23
111
|
<h2 class="sr-only">Checkout</h2>
|
|
24
112
|
|
|
25
|
-
<form class="lg:grid lg:grid-cols-2 lg:gap-x-12 xl:gap-x-16"
|
|
113
|
+
<form class="lg:grid lg:grid-cols-2 lg:gap-x-12 xl:gap-x-16" @submit="onSubmit($event)">
|
|
26
114
|
<!-- Left Column - Contact & Payment -->
|
|
27
115
|
<div>
|
|
28
116
|
<!-- Contact Information -->
|
|
@@ -139,12 +227,12 @@ export const containerClasses = `bg-gray-50 dark:bg-blue-gray-900 ${className}`.
|
|
|
139
227
|
</h4>
|
|
140
228
|
</div>
|
|
141
229
|
|
|
142
|
-
@if(
|
|
230
|
+
@if(showRemoveProduct)
|
|
143
231
|
<div class="ml-4 flow-root flex-shrink-0">
|
|
144
232
|
<button
|
|
145
233
|
type="button"
|
|
146
234
|
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
|
-
@click="
|
|
235
|
+
@click="onRemoveProduct('{{ product.id }}')"
|
|
148
236
|
title="Remove"
|
|
149
237
|
>
|
|
150
238
|
<span class="sr-only">Remove</span>
|
|
@@ -189,7 +277,7 @@ export const containerClasses = `bg-gray-50 dark:bg-blue-gray-900 ${className}`.
|
|
|
189
277
|
<div class="flex items-center justify-between">
|
|
190
278
|
<dt class="text-sm text-gray-600 dark:text-gray-400">Subtotal</dt>
|
|
191
279
|
<dd class="text-sm text-gray-900 dark:text-gray-100 font-medium">
|
|
192
|
-
${{ (
|
|
280
|
+
${{ (subtotal / 100).toFixed(2) }}
|
|
193
281
|
</dd>
|
|
194
282
|
</div>
|
|
195
283
|
|
|
@@ -233,121 +321,3 @@ export const containerClasses = `bg-gray-50 dark:bg-blue-gray-900 ${className}`.
|
|
|
233
321
|
</form>
|
|
234
322
|
</div>
|
|
235
323
|
</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,70 @@
|
|
|
1
|
+
<script server>
|
|
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 label = $props.label || ''
|
|
7
|
+
|
|
8
|
+
const sizeConfig = {
|
|
9
|
+
sm: { track: 'h-5 w-9', thumb: 'h-4 w-4', translate: 'translate-x-4' },
|
|
10
|
+
md: { track: 'h-[38px] w-[74px]', thumb: 'h-[34px] w-[34px]', translate: 'translate-x-9' },
|
|
11
|
+
lg: { track: 'h-12 w-24', thumb: 'h-11 w-11', translate: 'translate-x-12' },
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const config = sizeConfig[size]
|
|
15
|
+
|
|
16
|
+
export 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'
|
|
17
|
+
export const trackDisabledClasses = disabled ? 'opacity-50 cursor-not-allowed pointer-events-none' : ''
|
|
18
|
+
export const thumbBaseClasses = 'pointer-events-none inline-block transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out'
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<script client>
|
|
22
|
+
const emit = defineEmits()
|
|
23
|
+
const isDisabled = {{ disabled }}
|
|
24
|
+
const config = {{ config }}
|
|
25
|
+
const trackBase = {{ trackBaseClasses }}
|
|
26
|
+
const trackDisabled = {{ trackDisabledClasses }}
|
|
27
|
+
const thumbBase = {{ thumbBaseClasses }}
|
|
28
|
+
const extraClass = {{ className }}
|
|
29
|
+
const isChecked = state({{ checked }})
|
|
30
|
+
|
|
31
|
+
function toggle() {
|
|
32
|
+
if (isDisabled) return
|
|
33
|
+
isChecked.set(!isChecked())
|
|
34
|
+
emit('change', isChecked())
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function trackClasses() {
|
|
38
|
+
const stateClass = isChecked()
|
|
39
|
+
? 'bg-blue-500 dark:bg-blue-600'
|
|
40
|
+
: 'bg-neutral-400 dark:bg-neutral-600'
|
|
41
|
+
return `${trackBase} ${stateClass} ${trackDisabled} ${config.track} ${extraClass}`.trim()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function thumbClasses() {
|
|
45
|
+
const pos = isChecked() ? config.translate : 'translate-x-0'
|
|
46
|
+
return `${thumbBase} ${pos} ${config.thumb}`.trim()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
defineExpose({ isChecked, toggle })
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<label class="inline-flex items-center gap-3 cursor-pointer">
|
|
53
|
+
@if(label)
|
|
54
|
+
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">
|
|
55
|
+
{{ label }}
|
|
56
|
+
</span>
|
|
57
|
+
@endif
|
|
58
|
+
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
role="switch"
|
|
62
|
+
:aria-checked="isChecked() ? 'true' : 'false'"
|
|
63
|
+
:class="trackClasses()"
|
|
64
|
+
@if(disabled)disabled aria-disabled="true"@endif
|
|
65
|
+
@click="toggle()"
|
|
66
|
+
>
|
|
67
|
+
<span class="sr-only">@if(label){{ label }}@else Toggle switch@endif</span>
|
|
68
|
+
<span aria-hidden="true" :class="thumbClasses()"></span>
|
|
69
|
+
</button>
|
|
70
|
+
</label>
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
<script server>
|
|
2
2
|
export const className = $props.className || ''
|
|
3
3
|
export const sortable = $props.sortable || false
|
|
4
|
-
export const onClick = $props.onClick
|
|
5
4
|
|
|
6
5
|
const baseClasses = 'px-6 py-3 text-left text-xs font-medium text-neutral-500 uppercase tracking-wider dark:text-neutral-400'
|
|
7
6
|
const sortableClasses = sortable ? 'cursor-pointer hover:text-neutral-700 dark:hover:text-neutral-200' : ''
|
|
8
|
-
const headerClasses = `${baseClasses} ${sortableClasses} ${className}`.trim()
|
|
7
|
+
export const headerClasses = `${baseClasses} ${sortableClasses} ${className}`.trim()
|
|
9
8
|
</script>
|
|
10
9
|
|
|
11
|
-
<
|
|
10
|
+
<script client>
|
|
11
|
+
const emit = defineEmits()
|
|
12
|
+
|
|
13
|
+
function onClick() {
|
|
14
|
+
emit('click')
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<th class="{{ headerClasses }}" @if(sortable)@click="onClick()"@endif>
|
|
12
19
|
<slot />
|
|
13
20
|
</th>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<script server>
|
|
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
|
+
|
|
8
|
+
const orientationClasses = {
|
|
9
|
+
horizontal: 'flex-col',
|
|
10
|
+
vertical: 'flex-row',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const tabListOrientationClasses = {
|
|
14
|
+
horizontal: 'flex-row border-b',
|
|
15
|
+
vertical: 'flex-col border-r',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const variantClasses = {
|
|
19
|
+
line: 'border-gray-200 dark:border-gray-700',
|
|
20
|
+
pills: 'gap-2 border-0 bg-gray-100 dark:bg-gray-800 p-1 rounded-lg',
|
|
21
|
+
enclosed: 'border-gray-200 dark:border-gray-700',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const containerClasses = `flex ${orientationClasses[orientation]} ${className}`.trim()
|
|
25
|
+
export const tabListClasses = `flex ${tabListOrientationClasses[orientation]} ${variantClasses[variant]}`.trim()
|
|
26
|
+
export const tabPanelClasses = `p-4 focus:outline-none`.trim()
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<script client>
|
|
30
|
+
const emit = defineEmits()
|
|
31
|
+
const tabs = {{ tabs }}
|
|
32
|
+
const variant = {{ variant }}
|
|
33
|
+
const orientation = {{ orientation }}
|
|
34
|
+
const activeTab = state({{ defaultTab }})
|
|
35
|
+
|
|
36
|
+
function selectTab(index) {
|
|
37
|
+
activeTab.set(index)
|
|
38
|
+
emit('change', { index, tab: tabs[index] })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isActive(index) {
|
|
42
|
+
return activeTab() === index
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getTabClasses(index) {
|
|
46
|
+
const isActiveTab = isActive(index)
|
|
47
|
+
const base = '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'
|
|
48
|
+
if (variant === 'line') {
|
|
49
|
+
const border = orientation === 'horizontal' ? 'border-b-2' : 'border-r-2'
|
|
50
|
+
return `${base} ${border} ${isActiveTab ? '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'}`
|
|
51
|
+
}
|
|
52
|
+
if (variant === 'pills') {
|
|
53
|
+
return `${base} rounded-md ${isActiveTab ? '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'}`
|
|
54
|
+
}
|
|
55
|
+
if (variant === 'enclosed') {
|
|
56
|
+
const borders = orientation === 'horizontal' ? 'border-t border-l border-r rounded-t-md' : 'border-t border-l border-b rounded-l-md'
|
|
57
|
+
return `${base} ${borders} ${isActiveTab ? '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'}`
|
|
58
|
+
}
|
|
59
|
+
return base
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function onTabKey(event, index) {
|
|
63
|
+
const tabCount = tabs.length
|
|
64
|
+
let next = index
|
|
65
|
+
if (orientation === 'horizontal') {
|
|
66
|
+
if (event.key === 'ArrowRight') { event.preventDefault(); next = (index + 1) % tabCount }
|
|
67
|
+
else if (event.key === 'ArrowLeft') { event.preventDefault(); next = (index - 1 + tabCount) % tabCount }
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
if (event.key === 'ArrowDown') { event.preventDefault(); next = (index + 1) % tabCount }
|
|
71
|
+
else if (event.key === 'ArrowUp') { event.preventDefault(); next = (index - 1 + tabCount) % tabCount }
|
|
72
|
+
}
|
|
73
|
+
if (event.key === 'Home') { event.preventDefault(); next = 0 }
|
|
74
|
+
else if (event.key === 'End') { event.preventDefault(); next = tabCount - 1 }
|
|
75
|
+
if (next !== index) {
|
|
76
|
+
selectTab(next)
|
|
77
|
+
document.getElementById(`tab-${next}`)?.focus()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
defineExpose({ activeTab, selectTab })
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<div class="{{ containerClasses }}">
|
|
85
|
+
<div
|
|
86
|
+
class="{{ tabListClasses }}"
|
|
87
|
+
role="tablist"
|
|
88
|
+
aria-orientation="{{ orientation }}"
|
|
89
|
+
>
|
|
90
|
+
@foreach(tab in tabs)
|
|
91
|
+
<button
|
|
92
|
+
type="button"
|
|
93
|
+
:class="getTabClasses({{ $loop.index }})"
|
|
94
|
+
role="tab"
|
|
95
|
+
:aria-selected="isActive({{ $loop.index }}) ? 'true' : 'false'"
|
|
96
|
+
aria-controls="tab-panel-{{ $loop.index }}"
|
|
97
|
+
id="tab-{{ $loop.index }}"
|
|
98
|
+
:tabindex="isActive({{ $loop.index }}) ? '0' : '-1'"
|
|
99
|
+
@click="selectTab({{ $loop.index }})"
|
|
100
|
+
@keydown="onTabKey($event, {{ $loop.index }})"
|
|
101
|
+
>
|
|
102
|
+
@if(tab.icon)
|
|
103
|
+
<span class="inline-flex items-center gap-2">
|
|
104
|
+
<span x-html="{!! JSON.stringify(tab.icon) !!}"></span>
|
|
105
|
+
<span>{{ tab.label }}</span>
|
|
106
|
+
</span>
|
|
107
|
+
@else
|
|
108
|
+
{{ tab.label }}
|
|
109
|
+
@endif
|
|
110
|
+
</button>
|
|
111
|
+
@endforeach
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
@foreach(tab in tabs)
|
|
115
|
+
<div
|
|
116
|
+
:show="isActive({{ $loop.index }})"
|
|
117
|
+
id="tab-panel-{{ $loop.index }}"
|
|
118
|
+
class="{{ tabPanelClasses }}"
|
|
119
|
+
role="tabpanel"
|
|
120
|
+
aria-labelledby="tab-{{ $loop.index }}"
|
|
121
|
+
tabindex="0"
|
|
122
|
+
>
|
|
123
|
+
{!! tab.content !!}
|
|
124
|
+
</div>
|
|
125
|
+
@endforeach
|
|
126
|
+
</div>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
export const to = $props.to || 'body'
|
|
3
|
+
export const disabled = $props.disabled || false
|
|
4
|
+
export const defer = $props.defer || false
|
|
5
|
+
|
|
6
|
+
export const teleportId = `teleport-${Math.random().toString(36).slice(2, 11)}`
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<script client>
|
|
10
|
+
const to = {{ to }}
|
|
11
|
+
const isDisabled = {{ disabled }}
|
|
12
|
+
const shouldDefer = {{ defer }}
|
|
13
|
+
const teleportId = {{ teleportId }}
|
|
14
|
+
|
|
15
|
+
let teleportContainer = null
|
|
16
|
+
|
|
17
|
+
function teleport() {
|
|
18
|
+
if (isDisabled) return
|
|
19
|
+
const targetElement = typeof to === 'string' ? document.querySelector(to) : to
|
|
20
|
+
if (!targetElement) {
|
|
21
|
+
console.warn(`Teleport target "${to}" not found`)
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
const sourceElement = document.getElementById(teleportId)
|
|
25
|
+
if (!sourceElement) return
|
|
26
|
+
if (!teleportContainer) {
|
|
27
|
+
teleportContainer = document.createElement('div')
|
|
28
|
+
teleportContainer.setAttribute('data-teleport-id', teleportId)
|
|
29
|
+
}
|
|
30
|
+
while (sourceElement.firstChild) {
|
|
31
|
+
teleportContainer.appendChild(sourceElement.firstChild)
|
|
32
|
+
}
|
|
33
|
+
targetElement.appendChild(teleportContainer)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function returnContent() {
|
|
37
|
+
if (!teleportContainer || !teleportContainer.parentNode) return
|
|
38
|
+
const sourceElement = document.getElementById(teleportId)
|
|
39
|
+
if (!sourceElement) return
|
|
40
|
+
while (teleportContainer.firstChild) {
|
|
41
|
+
sourceElement.appendChild(teleportContainer.firstChild)
|
|
42
|
+
}
|
|
43
|
+
teleportContainer.parentNode.removeChild(teleportContainer)
|
|
44
|
+
teleportContainer = null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
onMount(() => {
|
|
48
|
+
if (shouldDefer) setTimeout(teleport, 0)
|
|
49
|
+
else teleport()
|
|
50
|
+
window.addEventListener('beforeunload', returnContent)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
onDestroy(() => {
|
|
54
|
+
returnContent()
|
|
55
|
+
window.removeEventListener('beforeunload', returnContent)
|
|
56
|
+
})
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
@if(disabled)
|
|
60
|
+
<slot />
|
|
61
|
+
@else
|
|
62
|
+
<div id="{{ teleportId }}" data-teleport-source="true">
|
|
63
|
+
<slot />
|
|
64
|
+
</div>
|
|
65
|
+
@endif
|