@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
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
export const className = $props.className || ''
|
|
3
|
+
export const as = $props.as || 'div'
|
|
4
|
+
|
|
5
|
+
const baseClasses = 'relative inline-block text-left'
|
|
6
|
+
export const dropdownClasses = `${baseClasses} ${className}`.trim()
|
|
7
|
+
export const dropdownInstanceId = `stx-dd-${Math.random().toString(36).slice(2, 11)}`
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<script client>
|
|
11
|
+
const emit = defineEmits()
|
|
12
|
+
const dropdownInstanceId = {{ dropdownInstanceId }}
|
|
13
|
+
const isOpen = state(false)
|
|
14
|
+
let rootEl = null
|
|
15
|
+
let outsideHandler = null
|
|
16
|
+
|
|
17
|
+
function toggle() {
|
|
18
|
+
isOpen.set(!isOpen())
|
|
19
|
+
emit('change', isOpen())
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function open() {
|
|
23
|
+
if (isOpen()) return
|
|
24
|
+
isOpen.set(true)
|
|
25
|
+
emit('change', true)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function close() {
|
|
29
|
+
if (!isOpen()) return
|
|
30
|
+
isOpen.set(false)
|
|
31
|
+
emit('change', false)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function onItemSelect(event) {
|
|
35
|
+
event.stopPropagation()
|
|
36
|
+
emit('select', event.detail)
|
|
37
|
+
close()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function onToggle(event) { event.stopPropagation(); toggle() }
|
|
41
|
+
function onOpen(event) { event.stopPropagation(); open() }
|
|
42
|
+
function onClose(event) { event.stopPropagation(); close() }
|
|
43
|
+
|
|
44
|
+
onMount(() => {
|
|
45
|
+
rootEl = document.querySelector(`[data-dropdown-id="${dropdownInstanceId}"]`)
|
|
46
|
+
outsideHandler = (event) => {
|
|
47
|
+
if (!isOpen()) return
|
|
48
|
+
if (rootEl && !rootEl.contains(event.target)) close()
|
|
49
|
+
}
|
|
50
|
+
document.addEventListener('click', outsideHandler, true)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
onDestroy(() => {
|
|
54
|
+
if (outsideHandler) document.removeEventListener('click', outsideHandler, true)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const dataState = derived(() => isOpen() ? 'open' : 'closed')
|
|
58
|
+
const dataExpanded = derived(() => isOpen() ? 'true' : 'false')
|
|
59
|
+
|
|
60
|
+
defineExpose({ isOpen, toggle, open, close })
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<component
|
|
64
|
+
:is="{{ as }}"
|
|
65
|
+
class="{{ dropdownClasses }}"
|
|
66
|
+
data-stx-dropdown
|
|
67
|
+
data-dropdown-id="{{ dropdownInstanceId }}"
|
|
68
|
+
:data-state="dataState()"
|
|
69
|
+
:data-expanded="dataExpanded()"
|
|
70
|
+
@stx:dropdown-toggle="onToggle($event)"
|
|
71
|
+
@stx:dropdown-open="onOpen($event)"
|
|
72
|
+
@stx:dropdown-close="onClose($event)"
|
|
73
|
+
@stx:dropdown-select="onItemSelect($event)"
|
|
74
|
+
>
|
|
75
|
+
<slot />
|
|
76
|
+
</component>
|
|
77
|
+
|
|
78
|
+
<style>
|
|
79
|
+
[data-stx-dropdown][data-state="closed"] [data-stx-dropdown-items] {
|
|
80
|
+
display: none;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
@@ -2,22 +2,28 @@
|
|
|
2
2
|
export const className = $props.className || ''
|
|
3
3
|
export const as = $props.as || 'button'
|
|
4
4
|
export const disabled = $props.disabled || false
|
|
5
|
-
export const onClick = $props.onClick
|
|
6
5
|
|
|
7
6
|
const baseClasses = 'inline-flex justify-center items-center rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75'
|
|
8
|
-
const buttonClasses = `${baseClasses} ${className}`.trim()
|
|
7
|
+
export const buttonClasses = `${baseClasses} ${className}`.trim()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<script client>
|
|
11
|
+
const emit = defineEmits()
|
|
12
|
+
const isDisabled = {{ disabled }}
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
if (
|
|
12
|
-
|
|
14
|
+
function onClick(event) {
|
|
15
|
+
if (isDisabled) return
|
|
16
|
+
emit('click', event)
|
|
17
|
+
event.currentTarget.dispatchEvent(new CustomEvent('stx:dropdown-toggle', { bubbles: true }))
|
|
13
18
|
}
|
|
14
19
|
</script>
|
|
15
20
|
|
|
16
21
|
<component
|
|
17
22
|
:is="{{ as }}"
|
|
18
23
|
class="{{ buttonClasses }}"
|
|
24
|
+
data-stx-dropdown-button
|
|
19
25
|
@if(disabled)disabled aria-disabled="true"@endif
|
|
20
|
-
@click="
|
|
26
|
+
@click="onClick($event)"
|
|
21
27
|
>
|
|
22
28
|
<slot />
|
|
23
29
|
</component>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
export const className = $props.className || ''
|
|
3
|
+
export const as = $props.as || 'div'
|
|
4
|
+
export const disabled = $props.disabled || false
|
|
5
|
+
export const value = $props.value
|
|
6
|
+
|
|
7
|
+
const baseClasses = 'group flex w-full items-center px-2 py-2 text-sm transition-colors text-neutral-900 dark:text-neutral-100 hover:bg-blue-500 hover:text-white dark:hover:bg-blue-600'
|
|
8
|
+
const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'
|
|
9
|
+
export const itemClasses = `${baseClasses} ${disabledClasses} ${className}`.trim()
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<script client>
|
|
13
|
+
const emit = defineEmits()
|
|
14
|
+
const isDisabled = {{ disabled }}
|
|
15
|
+
const itemValue = {{ value === undefined ? 'null' : value }}
|
|
16
|
+
|
|
17
|
+
function onClick(event) {
|
|
18
|
+
if (isDisabled) return
|
|
19
|
+
emit('click', event)
|
|
20
|
+
event.currentTarget.dispatchEvent(
|
|
21
|
+
new CustomEvent('stx:dropdown-select', { bubbles: true, detail: itemValue }),
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<component
|
|
27
|
+
:is="{{ as }}"
|
|
28
|
+
class="{{ itemClasses }}"
|
|
29
|
+
data-stx-dropdown-item
|
|
30
|
+
role="menuitem"
|
|
31
|
+
@if(disabled)aria-disabled="true"@endif
|
|
32
|
+
@click="onClick($event)"
|
|
33
|
+
>
|
|
34
|
+
<slot :disabled="disabled" />
|
|
35
|
+
</component>
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
<script server>
|
|
2
2
|
export const className = $props.className || ''
|
|
3
3
|
export const as = $props.as || 'div'
|
|
4
|
-
export const static_ = $props.static || false
|
|
5
4
|
|
|
6
5
|
const baseClasses = 'absolute z-10 mt-2 origin-top-right divide-y divide-neutral-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-neutral-800 dark:divide-neutral-700 dark:ring-neutral-600'
|
|
7
|
-
const itemsClasses = `${baseClasses} ${className}`.trim()
|
|
6
|
+
export const itemsClasses = `${baseClasses} ${className}`.trim()
|
|
8
7
|
</script>
|
|
9
8
|
|
|
10
9
|
<component
|
|
11
10
|
:is="{{ as }}"
|
|
12
11
|
class="{{ itemsClasses }}"
|
|
12
|
+
data-stx-dropdown-items
|
|
13
13
|
role="menu"
|
|
14
14
|
aria-orientation="vertical"
|
|
15
15
|
tabindex="-1"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
export const value = $props.value || ''
|
|
3
|
+
export const placeholder = $props.placeholder || 'Enter your email'
|
|
4
|
+
export const disabled = $props.disabled || false
|
|
5
|
+
export const readonly = $props.readonly || false
|
|
6
|
+
export const size = $props.size || 'md'
|
|
7
|
+
export const error = $props.error || false
|
|
8
|
+
export const label = $props.label || ''
|
|
9
|
+
export const helperText = $props.helperText || ''
|
|
10
|
+
export const required = $props.required || false
|
|
11
|
+
export const className = $props.className || ''
|
|
12
|
+
|
|
13
|
+
export const emailIcon = '<svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></svg>'
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<TextInput
|
|
17
|
+
:value="value"
|
|
18
|
+
:placeholder="placeholder"
|
|
19
|
+
:disabled="disabled"
|
|
20
|
+
:readonly="readonly"
|
|
21
|
+
:size="size"
|
|
22
|
+
:error="error"
|
|
23
|
+
:label="label"
|
|
24
|
+
:helperText="helperText"
|
|
25
|
+
:required="required"
|
|
26
|
+
:leftIcon="emailIcon"
|
|
27
|
+
:clearable="true"
|
|
28
|
+
:className="className"
|
|
29
|
+
inputClassName="font-mono"
|
|
30
|
+
/>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
export const action = $props.action || ''
|
|
3
|
+
export const method = $props.method || 'POST'
|
|
4
|
+
export const validationSchema = $props.validationSchema || {}
|
|
5
|
+
export const className = $props.className || ''
|
|
6
|
+
|
|
7
|
+
export const formClasses = `space-y-6 ${className}`.trim()
|
|
8
|
+
export const errorClasses = `mt-1 text-sm text-red-600 dark:text-red-400`.trim()
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<script client>
|
|
12
|
+
const emit = defineEmits()
|
|
13
|
+
const validationSchema = {{ validationSchema }}
|
|
14
|
+
|
|
15
|
+
const errors = state({})
|
|
16
|
+
const isSubmitting = state(false)
|
|
17
|
+
const submitCount = state(0)
|
|
18
|
+
|
|
19
|
+
function validateField(name, value, allValues) {
|
|
20
|
+
const schema = validationSchema[name]
|
|
21
|
+
if (!schema) return null
|
|
22
|
+
if (schema.required && !value) {
|
|
23
|
+
return schema.requiredMessage || `${name} is required`
|
|
24
|
+
}
|
|
25
|
+
if (schema.minLength && String(value).length < schema.minLength) {
|
|
26
|
+
return schema.minLengthMessage || `${name} must be at least ${schema.minLength} characters`
|
|
27
|
+
}
|
|
28
|
+
if (schema.maxLength && String(value).length > schema.maxLength) {
|
|
29
|
+
return schema.maxLengthMessage || `${name} must be at most ${schema.maxLength} characters`
|
|
30
|
+
}
|
|
31
|
+
if (schema.pattern && !new RegExp(schema.pattern).test(value)) {
|
|
32
|
+
return schema.patternMessage || `${name} is invalid`
|
|
33
|
+
}
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function validateForm(values) {
|
|
38
|
+
const next = {}
|
|
39
|
+
let valid = true
|
|
40
|
+
for (const name of Object.keys(validationSchema)) {
|
|
41
|
+
const err = validateField(name, values[name], values)
|
|
42
|
+
if (err) {
|
|
43
|
+
next[name] = err
|
|
44
|
+
valid = false
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
errors.set(next)
|
|
48
|
+
return valid
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function onSubmit(event) {
|
|
52
|
+
event.preventDefault()
|
|
53
|
+
submitCount.set(submitCount() + 1)
|
|
54
|
+
|
|
55
|
+
const formData = new FormData(event.target)
|
|
56
|
+
const values = {}
|
|
57
|
+
for (const [k, v] of formData.entries()) values[k] = v
|
|
58
|
+
|
|
59
|
+
if (!validateForm(values)) {
|
|
60
|
+
emit('error', errors())
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
isSubmitting.set(true)
|
|
65
|
+
try {
|
|
66
|
+
emit('submit', values)
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
isSubmitting.set(false)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function setErrors(next) {
|
|
74
|
+
errors.set({ ...errors(), ...next })
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function reset(form) {
|
|
78
|
+
errors.set({})
|
|
79
|
+
isSubmitting.set(false)
|
|
80
|
+
submitCount.set(0)
|
|
81
|
+
if (form?.reset) form.reset()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
defineExpose({ errors, isSubmitting, submitCount, setErrors, reset, validateForm })
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<form
|
|
88
|
+
class="{{ formClasses }}"
|
|
89
|
+
@if(action)action="{{ action }}"@endif
|
|
90
|
+
method="{{ method }}"
|
|
91
|
+
@submit="onSubmit($event)"
|
|
92
|
+
novalidate
|
|
93
|
+
>
|
|
94
|
+
<slot />
|
|
95
|
+
</form>
|
|
@@ -10,18 +10,14 @@ export const height = $props.height
|
|
|
10
10
|
export const lazy = $props.lazy ?? true
|
|
11
11
|
export const placeholder = $props.placeholder || 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300"%3E%3Crect fill="%23e5e7eb" width="400" height="300"/%3E%3C/svg%3E'
|
|
12
12
|
export const blurDataURL = $props.blurDataURL || ''
|
|
13
|
-
export const onLoad = $props.onLoad
|
|
14
|
-
export const onError = $props.onError
|
|
15
13
|
export const aspectRatio = $props.aspectRatio
|
|
16
14
|
export const objectFit = $props.objectFit || 'cover'
|
|
17
15
|
export const rounded = $props.rounded ?? false
|
|
18
16
|
export const zoomOnHover = $props.zoomOnHover || false
|
|
19
17
|
export const className = $props.className || ''
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
export const hasWebP = webpSrc || webpSrcSet
|
|
19
|
+
export const hasWebP = !!(webpSrc || webpSrcSet)
|
|
23
20
|
|
|
24
|
-
// Object fit classes
|
|
25
21
|
const objectFitClasses = {
|
|
26
22
|
contain: 'object-contain',
|
|
27
23
|
cover: 'object-cover',
|
|
@@ -30,23 +26,69 @@ const objectFitClasses = {
|
|
|
30
26
|
'scale-down': 'object-scale-down',
|
|
31
27
|
}
|
|
32
28
|
|
|
33
|
-
// Rounded classes
|
|
34
29
|
const roundedClass = rounded === true ? 'rounded-lg' : rounded === 'full' ? 'rounded-full' : rounded ? `rounded-${rounded}` : ''
|
|
35
|
-
|
|
36
30
|
const zoomClasses = zoomOnHover ? 'transform hover:scale-110 transition-transform duration-300 ease-in-out cursor-zoom-in' : ''
|
|
37
31
|
|
|
38
32
|
export const containerClasses = `relative overflow-hidden ${roundedClass} ${className}`.trim()
|
|
39
33
|
export const imageClasses = `w-full h-full transition-opacity duration-300 ${objectFitClasses[objectFit]} ${zoomClasses}`.trim()
|
|
40
|
-
export const placeholderClasses = `absolute inset-0 bg-gray-200 dark:bg-gray-700 ${roundedClass}`.trim()
|
|
41
34
|
export const blurPlaceholderClasses = `absolute inset-0 ${roundedClass} filter blur-lg scale-110`.trim()
|
|
35
|
+
export const containerStyle = aspectRatio ? `padding-bottom: ${(1 / aspectRatio) * 100}%` : ''
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<script client>
|
|
39
|
+
const emit = defineEmits()
|
|
40
|
+
const lazy = {{ lazy }}
|
|
41
|
+
const realSrc = {{ src }}
|
|
42
|
+
const realSrcSet = {{ srcSet }}
|
|
43
|
+
const errorPlaceholder = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300"%3E%3Crect fill="%23e5e7eb" width="400" height="300"/%3E%3Ctext x="50%25" y="50%25" fill="%239ca3af" font-size="16" text-anchor="middle" dominant-baseline="middle"%3EImage not found%3C/text%3E%3C/svg%3E'
|
|
44
|
+
|
|
45
|
+
function onImageLoad(event) {
|
|
46
|
+
const img = event.target
|
|
47
|
+
img.classList.remove('opacity-0')
|
|
48
|
+
img.classList.add('opacity-100')
|
|
49
|
+
emit('load', event)
|
|
50
|
+
}
|
|
42
51
|
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
function onImageError(event) {
|
|
53
|
+
const img = event.target
|
|
54
|
+
img.src = errorPlaceholder
|
|
55
|
+
emit('error', event)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onMount(() => {
|
|
59
|
+
if (!lazy) return
|
|
60
|
+
if (typeof IntersectionObserver === 'undefined') return
|
|
61
|
+
const lazyImages = document.querySelectorAll('.lazy-image:not([data-stx-img-loaded])')
|
|
62
|
+
const observer = new IntersectionObserver((entries, obs) => {
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
if (!entry.isIntersecting) continue
|
|
65
|
+
const img = entry.target
|
|
66
|
+
img.setAttribute('data-stx-img-loaded', '1')
|
|
67
|
+
const dataSrc = img.getAttribute('data-src')
|
|
68
|
+
const dataSrcSet = img.getAttribute('data-srcset')
|
|
69
|
+
if (!dataSrc) continue
|
|
70
|
+
const probe = new Image()
|
|
71
|
+
probe.onload = () => {
|
|
72
|
+
img.src = dataSrc
|
|
73
|
+
if (dataSrcSet) img.srcset = dataSrcSet
|
|
74
|
+
img.classList.remove('opacity-0')
|
|
75
|
+
img.classList.add('opacity-100')
|
|
76
|
+
obs.unobserve(img)
|
|
77
|
+
}
|
|
78
|
+
probe.onerror = () => {
|
|
79
|
+
onImageError({ target: img })
|
|
80
|
+
obs.unobserve(img)
|
|
81
|
+
}
|
|
82
|
+
probe.src = dataSrc
|
|
83
|
+
if (dataSrcSet) probe.srcset = dataSrcSet
|
|
84
|
+
}
|
|
85
|
+
}, { rootMargin: '50px' })
|
|
86
|
+
for (const img of lazyImages) observer.observe(img)
|
|
87
|
+
})
|
|
45
88
|
</script>
|
|
46
89
|
|
|
47
|
-
<div class="{{ containerClasses }}" style="{{
|
|
90
|
+
<div class="{{ containerClasses }}" style="{{ containerStyle }}">
|
|
48
91
|
@if(blurDataURL)
|
|
49
|
-
<!-- Blur placeholder -->
|
|
50
92
|
<img
|
|
51
93
|
src="{{ blurDataURL }}"
|
|
52
94
|
alt=""
|
|
@@ -56,17 +98,13 @@ export const paddingBottom = aspectRatio ? `${(1 / aspectRatio) * 100}%` : undef
|
|
|
56
98
|
@endif
|
|
57
99
|
|
|
58
100
|
@if(hasWebP)
|
|
59
|
-
<!-- Picture element with WebP support -->
|
|
60
101
|
<picture>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
/>
|
|
68
|
-
@endif
|
|
69
|
-
|
|
102
|
+
<source
|
|
103
|
+
type="image/webp"
|
|
104
|
+
@if(webpSrcSet)srcset="{{ webpSrcSet }}"@endif
|
|
105
|
+
@if(webpSrc && !webpSrcSet)srcset="{{ webpSrc }}"@endif
|
|
106
|
+
@if(sizes)sizes="{{ sizes }}"@endif
|
|
107
|
+
/>
|
|
70
108
|
@if(lazy)
|
|
71
109
|
<img
|
|
72
110
|
src="{{ placeholder }}"
|
|
@@ -78,8 +116,8 @@ export const paddingBottom = aspectRatio ? `${(1 / aspectRatio) * 100}%` : undef
|
|
|
78
116
|
width="{{ width }}"
|
|
79
117
|
height="{{ height }}"
|
|
80
118
|
loading="lazy"
|
|
81
|
-
|
|
82
|
-
|
|
119
|
+
@load="onImageLoad($event)"
|
|
120
|
+
@error="onImageError($event)"
|
|
83
121
|
/>
|
|
84
122
|
@else
|
|
85
123
|
<img
|
|
@@ -90,13 +128,12 @@ export const paddingBottom = aspectRatio ? `${(1 / aspectRatio) * 100}%` : undef
|
|
|
90
128
|
class="{{ imageClasses }}"
|
|
91
129
|
width="{{ width }}"
|
|
92
130
|
height="{{ height }}"
|
|
93
|
-
|
|
94
|
-
|
|
131
|
+
@load="onImageLoad($event)"
|
|
132
|
+
@error="onImageError($event)"
|
|
95
133
|
/>
|
|
96
134
|
@endif
|
|
97
135
|
</picture>
|
|
98
136
|
@else
|
|
99
|
-
<!-- Regular img element -->
|
|
100
137
|
@if(lazy)
|
|
101
138
|
<img
|
|
102
139
|
src="{{ placeholder }}"
|
|
@@ -108,8 +145,8 @@ export const paddingBottom = aspectRatio ? `${(1 / aspectRatio) * 100}%` : undef
|
|
|
108
145
|
width="{{ width }}"
|
|
109
146
|
height="{{ height }}"
|
|
110
147
|
loading="lazy"
|
|
111
|
-
|
|
112
|
-
|
|
148
|
+
@load="onImageLoad($event)"
|
|
149
|
+
@error="onImageError($event)"
|
|
113
150
|
/>
|
|
114
151
|
@else
|
|
115
152
|
<img
|
|
@@ -120,78 +157,9 @@ export const paddingBottom = aspectRatio ? `${(1 / aspectRatio) * 100}%` : undef
|
|
|
120
157
|
class="{{ imageClasses }}"
|
|
121
158
|
width="{{ width }}"
|
|
122
159
|
height="{{ height }}"
|
|
123
|
-
|
|
124
|
-
|
|
160
|
+
@load="onImageLoad($event)"
|
|
161
|
+
@error="onImageError($event)"
|
|
125
162
|
/>
|
|
126
163
|
@endif
|
|
127
164
|
@endif
|
|
128
|
-
|
|
129
|
-
<!-- Loading skeleton -->
|
|
130
|
-
<div class="{{ placeholderClasses }} skeleton" style="display: none;">
|
|
131
|
-
<div class="animate-pulse w-full h-full bg-gray-300 dark:bg-gray-600"></div>
|
|
132
|
-
</div>
|
|
133
165
|
</div>
|
|
134
|
-
|
|
135
|
-
<script>
|
|
136
|
-
// Intersection Observer for lazy loading
|
|
137
|
-
if (lazy && typeof window !== 'undefined' && 'IntersectionObserver' in window) {
|
|
138
|
-
const lazyImages = document.querySelectorAll('.lazy-image')
|
|
139
|
-
|
|
140
|
-
const imageObserver = new IntersectionObserver((entries, observer) => {
|
|
141
|
-
entries.forEach(entry => {
|
|
142
|
-
if (entry.isIntersecting) {
|
|
143
|
-
const img = entry.target
|
|
144
|
-
const realSrc = img.getAttribute('data-src')
|
|
145
|
-
const realSrcSet = img.getAttribute('data-srcset')
|
|
146
|
-
|
|
147
|
-
if (realSrc) {
|
|
148
|
-
// Load the real image
|
|
149
|
-
const tempImg = new Image()
|
|
150
|
-
tempImg.onload = () => {
|
|
151
|
-
img.src = realSrc
|
|
152
|
-
if (realSrcSet) {
|
|
153
|
-
img.srcset = realSrcSet
|
|
154
|
-
}
|
|
155
|
-
img.classList.remove('opacity-0')
|
|
156
|
-
img.classList.add('opacity-100')
|
|
157
|
-
observer.unobserve(img)
|
|
158
|
-
}
|
|
159
|
-
tempImg.onerror = () => {
|
|
160
|
-
handleImageError({ target: img })
|
|
161
|
-
observer.unobserve(img)
|
|
162
|
-
}
|
|
163
|
-
tempImg.src = realSrc
|
|
164
|
-
if (realSrcSet) {
|
|
165
|
-
tempImg.srcset = realSrcSet
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
})
|
|
170
|
-
}, {
|
|
171
|
-
rootMargin: '50px', // Start loading 50px before entering viewport
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
lazyImages.forEach(img => imageObserver.observe(img))
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function handleImageLoad(event) {
|
|
178
|
-
const img = event.target
|
|
179
|
-
img.classList.remove('opacity-0')
|
|
180
|
-
img.classList.add('opacity-100')
|
|
181
|
-
|
|
182
|
-
if (onLoad) {
|
|
183
|
-
onLoad(event)
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function handleImageError(event) {
|
|
188
|
-
const img = event.target
|
|
189
|
-
|
|
190
|
-
// Show a broken image placeholder
|
|
191
|
-
img.src = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300"%3E%3Crect fill="%23e5e7eb" width="400" height="300"/%3E%3Ctext x="50%25" y="50%25" fill="%239ca3af" font-size="16" text-anchor="middle" dominant-baseline="middle"%3EImage not found%3C/text%3E%3C/svg%3E'
|
|
192
|
-
|
|
193
|
-
if (onError) {
|
|
194
|
-
onError(event)
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
</script>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
export const value = $props.value
|
|
3
|
+
export const multiple = $props.multiple || false
|
|
4
|
+
export const className = $props.className || ''
|
|
5
|
+
export const as = $props.as || 'div'
|
|
6
|
+
|
|
7
|
+
const baseClasses = 'relative'
|
|
8
|
+
export const listboxClasses = `${baseClasses} ${className}`.trim()
|
|
9
|
+
export const listboxInstanceId = `stx-lb-${Math.random().toString(36).slice(2, 11)}`
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<script client>
|
|
13
|
+
const emit = defineEmits()
|
|
14
|
+
const listboxInstanceId = {{ listboxInstanceId }}
|
|
15
|
+
const multiple = {{ multiple }}
|
|
16
|
+
const initialValue = {{ value === undefined ? 'null' : value }}
|
|
17
|
+
|
|
18
|
+
const isOpen = state(false)
|
|
19
|
+
const selected = state(initialValue)
|
|
20
|
+
let rootEl = null
|
|
21
|
+
let outsideHandler = null
|
|
22
|
+
|
|
23
|
+
function isSelectedValue(v) {
|
|
24
|
+
const cur = selected()
|
|
25
|
+
if (multiple && Array.isArray(cur)) return cur.includes(v)
|
|
26
|
+
return cur === v
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function selectValue(v) {
|
|
30
|
+
if (multiple) {
|
|
31
|
+
const cur = Array.isArray(selected()) ? selected() : []
|
|
32
|
+
selected.set(cur.includes(v) ? cur.filter(x => x !== v) : [...cur, v])
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
selected.set(v)
|
|
36
|
+
isOpen.set(false)
|
|
37
|
+
}
|
|
38
|
+
syncOptionStates()
|
|
39
|
+
emit('change', selected())
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function syncOptionStates() {
|
|
43
|
+
if (!rootEl) return
|
|
44
|
+
const options = rootEl.querySelectorAll('[data-stx-listbox-option]')
|
|
45
|
+
for (const opt of options) {
|
|
46
|
+
const raw = opt.dataset.optionValue
|
|
47
|
+
let v
|
|
48
|
+
try { v = JSON.parse(raw ?? 'null') }
|
|
49
|
+
catch { v = raw }
|
|
50
|
+
const sel = isSelectedValue(v)
|
|
51
|
+
opt.setAttribute('aria-selected', sel ? 'true' : 'false')
|
|
52
|
+
opt.setAttribute('data-selected', sel ? 'true' : 'false')
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function open() { if (!isOpen()) { isOpen.set(true); emit('change', selected()) } }
|
|
57
|
+
function close() { if (isOpen()) { isOpen.set(false) } }
|
|
58
|
+
function toggle() { isOpen() ? close() : open() }
|
|
59
|
+
|
|
60
|
+
function onToggle(event) { event.stopPropagation(); toggle() }
|
|
61
|
+
function onSelect(event) {
|
|
62
|
+
event.stopPropagation()
|
|
63
|
+
selectValue(event.detail)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
onMount(() => {
|
|
67
|
+
rootEl = document.querySelector(`[data-listbox-id="${listboxInstanceId}"]`)
|
|
68
|
+
syncOptionStates()
|
|
69
|
+
outsideHandler = (event) => {
|
|
70
|
+
if (!isOpen()) return
|
|
71
|
+
if (rootEl && !rootEl.contains(event.target)) close()
|
|
72
|
+
}
|
|
73
|
+
document.addEventListener('click', outsideHandler, true)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
onDestroy(() => {
|
|
77
|
+
if (outsideHandler) document.removeEventListener('click', outsideHandler, true)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const dataState = derived(() => isOpen() ? 'open' : 'closed')
|
|
81
|
+
|
|
82
|
+
defineExpose({ isOpen, selected, toggle, open, close, selectValue })
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<component
|
|
86
|
+
:is="{{ as }}"
|
|
87
|
+
class="{{ listboxClasses }}"
|
|
88
|
+
data-stx-listbox
|
|
89
|
+
data-listbox-id="{{ listboxInstanceId }}"
|
|
90
|
+
:data-state="dataState()"
|
|
91
|
+
@stx:listbox-toggle="onToggle($event)"
|
|
92
|
+
@stx:listbox-select="onSelect($event)"
|
|
93
|
+
>
|
|
94
|
+
<slot />
|
|
95
|
+
</component>
|
|
96
|
+
|
|
97
|
+
<style>
|
|
98
|
+
[data-stx-listbox][data-state="closed"] [data-stx-listbox-options] {
|
|
99
|
+
display: none;
|
|
100
|
+
}
|
|
101
|
+
</style>
|
|
@@ -5,13 +5,24 @@ export const disabled = $props.disabled || false
|
|
|
5
5
|
|
|
6
6
|
const baseClasses = 'relative w-full cursor-pointer rounded-lg bg-white dark:bg-neutral-800 py-2 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-blue-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-blue-300'
|
|
7
7
|
const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : ''
|
|
8
|
-
const buttonClasses = `${baseClasses} ${disabledClasses} ${className}`.trim()
|
|
8
|
+
export const buttonClasses = `${baseClasses} ${disabledClasses} ${className}`.trim()
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<script client>
|
|
12
|
+
const isDisabled = {{ disabled }}
|
|
13
|
+
|
|
14
|
+
function onClick(event) {
|
|
15
|
+
if (isDisabled) return
|
|
16
|
+
event.currentTarget.dispatchEvent(new CustomEvent('stx:listbox-toggle', { bubbles: true }))
|
|
17
|
+
}
|
|
9
18
|
</script>
|
|
10
19
|
|
|
11
20
|
<component
|
|
12
21
|
:is="{{ as }}"
|
|
13
22
|
class="{{ buttonClasses }}"
|
|
23
|
+
data-stx-listbox-button
|
|
14
24
|
@if(disabled)disabled aria-disabled="true"@endif
|
|
25
|
+
@click="onClick($event)"
|
|
15
26
|
>
|
|
16
27
|
<slot />
|
|
17
28
|
</component>
|