@stacksjs/components 0.2.54 → 0.2.66
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-pt8j917s.stx +146 -0
- package/dist/AccordionItem-hbhzx63p.stx +37 -0
- package/dist/{Checkbox-zgdmy3p5.stx → Checkbox-aqmq4cqv.stx} +3 -1
- package/dist/{Dialog-hczpexs4.stx → Dialog-y5rz2vrq.stx} +5 -1
- package/dist/{Drawer-xhevp5cw.stx → Drawer-hrz7qcyp.stx} +4 -1
- package/dist/Notification-03vfh3k6.stx +117 -0
- package/dist/{NumberInput-drnvtqej.stx → NumberInput-g41gmd2c.stx} +2 -1
- package/dist/{Pagination-n3d31cns.stx → Pagination-wtgxmzey.stx} +3 -1
- package/dist/{PasswordInput-n02x6053.stx → PasswordInput-4rsq1gwb.stx} +2 -1
- package/dist/{Progress-yxcntr4x.stx → Progress-9dhakbh4.stx} +2 -1
- package/dist/{Radio-cgjv2hb2.stx → Radio-djtb5dpc.stx} +2 -1
- package/dist/{Select-28y0pg83.stx → Select-1vnd0brz.stx} +2 -1
- package/dist/{Sidebar-73vc78as.stx → Sidebar-0jzasrmt.stx} +116 -22
- package/dist/{SidebarHeader-7v174a9c.stx → SidebarHeader-ywpmx14d.stx} +80 -3
- package/dist/{SidebarItem-3y3e8g2t.stx → SidebarItem-h53sgqzz.stx} +8 -8
- package/dist/{SidebarSection-z5ey6awe.stx → SidebarSection-g195cb4f.stx} +4 -3
- package/dist/{Switch-r5t5w04w.stx → Switch-p1bcr9ks.stx} +3 -1
- package/dist/TabPanel-5htevsq6.stx +25 -0
- package/dist/Tabs-79brys99.stx +202 -0
- package/dist/{TextInput-kf084763.stx → TextInput-g0vaz0pe.stx} +4 -1
- package/dist/{Textarea-xg4rr2xz.stx → Textarea-96kawv5c.stx} +2 -1
- package/dist/{Tooltip-m0tqd72e.stx → Tooltip-y61h04gp.stx} +2 -1
- package/dist/index.js +3909 -15
- package/dist/ui/accordion/Accordion.stx +9 -1
- package/dist/ui/accordion/AccordionItem.stx +7 -0
- package/dist/ui/accordion/index.d.ts +30 -1
- package/dist/ui/input/NumberInput.stx +1 -1
- package/dist/ui/input/TextInput.stx +1 -1
- package/dist/ui/notification/Notification.stx +10 -8
- package/dist/ui/select/Select.stx +1 -1
- package/dist/ui/sidebar/Sidebar.stx +9 -0
- package/dist/ui/sidebar/SidebarHeader.stx +7 -0
- package/dist/ui/sidebar/SidebarItem.stx +1 -1
- package/dist/ui/sidebar/index.d.ts +14 -0
- package/dist/ui/tabs/TabPanel.stx +9 -0
- package/dist/ui/tabs/Tabs.stx +140 -0
- package/dist/ui/tabs/index.d.ts +32 -1
- package/dist/ui/textarea/Textarea.stx +1 -1
- package/package.json +3 -3
- package/src/ui/accordion/Accordion.stx +90 -32
- package/src/ui/accordion/AccordionItem.stx +37 -0
- package/src/ui/accordion/index.ts +32 -1
- package/src/ui/checkbox/Checkbox.stx +3 -1
- package/src/ui/dialog/Dialog.stx +5 -1
- package/src/ui/drawer/Drawer.stx +4 -1
- package/src/ui/input/NumberInput.stx +2 -1
- package/src/ui/input/PasswordInput.stx +2 -1
- package/src/ui/input/TextInput.stx +4 -1
- package/src/ui/notification/Notification.stx +65 -26
- package/src/ui/pagination/Pagination.stx +3 -1
- package/src/ui/progress/Progress.stx +2 -1
- package/src/ui/radio/Radio.stx +2 -1
- package/src/ui/select/Select.stx +2 -1
- package/src/ui/sidebar/Sidebar.stx +116 -22
- package/src/ui/sidebar/SidebarHeader.stx +80 -3
- package/src/ui/sidebar/SidebarItem.stx +8 -8
- package/src/ui/sidebar/SidebarSection.stx +4 -3
- package/src/ui/sidebar/index.ts +30 -0
- package/src/ui/switch/Switch.stx +3 -1
- package/src/ui/tabs/TabPanel.stx +25 -0
- package/src/ui/tabs/Tabs.stx +108 -32
- package/src/ui/tabs/index.ts +35 -1
- package/src/ui/textarea/Textarea.stx +2 -1
- package/src/ui/tooltip/Tooltip.stx +2 -1
- package/src/utils/accessibility.ts +1 -1
- package/dist/Accordion-19x8q4rj.stx +0 -88
- package/dist/Notification-tb9b525s.stx +0 -78
- package/dist/Tabs-bzvvn4ke.stx +0 -126
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
// Two modes (see stacksjs/stx#1703):
|
|
3
|
+
// 1. Legacy prop API — pass `items={[{title, content}, …]}` for simple
|
|
4
|
+
// string-content accordions (kept for backward compatibility with
|
|
5
|
+
// existing visual snapshots and consumers).
|
|
6
|
+
// 2. Slot API — drop `<AccordionItem title="…">` children for arbitrary
|
|
7
|
+
// content. Each AccordionItem renders its own header + content panel;
|
|
8
|
+
// this parent attaches click handlers and toggles content visibility.
|
|
9
|
+
export const items = $props.items || []
|
|
10
|
+
export const allowMultiple = $props.allowMultiple || false
|
|
11
|
+
export const defaultOpen = $props.defaultOpen || []
|
|
12
|
+
export const className = $props.className || ''
|
|
13
|
+
|
|
14
|
+
export const accordionClasses = `divide-y divide-gray-200 dark:divide-gray-700 border border-gray-200 dark:border-gray-700 rounded-lg ${className}`.trim()
|
|
15
|
+
export const headerClasses = `w-full flex items-center justify-between px-4 py-3 text-left font-medium text-gray-900 dark:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900`.trim()
|
|
16
|
+
export const iconClasses = `w-5 h-5 text-gray-500 dark:text-gray-400 transition-transform duration-200`.trim()
|
|
17
|
+
export const contentClasses = `px-4 py-3 text-gray-700 dark:text-gray-300`.trim()
|
|
18
|
+
export const hasPropItems = Array.isArray(items) && items.length > 0
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<script client>
|
|
22
|
+
const emit = defineEmits()
|
|
23
|
+
const allowMultiple = {{ allowMultiple }}
|
|
24
|
+
const hasPropItems = {{ hasPropItems }}
|
|
25
|
+
const containerRef = useRef()
|
|
26
|
+
const openItems = state({{ defaultOpen }})
|
|
27
|
+
|
|
28
|
+
function findOwnItems() {
|
|
29
|
+
const root = containerRef.value
|
|
30
|
+
if (!root) return []
|
|
31
|
+
return Array.from(root.querySelectorAll('[data-stx-accordion-item]'))
|
|
32
|
+
.filter(item => item.closest('[data-stx-accordion]') === root)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onMount(() => {
|
|
36
|
+
if (hasPropItems) return // legacy mode: server-rendered, no wiring needed
|
|
37
|
+
const items = findOwnItems()
|
|
38
|
+
items.forEach((item, idx) => {
|
|
39
|
+
const header = item.querySelector(':scope > [data-stx-accordion-header]')
|
|
40
|
+
if (header && !header.__stx_wired) {
|
|
41
|
+
header.__stx_wired = true
|
|
42
|
+
header.addEventListener('click', () => toggleItem(idx))
|
|
43
|
+
header.addEventListener('keydown', (e) => onHeaderKey(e, idx))
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Sync the open state into the DOM: show/hide content panels, update
|
|
49
|
+
// aria-expanded on headers, rotate chevrons.
|
|
50
|
+
effect(() => {
|
|
51
|
+
if (hasPropItems) return
|
|
52
|
+
const items = findOwnItems()
|
|
53
|
+
const open = openItems()
|
|
54
|
+
items.forEach((item, idx) => {
|
|
55
|
+
const isOpenItem = open.includes(idx)
|
|
56
|
+
const header = item.querySelector(':scope > [data-stx-accordion-header]')
|
|
57
|
+
const content = item.querySelector(':scope > [data-stx-accordion-content]')
|
|
58
|
+
const chevron = item.querySelector(':scope > [data-stx-accordion-header] [data-stx-accordion-chevron]')
|
|
59
|
+
if (header) header.setAttribute('aria-expanded', isOpenItem ? 'true' : 'false')
|
|
60
|
+
if (content) {
|
|
61
|
+
if (isOpenItem) content.removeAttribute('hidden')
|
|
62
|
+
else content.setAttribute('hidden', '')
|
|
63
|
+
}
|
|
64
|
+
if (chevron) chevron.style.transform = `rotate(${isOpenItem ? 180 : 0}deg)`
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
function toggleItem(index) {
|
|
69
|
+
const cur = openItems()
|
|
70
|
+
if (allowMultiple) {
|
|
71
|
+
openItems.set(cur.includes(index) ? cur.filter(i => i !== index) : [...cur, index])
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
openItems.set(cur.includes(index) ? [] : [index])
|
|
75
|
+
}
|
|
76
|
+
emit('change', openItems())
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isOpen(index) {
|
|
80
|
+
return openItems().includes(index)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function onHeaderKey(event, index) {
|
|
84
|
+
const root = containerRef.value
|
|
85
|
+
if (!root) return
|
|
86
|
+
const buttons = root.querySelectorAll('button[data-stx-accordion-header], button[data-accordion-header]')
|
|
87
|
+
if (event.key === 'ArrowDown') {
|
|
88
|
+
event.preventDefault()
|
|
89
|
+
buttons[index + 1]?.focus()
|
|
90
|
+
}
|
|
91
|
+
else if (event.key === 'ArrowUp') {
|
|
92
|
+
event.preventDefault()
|
|
93
|
+
buttons[index - 1]?.focus()
|
|
94
|
+
}
|
|
95
|
+
else if (event.key === 'Home') {
|
|
96
|
+
event.preventDefault()
|
|
97
|
+
buttons[0]?.focus()
|
|
98
|
+
}
|
|
99
|
+
else if (event.key === 'End') {
|
|
100
|
+
event.preventDefault()
|
|
101
|
+
buttons[buttons.length - 1]?.focus()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
defineExpose({ openItems, toggleItem })
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<div x-ref="containerRef" data-stx-accordion class="{{ accordionClasses }}" role="region">
|
|
109
|
+
@if(hasPropItems)
|
|
110
|
+
@foreach(item in items)
|
|
111
|
+
<div>
|
|
112
|
+
<button
|
|
113
|
+
type="button"
|
|
114
|
+
data-accordion-header
|
|
115
|
+
class="{{ headerClasses }}"
|
|
116
|
+
@click="toggleItem({{ $loop.index }})"
|
|
117
|
+
@keydown="onHeaderKey($event, {{ $loop.index }})"
|
|
118
|
+
:aria-expanded="isOpen({{ $loop.index }}) ? 'true' : 'false'"
|
|
119
|
+
aria-controls="accordion-content-{{ $loop.index }}"
|
|
120
|
+
>
|
|
121
|
+
<span>{{ item.title }}</span>
|
|
122
|
+
<svg
|
|
123
|
+
class="{{ iconClasses }}"
|
|
124
|
+
:style="`transform: rotate(${isOpen({{ $loop.index }}) ? 180 : 0}deg)`"
|
|
125
|
+
fill="none"
|
|
126
|
+
viewBox="0 0 24 24"
|
|
127
|
+
stroke="currentColor"
|
|
128
|
+
>
|
|
129
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
|
130
|
+
</svg>
|
|
131
|
+
</button>
|
|
132
|
+
|
|
133
|
+
<div
|
|
134
|
+
:show="isOpen({{ $loop.index }})"
|
|
135
|
+
id="accordion-content-{{ $loop.index }}"
|
|
136
|
+
class="{{ contentClasses }}"
|
|
137
|
+
role="region"
|
|
138
|
+
>
|
|
139
|
+
{!! item.content !!}
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
@endforeach
|
|
143
|
+
@else
|
|
144
|
+
<slot />
|
|
145
|
+
@endif
|
|
146
|
+
</div>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
// Slot wrapper that the parent <Accordion> discovers and wires up on mount.
|
|
3
|
+
// Renders its own header button + content panel; the parent attaches click
|
|
4
|
+
// handlers and toggles the content panel's `hidden` attribute as openItems
|
|
5
|
+
// changes. Hard-coded styling here matches the legacy prop-mode markup so
|
|
6
|
+
// snapshots compare cleanly in both modes. See stacksjs/stx#1703.
|
|
7
|
+
export const title = $props.title || ''
|
|
8
|
+
export const className = $props.className || ''
|
|
9
|
+
|
|
10
|
+
const headerClasses = 'w-full flex items-center justify-between px-4 py-3 text-left font-medium text-gray-900 dark:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900'
|
|
11
|
+
const iconClasses = 'w-5 h-5 text-gray-500 dark:text-gray-400 transition-transform duration-200'
|
|
12
|
+
const contentClasses = 'px-4 py-3 text-gray-700 dark:text-gray-300'
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<div data-stx-accordion-item data-title="{{ title }}" class="{{ className }}">
|
|
16
|
+
<button
|
|
17
|
+
type="button"
|
|
18
|
+
data-stx-accordion-header
|
|
19
|
+
data-accordion-header
|
|
20
|
+
class="{{ headerClasses }}"
|
|
21
|
+
aria-expanded="false"
|
|
22
|
+
>
|
|
23
|
+
<span>{{ title }}</span>
|
|
24
|
+
<svg
|
|
25
|
+
data-stx-accordion-chevron
|
|
26
|
+
class="{{ iconClasses }}"
|
|
27
|
+
fill="none"
|
|
28
|
+
viewBox="0 0 24 24"
|
|
29
|
+
stroke="currentColor"
|
|
30
|
+
>
|
|
31
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
|
32
|
+
</svg>
|
|
33
|
+
</button>
|
|
34
|
+
<div data-stx-accordion-content class="{{ contentClasses }}" role="region" hidden>
|
|
35
|
+
<slot />
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
@@ -26,7 +26,9 @@ export const inputId = `checkbox-${value || label}`
|
|
|
26
26
|
const emit = defineEmits()
|
|
27
27
|
const isDisabled = {{ disabled }}
|
|
28
28
|
const isIndeterminate = {{ indeterminate }}
|
|
29
|
-
|
|
29
|
+
// Reactive prop binding so the parent can drive `checked` via signals.
|
|
30
|
+
// See stacksjs/stx#1704.
|
|
31
|
+
const isChecked = useReactiveProp('checked', {{ checked }})
|
|
30
32
|
|
|
31
33
|
function onInputChange(event) {
|
|
32
34
|
if (isDisabled) {
|
|
@@ -8,7 +8,11 @@ export const dialogClasses = `${baseClasses} ${className}`.trim()
|
|
|
8
8
|
|
|
9
9
|
<script client>
|
|
10
10
|
const emit = defineEmits()
|
|
11
|
-
|
|
11
|
+
// useReactiveProp wires `:open="parentSignal()"` from the parent into our
|
|
12
|
+
// local `isOpen` signal — when the parent's signal changes, bindAttr
|
|
13
|
+
// updates the root's `open` attribute, our MutationObserver picks it up,
|
|
14
|
+
// and isOpen flips. See stacksjs/stx#1704.
|
|
15
|
+
const isOpen = useReactiveProp('open', {{ open }})
|
|
12
16
|
|
|
13
17
|
function close() {
|
|
14
18
|
isOpen.set(false)
|
|
@@ -32,7 +32,10 @@ export const closeButtonClasses = `absolute top-0 ${closeButtonPosition[position
|
|
|
32
32
|
|
|
33
33
|
<script client>
|
|
34
34
|
const emit = defineEmits()
|
|
35
|
-
|
|
35
|
+
// useReactiveProp lets the parent drive `open` via signals
|
|
36
|
+
// (`:open="drawerOpen()"`) instead of the prop being captured once.
|
|
37
|
+
// See stacksjs/stx#1704.
|
|
38
|
+
const isOpen = useReactiveProp('open', {{ open }})
|
|
36
39
|
|
|
37
40
|
function close() {
|
|
38
41
|
isOpen.set(false)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
// Initial defaults captured at render time. The client script wraps these
|
|
3
|
+
// in signals so consumers can mutate them after mount (fetch-then-toast,
|
|
4
|
+
// auto-dismiss, etc.). See stacksjs/stx#1692.
|
|
5
|
+
export const initialShow = $props.show !== false
|
|
6
|
+
export const initialTitle = $props.title || ''
|
|
7
|
+
export const initialMessage = $props.message || ''
|
|
8
|
+
export const initialType = $props.type || 'info'
|
|
9
|
+
export const initialPosition = $props.position || 'top-right'
|
|
10
|
+
export const initialDuration = Number($props.duration ?? 5000)
|
|
11
|
+
export const className = $props.className || ''
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<script client>
|
|
15
|
+
const emit = defineEmits()
|
|
16
|
+
|
|
17
|
+
const isVisible = state({{ initialShow }})
|
|
18
|
+
const title = state({{ initialTitle }})
|
|
19
|
+
const message = state({{ initialMessage }})
|
|
20
|
+
const type = state({{ initialType }})
|
|
21
|
+
const position = state({{ initialPosition }})
|
|
22
|
+
const duration = state({{ initialDuration }})
|
|
23
|
+
|
|
24
|
+
const typeColors = {
|
|
25
|
+
info: 'bg-blue-50 text-blue-900 dark:bg-blue-900 dark:text-blue-100',
|
|
26
|
+
success: 'bg-green-50 text-green-900 dark:bg-green-900 dark:text-green-100',
|
|
27
|
+
warning: 'bg-yellow-50 text-yellow-900 dark:bg-yellow-900 dark:text-yellow-100',
|
|
28
|
+
error: 'bg-red-50 text-red-900 dark:bg-red-900 dark:text-red-100',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const positions = {
|
|
32
|
+
'top-left': 'top-0 left-0',
|
|
33
|
+
'top-right': 'top-0 right-0',
|
|
34
|
+
'top-center': 'top-0 left-1/2 -translate-x-1/2',
|
|
35
|
+
'bottom-left': 'bottom-0 left-0',
|
|
36
|
+
'bottom-right': 'bottom-0 right-0',
|
|
37
|
+
'bottom-center': 'bottom-0 left-1/2 -translate-x-1/2',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const baseClasses = 'fixed z-50 m-4 max-w-md w-full pointer-events-auto rounded-lg p-4 shadow-lg ring-1 ring-black ring-opacity-5 transition-all'
|
|
41
|
+
const extraClassName = {{ JSON.stringify(className) }}
|
|
42
|
+
|
|
43
|
+
const notificationClasses = derived(() => {
|
|
44
|
+
const color = typeColors[type()] || typeColors.info
|
|
45
|
+
const pos = positions[position()] || positions['top-right']
|
|
46
|
+
return `${baseClasses} ${color} ${pos} ${extraClassName}`.trim()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
let autoHideTimer = null
|
|
50
|
+
function clearAutoHide() {
|
|
51
|
+
if (autoHideTimer) {
|
|
52
|
+
clearTimeout(autoHideTimer)
|
|
53
|
+
autoHideTimer = null
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function scheduleAutoHide() {
|
|
57
|
+
clearAutoHide()
|
|
58
|
+
const d = duration()
|
|
59
|
+
if (d > 0 && isVisible()) {
|
|
60
|
+
autoHideTimer = setTimeout(() => close(), d)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function close() {
|
|
65
|
+
isVisible.set(false)
|
|
66
|
+
clearAutoHide()
|
|
67
|
+
emit('close')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function open(opts) {
|
|
71
|
+
if (opts && typeof opts === 'object') {
|
|
72
|
+
if (opts.title !== undefined) title.set(opts.title)
|
|
73
|
+
if (opts.message !== undefined) message.set(opts.message)
|
|
74
|
+
if (opts.type !== undefined) type.set(opts.type)
|
|
75
|
+
if (opts.position !== undefined) position.set(opts.position)
|
|
76
|
+
if (opts.duration !== undefined) duration.set(Number(opts.duration))
|
|
77
|
+
}
|
|
78
|
+
isVisible.set(true)
|
|
79
|
+
scheduleAutoHide()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
onMount(() => {
|
|
83
|
+
if (isVisible()) scheduleAutoHide()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
defineExpose({
|
|
87
|
+
open,
|
|
88
|
+
close,
|
|
89
|
+
isVisible,
|
|
90
|
+
title,
|
|
91
|
+
message,
|
|
92
|
+
type,
|
|
93
|
+
position,
|
|
94
|
+
duration,
|
|
95
|
+
})
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<div :if="isVisible()" :class="notificationClasses()" role="alert">
|
|
99
|
+
<div class="flex items-start">
|
|
100
|
+
<div class="flex-1">
|
|
101
|
+
<p :if="title()" class="text-sm font-medium" :text="title()"></p>
|
|
102
|
+
<p :if="message()" class="mt-1 text-sm" :class="title() ? 'opacity-90' : ''" :text="message()"></p>
|
|
103
|
+
<slot :if="!title() && !message()" />
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<button
|
|
107
|
+
type="button"
|
|
108
|
+
@click="close()"
|
|
109
|
+
class="ml-4 inline-flex rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2"
|
|
110
|
+
>
|
|
111
|
+
<span class="sr-only">Close</span>
|
|
112
|
+
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
113
|
+
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
114
|
+
</svg>
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
@@ -37,7 +37,8 @@ const emit = defineEmits()
|
|
|
37
37
|
const stepSize = {{ step }}
|
|
38
38
|
const minVal = {{ min === undefined ? 'undefined' : min }}
|
|
39
39
|
const maxVal = {{ max === undefined ? 'undefined' : max }}
|
|
40
|
-
|
|
40
|
+
// Reactive prop binding — parent can drive value via signals. See #1704.
|
|
41
|
+
const inputValue = useReactiveProp('value', {{ value }}, { parse: (v) => v == null || v === '' ? '' : String(v) })
|
|
41
42
|
|
|
42
43
|
function onInput(event) {
|
|
43
44
|
inputValue.set(event.target.value)
|
|
@@ -17,7 +17,9 @@ export const iconClasses = `w-5 h-5`.trim()
|
|
|
17
17
|
const emit = defineEmits()
|
|
18
18
|
const totalPages = {{ totalPages }}
|
|
19
19
|
const siblingCount = {{ siblingCount }}
|
|
20
|
-
|
|
20
|
+
// Reactive prop binding — parent can drive `current-page` via signals
|
|
21
|
+
// (useful for URL-driven pagination via useSearchParams). See #1704.
|
|
22
|
+
const page = useReactiveProp('current-page', {{ currentPage }})
|
|
21
23
|
|
|
22
24
|
function goToPage(target) {
|
|
23
25
|
if (target >= 1 && target <= totalPages && target !== page()) {
|
|
@@ -34,7 +34,8 @@ export const iconClasses = iconSizeClasses[size]
|
|
|
34
34
|
<script client>
|
|
35
35
|
const emit = defineEmits()
|
|
36
36
|
const showPassword = state(false)
|
|
37
|
-
|
|
37
|
+
// Reactive prop binding — parent can drive value via signals. See #1704.
|
|
38
|
+
const inputValue = useReactiveProp('value', {{ value }}, { parse: (v) => v == null ? '' : String(v) })
|
|
38
39
|
|
|
39
40
|
function onInput(event) {
|
|
40
41
|
inputValue.set(event.target.value)
|
|
@@ -45,7 +45,8 @@ export const circularBarColor = color === 'primary' ? 'stroke-blue-600 dark:stro
|
|
|
45
45
|
const max = {{ max }}
|
|
46
46
|
const indeterminate = {{ indeterminate }}
|
|
47
47
|
const circumference = {{ circumference }}
|
|
48
|
-
|
|
48
|
+
// Reactive prop binding — parent can drive `value` via signals. See #1704.
|
|
49
|
+
const value = useReactiveProp('value', {{ value }})
|
|
49
50
|
|
|
50
51
|
const percentage = derived(() => Math.min(Math.max((value() / max) * 100, 0), 100))
|
|
51
52
|
const percentLabel = derived(() => `${Math.round(percentage())}%`)
|
|
@@ -25,7 +25,8 @@ export const inputId = `radio-${value || label}`
|
|
|
25
25
|
const emit = defineEmits()
|
|
26
26
|
const isDisabled = {{ disabled }}
|
|
27
27
|
const radioValue = {{ value }}
|
|
28
|
-
|
|
28
|
+
// Reactive prop binding — parent can drive `checked` via signals. See #1704.
|
|
29
|
+
const isChecked = useReactiveProp('checked', {{ checked }})
|
|
29
30
|
|
|
30
31
|
function onInputChange(event) {
|
|
31
32
|
if (isDisabled) {
|
|
@@ -27,7 +27,8 @@ export const selectId = `select-${value}`
|
|
|
27
27
|
|
|
28
28
|
<script client>
|
|
29
29
|
const emit = defineEmits()
|
|
30
|
-
|
|
30
|
+
// Reactive prop binding — parent can drive `value` via signals. See #1704.
|
|
31
|
+
const selectedValue = useReactiveProp('value', {{ value }}, { parse: (v) => v == null ? '' : String(v) })
|
|
31
32
|
|
|
32
33
|
function onChange(event) {
|
|
33
34
|
selectedValue.set(event.target.value)
|
|
@@ -4,10 +4,19 @@ export const collapsed = $props.collapsed ?? false
|
|
|
4
4
|
export const collapsible = $props.collapsible ?? true
|
|
5
5
|
export const width = $props.width || 240
|
|
6
6
|
export const minWidth = $props.minWidth || 64
|
|
7
|
+
export const collapseMode = $props.collapseMode || 'rail'
|
|
7
8
|
export const variant = $props.variant || 'tahoe'
|
|
8
9
|
export const position = $props.position || 'left'
|
|
9
10
|
export const placement = $props.placement || 'fixed'
|
|
10
11
|
export const bordered = $props.bordered ?? true
|
|
12
|
+
export const materialOpacity = $props.materialOpacity ?? (variant === 'desktop' ? 0.78 : 0.7)
|
|
13
|
+
export const materialDarkOpacity = $props.materialDarkOpacity ?? (variant === 'desktop' ? 0.8 : materialOpacity)
|
|
14
|
+
export const materialScheme = $props.materialScheme || 'system'
|
|
15
|
+
export const persistKey = $props.persistKey || ''
|
|
16
|
+
export const shellSelector = $props.shellSelector || ''
|
|
17
|
+
export const widthVar = $props.widthVar || '--stx-sidebar-width'
|
|
18
|
+
export const collapsedClass = $props.collapsedClass || ''
|
|
19
|
+
export const nativeMaterialCollapse = $props.nativeMaterialCollapse ?? (variant === 'desktop' && collapseMode === 'hidden')
|
|
11
20
|
export const className = $props.className || ''
|
|
12
21
|
|
|
13
22
|
const variantStyles = {
|
|
@@ -44,13 +53,13 @@ const variantStyles = {
|
|
|
44
53
|
slot: 'px-5 py-5',
|
|
45
54
|
},
|
|
46
55
|
desktop: {
|
|
47
|
-
bg: 'bg-
|
|
48
|
-
border: 'border-white/
|
|
49
|
-
backdrop: 'backdrop-blur-3xl backdrop-saturate-
|
|
56
|
+
bg: 'bg-[#f3f3f1]/48 dark:bg-neutral-950/32',
|
|
57
|
+
border: 'border-white/15 dark:border-white/10',
|
|
58
|
+
backdrop: 'backdrop-blur-3xl backdrop-saturate-150',
|
|
50
59
|
tint: false,
|
|
51
60
|
shimmer: true,
|
|
52
|
-
nav: 'px-
|
|
53
|
-
slot: 'px-
|
|
61
|
+
nav: 'px-5 py-2 space-y-1',
|
|
62
|
+
slot: 'px-5 py-2',
|
|
54
63
|
},
|
|
55
64
|
}
|
|
56
65
|
|
|
@@ -77,7 +86,9 @@ export const containerClasses = [
|
|
|
77
86
|
className,
|
|
78
87
|
].filter(Boolean).join(' ')
|
|
79
88
|
|
|
80
|
-
export const collapseBtnPosition =
|
|
89
|
+
export const collapseBtnPosition = variant === 'desktop'
|
|
90
|
+
? (position === 'left' ? 'left-[166px]' : 'right-[166px]')
|
|
91
|
+
: (position === 'left' ? 'right-2' : 'left-2')
|
|
81
92
|
export const navClasses = style.nav || 'px-3 py-2 space-y-1'
|
|
82
93
|
export const slotClasses = style.slot || 'px-3 py-2'
|
|
83
94
|
</script>
|
|
@@ -88,9 +99,18 @@ const collapsedProp = {{ collapsed }}
|
|
|
88
99
|
const collapsibleProp = {{ collapsible }}
|
|
89
100
|
const widthProp = {{ width }}
|
|
90
101
|
const minWidthProp = {{ minWidth }}
|
|
102
|
+
const collapseModeProp = {{ collapseMode }}
|
|
91
103
|
const sectionsProp = {{ sections }}
|
|
92
104
|
const positionProp = {{ position }}
|
|
93
105
|
const variantProp = {{ variant }}
|
|
106
|
+
const materialOpacityProp = {{ materialOpacity }}
|
|
107
|
+
const materialDarkOpacityProp = {{ materialDarkOpacity }}
|
|
108
|
+
const materialSchemeProp = {{ materialScheme }}
|
|
109
|
+
const persistKeyProp = {{ persistKey }}
|
|
110
|
+
const shellSelectorProp = {{ shellSelector }}
|
|
111
|
+
const widthVarProp = {{ widthVar }}
|
|
112
|
+
const collapsedClassProp = {{ collapsedClass }}
|
|
113
|
+
const nativeMaterialCollapseProp = {{ nativeMaterialCollapse }}
|
|
94
114
|
|
|
95
115
|
const collapsed = state(collapsedProp)
|
|
96
116
|
const isHovered = state(false)
|
|
@@ -98,14 +118,45 @@ const hasNativeSidebar = state(false)
|
|
|
98
118
|
|
|
99
119
|
const showExpanded = derived(() => !collapsed() || isHovered())
|
|
100
120
|
const sidebarWidth = derived(() => {
|
|
121
|
+
if (collapsed() && collapseModeProp === 'hidden') return '0px'
|
|
101
122
|
if (collapsed() && !isHovered()) return `${minWidthProp}px`
|
|
102
123
|
return `${widthProp}px`
|
|
103
124
|
})
|
|
104
125
|
const collapseAriaLabel = derived(() => collapsed() ? 'Expand sidebar' : 'Collapse sidebar')
|
|
105
|
-
|
|
126
|
+
|
|
127
|
+
function applyCollapseEffects(nextCollapsed) {
|
|
128
|
+
if (persistKeyProp) {
|
|
129
|
+
try {
|
|
130
|
+
window.localStorage?.setItem(persistKeyProp, nextCollapsed ? 'true' : 'false')
|
|
131
|
+
}
|
|
132
|
+
catch {}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (shellSelectorProp) {
|
|
136
|
+
document.querySelectorAll(shellSelectorProp).forEach((shell) => {
|
|
137
|
+
shell.style?.setProperty?.(widthVarProp, nextCollapsed && collapseModeProp === 'hidden' ? '0px' : `${widthProp}px`)
|
|
138
|
+
if (shell.dataset) shell.dataset.sidebarCollapsed = nextCollapsed ? 'true' : 'false'
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
document.documentElement.dataset.stxSidebarCollapsed = nextCollapsed ? 'true' : 'false'
|
|
143
|
+
if (collapsedClassProp) {
|
|
144
|
+
document.documentElement.classList.toggle(collapsedClassProp, nextCollapsed)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (nativeMaterialCollapseProp) {
|
|
148
|
+
window.craft?.window?.setWebSidebarCollapsed?.(nextCollapsed).catch?.(() => {})
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function setCollapsed(nextCollapsed, shouldEmit = true) {
|
|
153
|
+
collapsed.set(nextCollapsed)
|
|
154
|
+
applyCollapseEffects(nextCollapsed)
|
|
155
|
+
if (shouldEmit) emit('collapse', nextCollapsed)
|
|
156
|
+
}
|
|
106
157
|
|
|
107
158
|
function onMouseEnter() {
|
|
108
|
-
if (collapsed() && collapsibleProp) isHovered.set(true)
|
|
159
|
+
if (collapsed() && collapsibleProp && collapseModeProp !== 'hidden') isHovered.set(true)
|
|
109
160
|
}
|
|
110
161
|
|
|
111
162
|
function onMouseLeave() {
|
|
@@ -113,8 +164,7 @@ function onMouseLeave() {
|
|
|
113
164
|
}
|
|
114
165
|
|
|
115
166
|
function toggleCollapse() {
|
|
116
|
-
|
|
117
|
-
emit('collapse', collapsed())
|
|
167
|
+
setCollapsed(!collapsed())
|
|
118
168
|
}
|
|
119
169
|
|
|
120
170
|
function onSectionToggle(sectionId) {
|
|
@@ -125,22 +175,46 @@ function onItemClick(event) {
|
|
|
125
175
|
emit('itemClick', event.detail)
|
|
126
176
|
}
|
|
127
177
|
|
|
178
|
+
function isCraftRuntime() {
|
|
179
|
+
return window.__craftNativeSidebar === true
|
|
180
|
+
|| window.craft?.__craft_bridge_loaded === true
|
|
181
|
+
|| !!window.craft?.window
|
|
182
|
+
|| !!window.webkit?.messageHandlers?.craft
|
|
183
|
+
}
|
|
184
|
+
|
|
128
185
|
onMount(async () => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
186
|
+
if (persistKeyProp) {
|
|
187
|
+
try {
|
|
188
|
+
const saved = window.localStorage?.getItem(persistKeyProp)
|
|
189
|
+
if (saved === 'true' || saved === 'false') collapsed.set(saved === 'true')
|
|
190
|
+
}
|
|
191
|
+
catch {}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
applyCollapseEffects(collapsed())
|
|
195
|
+
|
|
196
|
+
if (variantProp === 'desktop' && isCraftRuntime()) {
|
|
197
|
+
document.documentElement.classList.add('has-stx-sidebar-material')
|
|
198
|
+
if (document.body?.dataset) document.body.dataset.sidebarMaterial = 'desktop'
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
hasNativeSidebar.set(isCraftRuntime() && window.craft?.hasNativeSidebar?.() === true)
|
|
202
|
+
|
|
203
|
+
const nativeSidebarApi = window.craft?.nativeUI || window.craft?.components
|
|
204
|
+
if (isCraftRuntime() && nativeSidebarApi?.createSidebar) {
|
|
135
205
|
try {
|
|
136
|
-
await
|
|
206
|
+
const sidebar = await nativeSidebarApi.createSidebar({
|
|
137
207
|
position: positionProp,
|
|
138
208
|
width: widthProp,
|
|
139
209
|
minWidth: minWidthProp,
|
|
210
|
+
collapseMode: collapseModeProp,
|
|
140
211
|
collapsible: collapsibleProp,
|
|
141
212
|
variant: variantProp,
|
|
142
213
|
material: variantProp === 'desktop' ? 'sidebar' : undefined,
|
|
143
|
-
backgroundEffect: variantProp === 'desktop' ? '
|
|
214
|
+
backgroundEffect: variantProp === 'desktop' ? 'vibrancy' : undefined,
|
|
215
|
+
materialOpacity: materialOpacityProp,
|
|
216
|
+
materialDarkOpacity: materialDarkOpacityProp,
|
|
217
|
+
materialScheme: materialSchemeProp,
|
|
144
218
|
allowsVibrancy: variantProp === 'desktop' || variantProp === 'vibrancy' || variantProp === 'tahoe',
|
|
145
219
|
sections: sectionsProp.map(s => ({
|
|
146
220
|
id: s.id,
|
|
@@ -148,6 +222,7 @@ onMount(async () => {
|
|
|
148
222
|
items: s.items?.map(i => ({ id: i.id, label: i.label, icon: i.icon, href: i.href, badge: i.badge })),
|
|
149
223
|
})),
|
|
150
224
|
})
|
|
225
|
+
if (sidebar) hasNativeSidebar.set(true)
|
|
151
226
|
}
|
|
152
227
|
catch (err) {
|
|
153
228
|
console.warn('[Sidebar] Failed to create native sidebar, using web fallback:', err)
|
|
@@ -161,7 +236,10 @@ defineExpose({ collapsed, toggle: toggleCollapse })
|
|
|
161
236
|
<aside
|
|
162
237
|
:show="!hasNativeSidebar()"
|
|
163
238
|
class="{{ containerClasses }} sidebar-container"
|
|
164
|
-
|
|
239
|
+
data-sidebar-variant="{{ variant }}"
|
|
240
|
+
data-sidebar-material-scheme="{{ materialScheme }}"
|
|
241
|
+
:aria-hidden="String(collapsed() && collapseModeProp === 'hidden')"
|
|
242
|
+
:style="`width: ${sidebarWidth()}; --stx-sidebar-material-opacity: ${materialOpacityProp}; --stx-sidebar-material-dark-opacity: ${materialDarkOpacityProp}`"
|
|
165
243
|
@mouseenter="onMouseEnter()"
|
|
166
244
|
@mouseleave="onMouseLeave()"
|
|
167
245
|
role="navigation"
|
|
@@ -171,7 +249,7 @@ defineExpose({ collapsed, toggle: toggleCollapse })
|
|
|
171
249
|
<div class="absolute inset-0 pointer-events-none bg-gradient-to-b from-amber-50/30 via-orange-50/20 to-stone-100/30 dark:from-neutral-800/20 dark:via-neutral-900/10 dark:to-neutral-800/20 mix-blend-overlay"></div>
|
|
172
250
|
@endif
|
|
173
251
|
@if(variantShimmer)
|
|
174
|
-
<div class="absolute inset-0 pointer-events-none bg-[
|
|
252
|
+
<div class="absolute inset-0 pointer-events-none bg-[linear-gradient(112deg,rgba(255,255,255,0.20)_0%,rgba(255,255,255,0.04)_45%,rgba(226,226,222,0.16)_100%)] dark:bg-[linear-gradient(112deg,rgba(255,255,255,0.08)_0%,rgba(255,255,255,0.02)_46%,rgba(255,255,255,0.06)_100%)]"></div>
|
|
175
253
|
@endif
|
|
176
254
|
|
|
177
255
|
@if($slots.header)
|
|
@@ -213,13 +291,15 @@ defineExpose({ collapsed, toggle: toggleCollapse })
|
|
|
213
291
|
|
|
214
292
|
@if(collapsible)
|
|
215
293
|
<button
|
|
216
|
-
:show="showExpanded()"
|
|
217
294
|
type="button"
|
|
218
295
|
class="absolute top-4 {{ collapseBtnPosition }} p-1.5 rounded-lg text-stone-400 hover:text-stone-600 hover:bg-black/5 dark:hover:text-neutral-300 dark:hover:bg-white/10 transition-colors z-20"
|
|
219
296
|
@click="toggleCollapse()"
|
|
220
297
|
:aria-label="collapseAriaLabel()"
|
|
298
|
+
data-sidebar-collapse
|
|
299
|
+
data-stx-sidebar-collapse-button
|
|
221
300
|
>
|
|
222
|
-
<div :
|
|
301
|
+
<div :show="!collapsed()" class="i-hugeicons-sidebar-left w-5 h-5"></div>
|
|
302
|
+
<div :show="collapsed()" class="i-hugeicons-sidebar-right w-5 h-5"></div>
|
|
223
303
|
</button>
|
|
224
304
|
@endif
|
|
225
305
|
</aside>
|
|
@@ -229,7 +309,21 @@ defineExpose({ collapsed, toggle: toggleCollapse })
|
|
|
229
309
|
.overflow-y-auto::-webkit-scrollbar-track { background: transparent; }
|
|
230
310
|
.overflow-y-auto::-webkit-scrollbar-thumb { background-color: rgba(0, 0, 0, 0.15); border-radius: 3px; }
|
|
231
311
|
.overflow-y-auto::-webkit-scrollbar-thumb:hover { background-color: rgba(0, 0, 0, 0.25); }
|
|
312
|
+
.sidebar-container[data-sidebar-variant="desktop"] {
|
|
313
|
+
background-color: rgba(246, 246, 244, var(--stx-sidebar-material-opacity, 0.78));
|
|
314
|
+
backdrop-filter: blur(24px) saturate(1.08);
|
|
315
|
+
-webkit-backdrop-filter: blur(24px) saturate(1.08);
|
|
316
|
+
}
|
|
317
|
+
html.has-native-sidebar [data-stx-native-window-controls] {
|
|
318
|
+
display: none;
|
|
319
|
+
}
|
|
320
|
+
html.has-native-sidebar [data-stx-sidebar-web-chrome] {
|
|
321
|
+
margin-left: 202px;
|
|
322
|
+
}
|
|
232
323
|
@media (prefers-color-scheme: dark) {
|
|
324
|
+
.sidebar-container[data-sidebar-variant="desktop"]:not([data-sidebar-material-scheme="light"]) {
|
|
325
|
+
background-color: rgba(24, 24, 23, var(--stx-sidebar-material-dark-opacity, 0.8));
|
|
326
|
+
}
|
|
233
327
|
.overflow-y-auto::-webkit-scrollbar-thumb { background-color: rgba(255, 255, 255, 0.15); }
|
|
234
328
|
.overflow-y-auto::-webkit-scrollbar-thumb:hover { background-color: rgba(255, 255, 255, 0.25); }
|
|
235
329
|
}
|