@stacksjs/components 0.2.92 → 0.2.94
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/{Sidebar-yb115a2w.stx → Sidebar-4hn7vw4n.stx} +18 -0
- package/dist/index.js +5 -5
- package/package.json +5 -6
- package/src/components/CodeBlock.stx +0 -54
- package/src/components/Footer.stx +0 -66
- package/src/components/Hero.stx +0 -25
- package/src/components/Installation.stx +0 -37
- package/src/components/index.ts +0 -4
- package/src/composables/index.ts +0 -4
- package/src/composables/useCopyCode.ts +0 -73
- package/src/composables/useDarkMode.ts +0 -100
- package/src/composables/useSEO.ts +0 -230
- package/src/composables/utilities/index.ts +0 -7
- package/src/composables/utilities/useClickOutside.ts +0 -72
- package/src/composables/utilities/useFocusTrap.ts +0 -159
- package/src/composables/utilities/useKeyboard.ts +0 -124
- package/src/composables/utilities/useLocalStorage.ts +0 -132
- package/src/composables/utilities/useMediaQuery.ts +0 -118
- package/src/index.ts +0 -99
- package/src/stx.d.ts +0 -4
- package/src/ui/accordion/Accordion.stx +0 -146
- package/src/ui/accordion/AccordionItem.stx +0 -37
- package/src/ui/accordion/index.ts +0 -47
- package/src/ui/audio/Audio.stx +0 -50
- package/src/ui/audio/README.md +0 -41
- package/src/ui/audio/index.ts +0 -13
- package/src/ui/auth/Login.stx +0 -161
- package/src/ui/auth/README.md +0 -142
- package/src/ui/auth/Signup.stx +0 -159
- package/src/ui/auth/TwoFactorChallenge.stx +0 -111
- package/src/ui/auth/index.ts +0 -32
- package/src/ui/avatar/Avatar.stx +0 -77
- package/src/ui/avatar/index.ts +0 -12
- package/src/ui/badge/Badge.stx +0 -62
- package/src/ui/badge/index.ts +0 -10
- package/src/ui/breadcrumb/Breadcrumb.stx +0 -79
- package/src/ui/breadcrumb/index.ts +0 -15
- package/src/ui/button/Button.stx +0 -74
- package/src/ui/button/README.md +0 -80
- package/src/ui/button/index.ts +0 -85
- package/src/ui/calendar/Calendar.stx +0 -189
- package/src/ui/calendar/README.md +0 -254
- package/src/ui/calendar/index.ts +0 -14
- package/src/ui/card/Card.stx +0 -68
- package/src/ui/card/index.ts +0 -13
- package/src/ui/checkbox/Checkbox.stx +0 -86
- package/src/ui/checkbox/index.ts +0 -16
- package/src/ui/combobox/Combobox.stx +0 -96
- package/src/ui/combobox/ComboboxButton.stx +0 -25
- package/src/ui/combobox/ComboboxInput.stx +0 -32
- package/src/ui/combobox/ComboboxOption.stx +0 -49
- package/src/ui/combobox/ComboboxOptions.stx +0 -16
- package/src/ui/combobox/README.md +0 -150
- package/src/ui/combobox/index.ts +0 -40
- package/src/ui/command-palette/CommandPalette.stx +0 -81
- package/src/ui/command-palette/CommandPaletteItem.stx +0 -27
- package/src/ui/command-palette/README.md +0 -42
- package/src/ui/command-palette/index.ts +0 -15
- package/src/ui/dialog/Dialog.stx +0 -41
- package/src/ui/dialog/DialogBackdrop.stx +0 -9
- package/src/ui/dialog/DialogDescription.stx +0 -11
- package/src/ui/dialog/DialogPanel.stx +0 -16
- package/src/ui/dialog/DialogTitle.stx +0 -11
- package/src/ui/dialog/README.md +0 -121
- package/src/ui/dialog/index.ts +0 -32
- package/src/ui/drawer/Drawer.stx +0 -120
- package/src/ui/drawer/README.md +0 -90
- package/src/ui/drawer/index.ts +0 -9
- package/src/ui/dropdown/Dropdown.stx +0 -82
- package/src/ui/dropdown/DropdownButton.stx +0 -29
- package/src/ui/dropdown/DropdownItem.stx +0 -35
- package/src/ui/dropdown/DropdownItems.stx +0 -18
- package/src/ui/dropdown/README.md +0 -114
- package/src/ui/dropdown/index.ts +0 -29
- package/src/ui/form/Form.stx +0 -95
- package/src/ui/form/index.ts +0 -67
- package/src/ui/heatmap/Heatmap.stx +0 -294
- package/src/ui/image/Image.stx +0 -165
- package/src/ui/image/README.md +0 -164
- package/src/ui/image/index.ts +0 -22
- package/src/ui/input/EmailInput.stx +0 -30
- package/src/ui/input/NumberInput.stx +0 -126
- package/src/ui/input/PasswordInput.stx +0 -143
- package/src/ui/input/SearchInput.stx +0 -44
- package/src/ui/input/TextInput.stx +0 -132
- package/src/ui/input/index.ts +0 -180
- package/src/ui/listbox/Listbox.stx +0 -101
- package/src/ui/listbox/ListboxButton.stx +0 -28
- package/src/ui/listbox/ListboxLabel.stx +0 -11
- package/src/ui/listbox/ListboxOption.stx +0 -49
- package/src/ui/listbox/ListboxOptions.stx +0 -16
- package/src/ui/listbox/README.md +0 -135
- package/src/ui/listbox/index.ts +0 -39
- package/src/ui/navigator/Navigator.stx +0 -113
- package/src/ui/navigator/README.md +0 -352
- package/src/ui/navigator/index.ts +0 -20
- package/src/ui/notification/Notification.stx +0 -117
- package/src/ui/notification/README.md +0 -114
- package/src/ui/notification/index.ts +0 -12
- package/src/ui/pagination/Pagination.stx +0 -150
- package/src/ui/pagination/index.ts +0 -11
- package/src/ui/payment/Checkout.stx +0 -31
- package/src/ui/payment/DefaultPaymentMethod.stx +0 -62
- package/src/ui/payment/PaymentMethods.stx +0 -104
- package/src/ui/payment/README.md +0 -345
- package/src/ui/payment/SubscriptionCheckout.stx +0 -323
- package/src/ui/payment/index.ts +0 -59
- package/src/ui/popover/Popover.stx +0 -60
- package/src/ui/popover/PopoverButton.stx +0 -29
- package/src/ui/popover/PopoverPanel.stx +0 -23
- package/src/ui/popover/README.md +0 -91
- package/src/ui/popover/index.ts +0 -22
- package/src/ui/portal/Portal.stx +0 -57
- package/src/ui/portal/index.ts +0 -6
- package/src/ui/progress/Progress.stx +0 -131
- package/src/ui/progress/index.ts +0 -12
- package/src/ui/radio/Radio.stx +0 -77
- package/src/ui/radio/index.ts +0 -15
- package/src/ui/radio-group/README.md +0 -139
- package/src/ui/radio-group/RadioGroup.stx +0 -60
- package/src/ui/radio-group/RadioGroupDescription.stx +0 -11
- package/src/ui/radio-group/RadioGroupLabel.stx +0 -11
- package/src/ui/radio-group/RadioGroupOption.stx +0 -58
- package/src/ui/radio-group/index.ts +0 -30
- package/src/ui/select/README.md +0 -180
- package/src/ui/select/Select.stx +0 -82
- package/src/ui/select/index.ts +0 -21
- package/src/ui/sidebar/Sidebar.stx +0 -458
- package/src/ui/sidebar/SidebarFooter.stx +0 -71
- package/src/ui/sidebar/SidebarHeader.stx +0 -103
- package/src/ui/sidebar/SidebarItem.stx +0 -86
- package/src/ui/sidebar/SidebarSection.stx +0 -83
- package/src/ui/sidebar/index.ts +0 -153
- package/src/ui/sidebar/themes.ts +0 -240
- package/src/ui/skeleton/Skeleton.stx +0 -48
- package/src/ui/skeleton/index.ts +0 -11
- package/src/ui/spinner/Spinner.stx +0 -109
- package/src/ui/spinner/index.ts +0 -9
- package/src/ui/stepper/README.md +0 -36
- package/src/ui/stepper/Stepper.stx +0 -17
- package/src/ui/stepper/StepperStep.stx +0 -57
- package/src/ui/stepper/index.ts +0 -19
- package/src/ui/storage/README.md +0 -193
- package/src/ui/storage/index.ts +0 -347
- package/src/ui/switch/README.md +0 -114
- package/src/ui/switch/Switch.stx +0 -72
- package/src/ui/switch/index.ts +0 -10
- package/src/ui/table/README.md +0 -39
- package/src/ui/table/Table.stx +0 -14
- package/src/ui/table/TableBody.stx +0 -10
- package/src/ui/table/TableCell.stx +0 -10
- package/src/ui/table/TableHead.stx +0 -10
- package/src/ui/table/TableHeader.stx +0 -20
- package/src/ui/table/TableRow.stx +0 -11
- package/src/ui/table/index.ts +0 -35
- package/src/ui/tabs/TabPanel.stx +0 -25
- package/src/ui/tabs/Tabs.stx +0 -202
- package/src/ui/tabs/index.ts +0 -50
- package/src/ui/teleport/Teleport.stx +0 -65
- package/src/ui/teleport/index.ts +0 -7
- package/src/ui/textarea/Textarea.stx +0 -108
- package/src/ui/textarea/index.ts +0 -20
- package/src/ui/tooltip/Tooltip.stx +0 -71
- package/src/ui/tooltip/index.ts +0 -10
- package/src/ui/transition/README.md +0 -30
- package/src/ui/transition/Transition.stx +0 -102
- package/src/ui/transition/index.ts +0 -25
- package/src/ui/video/README.md +0 -47
- package/src/ui/video/Video.stx +0 -45
- package/src/ui/video/index.ts +0 -16
- package/src/ui/virtual-list/VirtualList.stx +0 -67
- package/src/ui/virtual-list/index.ts +0 -10
- package/src/ui/virtual-table/VirtualTable.stx +0 -103
- package/src/ui/virtual-table/index.ts +0 -21
- package/src/utils/PROP_VALIDATION.md +0 -458
- package/src/utils/THEME_SYSTEM.md +0 -702
- package/src/utils/accessibility.ts +0 -841
- package/src/utils/animation/easing.ts +0 -368
- package/src/utils/animation/helpers.ts +0 -223
- package/src/utils/animation/index.ts +0 -5
- package/src/utils/animation/keyframes.ts +0 -230
- package/src/utils/error-handling.ts +0 -475
- package/src/utils/highlighter.ts +0 -93
- package/src/utils/i18n.ts +0 -634
- package/src/utils/keep-alive.ts +0 -819
- package/src/utils/lazy-loading.ts +0 -530
- package/src/utils/prop-validation.ts +0 -478
- package/src/utils/theme.ts +0 -626
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
<script server>
|
|
2
|
-
import { resolveSidebarTheme } from './themes'
|
|
3
|
-
|
|
4
|
-
// Data: [{ id, label, collapsible?, collapsed?, items: [{ id, label, icon,
|
|
5
|
-
// iconColor, image, href, count, active?, disabled?, expanded?, children? }] }]
|
|
6
|
-
export const sections = $props.sections || []
|
|
7
|
-
|
|
8
|
-
// `theme` is the new name; `variant` is accepted for backwards compatibility.
|
|
9
|
-
export const theme = $props.theme || $props.variant || 'macos'
|
|
10
|
-
export const width = $props.width || 250
|
|
11
|
-
export const position = $props.position || 'left'
|
|
12
|
-
export const placement = $props.placement || 'fixed'
|
|
13
|
-
export const bordered = $props.bordered ?? false
|
|
14
|
-
|
|
15
|
-
// Collapsing. macOS sidebars hide entirely (toolbar button / drag); legacy
|
|
16
|
-
// themes may collapse to a compact rail instead.
|
|
17
|
-
export const collapsed = $props.collapsed ?? false
|
|
18
|
-
export const collapsible = $props.collapsible ?? true
|
|
19
|
-
export const collapseMode = $props.collapseMode || (theme === 'workspace' || theme === 'desktop' ? 'rail' : 'hidden')
|
|
20
|
-
export const minWidth = $props.minWidth || 64
|
|
21
|
-
export const persistKey = $props.persistKey || ''
|
|
22
|
-
export const shellSelector = $props.shellSelector || ''
|
|
23
|
-
export const widthVar = $props.widthVar || '--stx-sidebar-width'
|
|
24
|
-
export const collapsedClass = $props.collapsedClass || ''
|
|
25
|
-
export const className = $props.className || ''
|
|
26
|
-
|
|
27
|
-
// Unique id so the client controller can find this pane even with several
|
|
28
|
-
// sidebars on the page.
|
|
29
|
-
export const sidebarUid = $props.id || `stx-sidebar-${Math.random().toString(36).slice(2, 8)}`
|
|
30
|
-
|
|
31
|
-
const themeStyle = resolveSidebarTheme(theme)
|
|
32
|
-
export const hasShimmerRim = themeStyle.layers.shimmerRim === true
|
|
33
|
-
export const tintClasses = themeStyle.layers.tint || ''
|
|
34
|
-
export const scrollAreaClasses = themeStyle.scrollArea
|
|
35
|
-
|
|
36
|
-
const borderSide = position === 'left' ? 'border-r' : 'border-l'
|
|
37
|
-
const borderClasses = bordered ? `${borderSide} border-black/5 dark:border-white/10` : ''
|
|
38
|
-
const placementClasses = {
|
|
39
|
-
fixed: `fixed inset-y-0 z-50 ${position === 'left' ? 'left-0' : 'right-0'}`,
|
|
40
|
-
sticky: 'sticky top-0 h-screen',
|
|
41
|
-
static: 'relative h-full',
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export const paneClasses = [
|
|
45
|
-
placementClasses[placement] || placementClasses.fixed,
|
|
46
|
-
'flex flex-col overflow-hidden',
|
|
47
|
-
'transition-[width] duration-300 ease-out',
|
|
48
|
-
themeStyle.pane,
|
|
49
|
-
borderClasses,
|
|
50
|
-
className,
|
|
51
|
-
].filter(Boolean).join(' ')
|
|
52
|
-
|
|
53
|
-
// Normalized ahead of the loop so template bindings stay simple accessors.
|
|
54
|
-
export const sectionList = sections.map(section => ({
|
|
55
|
-
id: section.id,
|
|
56
|
-
label: section.label || '',
|
|
57
|
-
items: section.items || [],
|
|
58
|
-
collapsible: section.collapsible !== false,
|
|
59
|
-
collapsed: section.collapsed === true,
|
|
60
|
-
}))
|
|
61
|
-
|
|
62
|
-
// State classes the client controller swaps on rows. Kept as data attributes
|
|
63
|
-
// so all behavior lives in one place (the controller below) and themes stay
|
|
64
|
-
// pure data.
|
|
65
|
-
export const activeClasses = themeStyle.item.active
|
|
66
|
-
</script>
|
|
67
|
-
|
|
68
|
-
<script client>
|
|
69
|
-
const emit = defineEmits()
|
|
70
|
-
const sidebarUidProp = '{{ sidebarUid }}'
|
|
71
|
-
const collapsedProp = {{ collapsed }}
|
|
72
|
-
const collapsibleProp = {{ collapsible }}
|
|
73
|
-
const collapseModeProp = '{{ collapseMode }}'
|
|
74
|
-
const widthProp = {{ width }}
|
|
75
|
-
const minWidthProp = {{ minWidth }}
|
|
76
|
-
const persistKeyProp = '{{ persistKey }}'
|
|
77
|
-
const shellSelectorProp = '{{ shellSelector }}'
|
|
78
|
-
const widthVarProp = '{{ widthVar }}'
|
|
79
|
-
const collapsedClassProp = '{{ collapsedClass }}'
|
|
80
|
-
|
|
81
|
-
const collapsed = state(collapsedProp)
|
|
82
|
-
const sidebarWidth = derived(() => {
|
|
83
|
-
if (collapsed()) return collapseModeProp === 'hidden' ? '0px' : `${minWidthProp}px`
|
|
84
|
-
return `${widthProp}px`
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
let pane = null
|
|
88
|
-
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
// Selection — one row carries data-active="true"; the highlight classes come
|
|
91
|
-
// from the theme (stored on the pane as data-active-class).
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
|
|
94
|
-
function activeClassList() {
|
|
95
|
-
return (pane?.dataset.activeClass || '').split(' ').filter(Boolean)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function setActiveRow(button) {
|
|
99
|
-
const classes = activeClassList()
|
|
100
|
-
pane.querySelectorAll('[data-sidebar-item][data-active="true"]').forEach((el) => {
|
|
101
|
-
el.removeAttribute('data-active')
|
|
102
|
-
el.classList.remove(...classes)
|
|
103
|
-
})
|
|
104
|
-
button.setAttribute('data-active', 'true')
|
|
105
|
-
button.classList.add(...classes)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ---------------------------------------------------------------------------
|
|
109
|
-
// Disclosure — rows know their ancestors (data-parents="a/b"). Collapsing a
|
|
110
|
-
// row (or section) marks every descendant with data-collapsed, which the
|
|
111
|
-
// scoped CSS animates to zero height, exactly like AppKit's outline collapse.
|
|
112
|
-
// ---------------------------------------------------------------------------
|
|
113
|
-
|
|
114
|
-
function syncRowVisibility() {
|
|
115
|
-
const collapsedIds = new Set()
|
|
116
|
-
pane.querySelectorAll('[data-sidebar-row][data-expanded="false"]').forEach((row) => {
|
|
117
|
-
collapsedIds.add(row.dataset.itemId)
|
|
118
|
-
})
|
|
119
|
-
pane.querySelectorAll('[data-sidebar-section][data-expanded="false"]').forEach((section) => {
|
|
120
|
-
section.querySelectorAll('[data-sidebar-row]').forEach((row) => {
|
|
121
|
-
row.setAttribute('data-collapsed', '')
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
pane.querySelectorAll('[data-sidebar-section][data-expanded="true"] [data-sidebar-row]').forEach((row) => {
|
|
125
|
-
const parents = (row.dataset.parents || '').split('/').filter(Boolean)
|
|
126
|
-
const hidden = parents.some(id => collapsedIds.has(id))
|
|
127
|
-
if (hidden) row.setAttribute('data-collapsed', '')
|
|
128
|
-
else row.removeAttribute('data-collapsed')
|
|
129
|
-
})
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function toggleRowDisclosure(row) {
|
|
133
|
-
const expanded = row.getAttribute('data-expanded') === 'true'
|
|
134
|
-
row.setAttribute('data-expanded', String(!expanded))
|
|
135
|
-
syncRowVisibility()
|
|
136
|
-
emit('itemToggle', { id: row.dataset.itemId, expanded: !expanded })
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function toggleSection(section) {
|
|
140
|
-
const expanded = section.getAttribute('data-expanded') !== 'false'
|
|
141
|
-
section.setAttribute('data-expanded', String(!expanded))
|
|
142
|
-
syncRowVisibility()
|
|
143
|
-
emit('sectionToggle', section.dataset.sectionId)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ---------------------------------------------------------------------------
|
|
147
|
-
// Pointer input (event delegation — items and sections stay presentational)
|
|
148
|
-
// ---------------------------------------------------------------------------
|
|
149
|
-
|
|
150
|
-
function onClick(event) {
|
|
151
|
-
const chevron = event.target.closest('[data-sidebar-disclosure]')
|
|
152
|
-
if (chevron) {
|
|
153
|
-
event.preventDefault()
|
|
154
|
-
toggleRowDisclosure(chevron.closest('[data-sidebar-row]'))
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const sectionHeader = event.target.closest('[data-sidebar-section-header]')
|
|
159
|
-
if (sectionHeader) {
|
|
160
|
-
toggleSection(sectionHeader.closest('[data-sidebar-section]'))
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const item = event.target.closest('[data-sidebar-item]')
|
|
165
|
-
if (item && !item.hasAttribute('data-disabled')) {
|
|
166
|
-
setActiveRow(item)
|
|
167
|
-
const row = item.closest('[data-sidebar-row]')
|
|
168
|
-
emit('itemClick', {
|
|
169
|
-
item: { id: row.dataset.itemId, label: item.dataset.label || '', href: item.getAttribute('href') || '' },
|
|
170
|
-
event,
|
|
171
|
-
})
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ---------------------------------------------------------------------------
|
|
176
|
-
// Keyboard — matches macOS source lists: ↑/↓ move the selection over visible
|
|
177
|
-
// rows, ←/→ collapse/expand, Return opens.
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
|
|
180
|
-
function visibleItems() {
|
|
181
|
-
return [...pane.querySelectorAll('[data-sidebar-row]:not([data-collapsed]) [data-sidebar-item]:not([data-disabled])')]
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function onKeyDown(event) {
|
|
185
|
-
const items = visibleItems()
|
|
186
|
-
if (items.length === 0) return
|
|
187
|
-
const current = pane.querySelector('[data-sidebar-item][data-active="true"]')
|
|
188
|
-
const index = items.indexOf(current)
|
|
189
|
-
|
|
190
|
-
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
191
|
-
event.preventDefault()
|
|
192
|
-
const next = event.key === 'ArrowDown'
|
|
193
|
-
? items[Math.min(index + 1, items.length - 1)]
|
|
194
|
-
: items[Math.max(index - 1, 0)]
|
|
195
|
-
if (next && next !== current) {
|
|
196
|
-
setActiveRow(next)
|
|
197
|
-
next.scrollIntoView({ block: 'nearest' })
|
|
198
|
-
emit('itemClick', { item: { id: next.closest('[data-sidebar-row]').dataset.itemId, label: next.dataset.label || '', href: next.getAttribute('href') || '' }, event })
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
else if ((event.key === 'ArrowRight' || event.key === 'ArrowLeft') && current) {
|
|
202
|
-
const row = current.closest('[data-sidebar-row]')
|
|
203
|
-
if (row.querySelector('[data-sidebar-disclosure]')) {
|
|
204
|
-
const expanded = row.getAttribute('data-expanded') === 'true'
|
|
205
|
-
if (event.key === 'ArrowRight' ? !expanded : expanded) {
|
|
206
|
-
event.preventDefault()
|
|
207
|
-
toggleRowDisclosure(row)
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
else if (event.key === 'Enter' && current) {
|
|
212
|
-
current.click()
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// ---------------------------------------------------------------------------
|
|
217
|
-
// Overlay scrollbar — a floating thumb that appears while scrolling and fades
|
|
218
|
-
// away, like macOS overlay scrollbars. The native scrollbar is hidden so rows
|
|
219
|
-
// keep their exact insets.
|
|
220
|
-
// ---------------------------------------------------------------------------
|
|
221
|
-
|
|
222
|
-
function attachOverlayScrollbar(scrollArea, thumb) {
|
|
223
|
-
let fadeTimer
|
|
224
|
-
|
|
225
|
-
function update() {
|
|
226
|
-
const { scrollTop, scrollHeight, clientHeight } = scrollArea
|
|
227
|
-
if (scrollHeight <= clientHeight) {
|
|
228
|
-
thumb.style.opacity = '0'
|
|
229
|
-
return
|
|
230
|
-
}
|
|
231
|
-
const trackHeight = clientHeight - 8
|
|
232
|
-
const height = Math.max(24, (clientHeight / scrollHeight) * trackHeight)
|
|
233
|
-
const top = 4 + (scrollTop / (scrollHeight - clientHeight)) * (trackHeight - height)
|
|
234
|
-
thumb.style.height = `${height}px`
|
|
235
|
-
thumb.style.transform = `translateY(${top}px)`
|
|
236
|
-
thumb.style.opacity = '1'
|
|
237
|
-
clearTimeout(fadeTimer)
|
|
238
|
-
fadeTimer = setTimeout(() => { thumb.style.opacity = '0' }, 900)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
scrollArea.addEventListener('scroll', update, { passive: true })
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// ---------------------------------------------------------------------------
|
|
245
|
-
// Collapse (sidebar-wide) — persisted, synced to the app shell and to Craft's
|
|
246
|
-
// native web-sidebar material when present.
|
|
247
|
-
// ---------------------------------------------------------------------------
|
|
248
|
-
|
|
249
|
-
function applyCollapseEffects(nextCollapsed) {
|
|
250
|
-
if (persistKeyProp) {
|
|
251
|
-
try { window.localStorage?.setItem(persistKeyProp, String(nextCollapsed)) } catch {}
|
|
252
|
-
}
|
|
253
|
-
if (shellSelectorProp) {
|
|
254
|
-
document.querySelectorAll(shellSelectorProp).forEach((shell) => {
|
|
255
|
-
shell.style?.setProperty?.(widthVarProp, nextCollapsed && collapseModeProp === 'hidden' ? '0px' : `${widthProp}px`)
|
|
256
|
-
if (shell.dataset) shell.dataset.sidebarCollapsed = String(nextCollapsed)
|
|
257
|
-
})
|
|
258
|
-
}
|
|
259
|
-
document.documentElement.dataset.stxSidebarCollapsed = String(nextCollapsed)
|
|
260
|
-
if (collapsedClassProp) document.documentElement.classList.toggle(collapsedClassProp, nextCollapsed)
|
|
261
|
-
window.craft?.window?.setWebSidebarCollapsed?.(nextCollapsed).catch?.(() => {})
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function setCollapsed(nextCollapsed) {
|
|
265
|
-
collapsed.set(nextCollapsed)
|
|
266
|
-
applyCollapseEffects(nextCollapsed)
|
|
267
|
-
emit('collapse', nextCollapsed)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function toggleCollapse() {
|
|
271
|
-
if (collapsibleProp) setCollapsed(!collapsed())
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
onMount(() => {
|
|
275
|
-
pane = document.getElementById(sidebarUidProp)
|
|
276
|
-
if (!pane) return
|
|
277
|
-
|
|
278
|
-
if (persistKeyProp) {
|
|
279
|
-
try {
|
|
280
|
-
const saved = window.localStorage?.getItem(persistKeyProp)
|
|
281
|
-
if (saved === 'true' || saved === 'false') collapsed.set(saved === 'true')
|
|
282
|
-
} catch {}
|
|
283
|
-
}
|
|
284
|
-
applyCollapseEffects(collapsed())
|
|
285
|
-
|
|
286
|
-
pane.addEventListener('click', onClick)
|
|
287
|
-
pane.addEventListener('keydown', onKeyDown)
|
|
288
|
-
|
|
289
|
-
const scrollArea = pane.querySelector('[data-sidebar-scroll]')
|
|
290
|
-
const thumb = pane.querySelector('[data-sidebar-scroll-thumb]')
|
|
291
|
-
if (scrollArea && thumb) attachOverlayScrollbar(scrollArea, thumb)
|
|
292
|
-
|
|
293
|
-
// Ensure the initial active row carries the theme's highlight classes.
|
|
294
|
-
const active = pane.querySelector('[data-sidebar-item][data-active="true"]')
|
|
295
|
-
if (active) active.classList.add(...activeClassList())
|
|
296
|
-
syncRowVisibility()
|
|
297
|
-
})
|
|
298
|
-
|
|
299
|
-
defineExpose({ collapsed, toggle: toggleCollapse })
|
|
300
|
-
</script>
|
|
301
|
-
|
|
302
|
-
<aside
|
|
303
|
-
id="{{ sidebarUid }}"
|
|
304
|
-
data-stx-sidebar
|
|
305
|
-
data-sidebar-theme="{{ theme }}"
|
|
306
|
-
data-active-class="{{ activeClasses }}"
|
|
307
|
-
class="{{ paneClasses }}"
|
|
308
|
-
:style="`width: ${sidebarWidth()}`"
|
|
309
|
-
:aria-hidden="String(collapsed() && collapseModeProp === 'hidden')"
|
|
310
|
-
role="navigation"
|
|
311
|
-
aria-label="Sidebar"
|
|
312
|
-
tabindex="0"
|
|
313
|
-
>
|
|
314
|
-
@if(tintClasses)
|
|
315
|
-
<div class="absolute inset-0 pointer-events-none {{ tintClasses }}"></div>
|
|
316
|
-
@endif
|
|
317
|
-
@if(hasShimmerRim)
|
|
318
|
-
<div class="stx-sidebar-rim" aria-hidden="true"></div>
|
|
319
|
-
@endif
|
|
320
|
-
|
|
321
|
-
@if($slots.header)
|
|
322
|
-
<div class="relative z-10 flex-shrink-0">
|
|
323
|
-
<slot name="header" />
|
|
324
|
-
</div>
|
|
325
|
-
@endif
|
|
326
|
-
|
|
327
|
-
<div class="relative z-10 min-h-0 flex-1">
|
|
328
|
-
<div data-sidebar-scroll class="{{ scrollAreaClasses }} h-full stx-sidebar-scroll">
|
|
329
|
-
@if(sectionList.length > 0)
|
|
330
|
-
<nav class="flex flex-col">
|
|
331
|
-
@foreach(sectionList as section)
|
|
332
|
-
<SidebarSection
|
|
333
|
-
:id="section.id"
|
|
334
|
-
:label="section.label"
|
|
335
|
-
:items="section.items"
|
|
336
|
-
:collapsible="section.collapsible"
|
|
337
|
-
:collapsed="section.collapsed"
|
|
338
|
-
:theme="theme"
|
|
339
|
-
/>
|
|
340
|
-
@endforeach
|
|
341
|
-
</nav>
|
|
342
|
-
@else
|
|
343
|
-
<slot />
|
|
344
|
-
@endif
|
|
345
|
-
</div>
|
|
346
|
-
<div data-sidebar-scroll-thumb class="stx-sidebar-scroll-thumb" aria-hidden="true"></div>
|
|
347
|
-
</div>
|
|
348
|
-
|
|
349
|
-
@if($slots.footer)
|
|
350
|
-
<div class="relative z-10 flex-shrink-0">
|
|
351
|
-
<slot name="footer" />
|
|
352
|
-
</div>
|
|
353
|
-
@endif
|
|
354
|
-
</aside>
|
|
355
|
-
|
|
356
|
-
<style scoped>
|
|
357
|
-
[data-stx-sidebar] {
|
|
358
|
-
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', sans-serif;
|
|
359
|
-
-webkit-font-smoothing: antialiased;
|
|
360
|
-
outline: none;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/* Liquid Glass edge highlight: a hairline rim that catches light along the
|
|
364
|
-
pane's edges, with a slow sheen drifting across it. */
|
|
365
|
-
.stx-sidebar-rim {
|
|
366
|
-
position: absolute;
|
|
367
|
-
inset: 0;
|
|
368
|
-
border-radius: inherit;
|
|
369
|
-
pointer-events: none;
|
|
370
|
-
z-index: 5;
|
|
371
|
-
box-shadow:
|
|
372
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.50),
|
|
373
|
-
inset 1px 0 0 rgba(255, 255, 255, 0.26),
|
|
374
|
-
inset -1px 0 0 rgba(255, 255, 255, 0.10),
|
|
375
|
-
inset 0 -1px 0 rgba(255, 255, 255, 0.14);
|
|
376
|
-
}
|
|
377
|
-
.stx-sidebar-rim::after {
|
|
378
|
-
content: '';
|
|
379
|
-
position: absolute;
|
|
380
|
-
inset: 0;
|
|
381
|
-
border-radius: inherit;
|
|
382
|
-
padding: 1.5px;
|
|
383
|
-
background: linear-gradient(
|
|
384
|
-
115deg,
|
|
385
|
-
rgba(255, 255, 255, 0.85) 0%,
|
|
386
|
-
rgba(255, 255, 255, 0.00) 28%,
|
|
387
|
-
rgba(255, 255, 255, 0.18) 52%,
|
|
388
|
-
rgba(255, 255, 255, 0.00) 74%,
|
|
389
|
-
rgba(255, 255, 255, 0.65) 100%
|
|
390
|
-
);
|
|
391
|
-
background-size: 250% 250%;
|
|
392
|
-
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
|
393
|
-
-webkit-mask-composite: xor;
|
|
394
|
-
mask-composite: exclude;
|
|
395
|
-
animation: stx-sidebar-shimmer 10s ease-in-out infinite alternate;
|
|
396
|
-
}
|
|
397
|
-
@keyframes stx-sidebar-shimmer {
|
|
398
|
-
from { background-position: 0% 0%; }
|
|
399
|
-
to { background-position: 100% 100%; }
|
|
400
|
-
}
|
|
401
|
-
@media (prefers-color-scheme: dark) {
|
|
402
|
-
.stx-sidebar-rim {
|
|
403
|
-
box-shadow:
|
|
404
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.18),
|
|
405
|
-
inset 1px 0 0 rgba(255, 255, 255, 0.10),
|
|
406
|
-
inset -1px 0 0 rgba(255, 255, 255, 0.05),
|
|
407
|
-
inset 0 -1px 0 rgba(255, 255, 255, 0.06);
|
|
408
|
-
}
|
|
409
|
-
.stx-sidebar-rim::after { opacity: 0.35; }
|
|
410
|
-
}
|
|
411
|
-
@media (prefers-reduced-motion: reduce) {
|
|
412
|
-
.stx-sidebar-rim::after { animation: none; }
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/* Rows collapse like AppKit outlines: fixed row height animates to zero. */
|
|
416
|
-
[data-sidebar-row] {
|
|
417
|
-
height: 32px; /* 30px row + 2px gap */
|
|
418
|
-
padding-bottom: 2px;
|
|
419
|
-
transition: height 0.22s ease, opacity 0.16s ease;
|
|
420
|
-
}
|
|
421
|
-
[data-sidebar-row][data-collapsed] {
|
|
422
|
-
height: 0;
|
|
423
|
-
opacity: 0;
|
|
424
|
-
overflow: hidden;
|
|
425
|
-
pointer-events: none;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/* Disclosure chevron rotates from ▸ to ▾ */
|
|
429
|
-
[data-sidebar-row][data-expanded="true"] [data-sidebar-disclosure] > * {
|
|
430
|
-
transform: rotate(90deg);
|
|
431
|
-
}
|
|
432
|
-
[data-sidebar-section][data-expanded="false"] [data-sidebar-section-chevron] {
|
|
433
|
-
transform: rotate(-90deg);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/* Native scrollbar hidden; the floating thumb below takes its place. */
|
|
437
|
-
.stx-sidebar-scroll {
|
|
438
|
-
scrollbar-width: none;
|
|
439
|
-
}
|
|
440
|
-
.stx-sidebar-scroll::-webkit-scrollbar {
|
|
441
|
-
display: none;
|
|
442
|
-
}
|
|
443
|
-
.stx-sidebar-scroll-thumb {
|
|
444
|
-
position: absolute;
|
|
445
|
-
top: 0;
|
|
446
|
-
right: 3px;
|
|
447
|
-
width: 7px;
|
|
448
|
-
border-radius: 4px;
|
|
449
|
-
background: rgba(0, 0, 0, 0.35);
|
|
450
|
-
opacity: 0;
|
|
451
|
-
transition: opacity 0.3s ease;
|
|
452
|
-
pointer-events: none;
|
|
453
|
-
z-index: 20;
|
|
454
|
-
}
|
|
455
|
-
@media (prefers-color-scheme: dark) {
|
|
456
|
-
.stx-sidebar-scroll-thumb { background: rgba(255, 255, 255, 0.35); }
|
|
457
|
-
}
|
|
458
|
-
</style>
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
<script server>
|
|
2
|
-
// The bottom strip of a macOS sidebar — e.g. the account row in Music
|
|
3
|
-
// ("Chris Breuer" with an avatar) or custom actions.
|
|
4
|
-
export const theme = $props.theme || $props.variant || 'macos'
|
|
5
|
-
export const avatar = $props.avatar || ''
|
|
6
|
-
export const name = $props.name || ''
|
|
7
|
-
export const detail = $props.detail || ''
|
|
8
|
-
export const actions = $props.actions || [] // [{ id, icon, label }]
|
|
9
|
-
|
|
10
|
-
export const isMacos = theme === 'macos' || theme === 'tahoe' || theme === 'macos-tahoe' || theme === 'macos-latest'
|
|
11
|
-
|
|
12
|
-
export const profileClasses = [
|
|
13
|
-
'flex w-full items-center gap-[8px]',
|
|
14
|
-
'h-[44px] rounded-[10px] px-[6px]',
|
|
15
|
-
'text-[13px] font-medium text-black dark:text-white',
|
|
16
|
-
'transition-colors duration-150',
|
|
17
|
-
'hover:bg-black/4 active:bg-black/8',
|
|
18
|
-
'dark:hover:bg-white/6 dark:active:bg-white/12',
|
|
19
|
-
].join(' ')
|
|
20
|
-
|
|
21
|
-
export const actionClasses = [
|
|
22
|
-
'grid h-[28px] w-[28px] place-items-center rounded-[7px]',
|
|
23
|
-
'text-[#3c3c43]/70 dark:text-[#ebebf5]/70',
|
|
24
|
-
'transition-colors duration-150',
|
|
25
|
-
'hover:bg-black/5 dark:hover:bg-white/10',
|
|
26
|
-
].join(' ')
|
|
27
|
-
</script>
|
|
28
|
-
|
|
29
|
-
<script client>
|
|
30
|
-
const emit = defineEmits()
|
|
31
|
-
|
|
32
|
-
function onProfileClick() {
|
|
33
|
-
emit('profileClick')
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function onAction(event) {
|
|
37
|
-
emit('action', event.target.closest('[data-sidebar-action]')?.dataset.sidebarAction)
|
|
38
|
-
}
|
|
39
|
-
</script>
|
|
40
|
-
|
|
41
|
-
@if(isMacos)
|
|
42
|
-
<div class="shrink-0 px-[10px] pb-[8px] pt-[4px]">
|
|
43
|
-
@if(name)
|
|
44
|
-
<button type="button" class="{{ profileClasses }}" @click="onProfileClick()">
|
|
45
|
-
@if(avatar)
|
|
46
|
-
<img src="{{ avatar }}" alt="" class="h-[30px] w-[30px] rounded-full object-cover shadow-sm" />
|
|
47
|
-
@else
|
|
48
|
-
<span class="grid h-[30px] w-[30px] place-items-center rounded-full bg-black/10 text-[13px] font-semibold dark:bg-white/15">{{ name.charAt(0) }}</span>
|
|
49
|
-
@endif
|
|
50
|
-
<span class="min-w-0 flex-1 truncate text-left">{{ name }}</span>
|
|
51
|
-
@if(detail)
|
|
52
|
-
<span class="shrink-0 text-[12px] text-[#3c3c43]/60 dark:text-[#ebebf5]/60">{{ detail }}</span>
|
|
53
|
-
@endif
|
|
54
|
-
</button>
|
|
55
|
-
@endif
|
|
56
|
-
@if(actions.length > 0)
|
|
57
|
-
<div class="flex items-center gap-[4px] pt-[4px]">
|
|
58
|
-
@foreach(actions as action)
|
|
59
|
-
<button type="button" class="{{ actionClasses }}" aria-label="{{ action.label }}" data-sidebar-action="{{ action.id }}" @click="onAction($event)">
|
|
60
|
-
<span class="{{ action.icon }} h-[16px] w-[16px]" aria-hidden="true"></span>
|
|
61
|
-
</button>
|
|
62
|
-
@endforeach
|
|
63
|
-
</div>
|
|
64
|
-
@endif
|
|
65
|
-
<slot />
|
|
66
|
-
</div>
|
|
67
|
-
@else
|
|
68
|
-
<div class="shrink-0 border-t border-black/5 p-3 dark:border-white/10">
|
|
69
|
-
<slot />
|
|
70
|
-
</div>
|
|
71
|
-
@endif
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
<script server>
|
|
2
|
-
// The window-chrome strip at the top of a macOS sidebar: traffic lights on
|
|
3
|
-
// the left, floating "Liquid Glass" toolbar buttons on the right, and the
|
|
4
|
-
// whole strip acts as a window drag region inside a Craft window.
|
|
5
|
-
export const theme = $props.theme || $props.variant || 'macos'
|
|
6
|
-
export const showWindowControls = $props.showWindowControls ?? true
|
|
7
|
-
export const actions = $props.actions || [] // [{ id, icon, label }]
|
|
8
|
-
export const title = $props.title || ''
|
|
9
|
-
export const subtitle = $props.subtitle || ''
|
|
10
|
-
export const logo = $props.logo || ''
|
|
11
|
-
export const showSearch = $props.showSearch ?? false
|
|
12
|
-
export const searchPlaceholder = $props.searchPlaceholder || 'Search'
|
|
13
|
-
|
|
14
|
-
export const isMacos = theme === 'macos' || theme === 'tahoe' || theme === 'macos-tahoe' || theme === 'macos-latest'
|
|
15
|
-
|
|
16
|
-
// Traffic lights: 13px circles, 7px apart, inset ~21px — measured on
|
|
17
|
-
// macOS Tahoe. Colors are Apple's exact stoplight values.
|
|
18
|
-
export const trafficLightClasses = 'h-[13px] w-[13px] rounded-full shadow-[inset_0_0_1px_rgba(0,0,0,0.2)]'
|
|
19
|
-
|
|
20
|
-
// Floating toolbar button: a 34px glass circle that brightens on hover.
|
|
21
|
-
export const toolbarButtonClasses = [
|
|
22
|
-
'grid h-[34px] w-[34px] place-items-center rounded-full',
|
|
23
|
-
'bg-white/55 dark:bg-white/10',
|
|
24
|
-
'backdrop-blur-xl',
|
|
25
|
-
'shadow-[0_1px_4px_rgba(0,0,0,0.12),inset_0_0.5px_0_rgba(255,255,255,0.6)]',
|
|
26
|
-
'text-[#3c3c43]/75 dark:text-[#ebebf5]/75',
|
|
27
|
-
'transition-colors duration-150',
|
|
28
|
-
'hover:bg-white/80 dark:hover:bg-white/20',
|
|
29
|
-
'active:bg-white/95 dark:active:bg-white/25',
|
|
30
|
-
].join(' ')
|
|
31
|
-
</script>
|
|
32
|
-
|
|
33
|
-
<script client>
|
|
34
|
-
const emit = defineEmits()
|
|
35
|
-
|
|
36
|
-
function onAction(event) {
|
|
37
|
-
emit('action', event.currentTarget?.dataset?.sidebarAction || event.target.closest('[data-sidebar-action]')?.dataset.sidebarAction)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function onSearchInput(event) {
|
|
41
|
-
emit('search', event.target.value)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function onWindowControl(action) {
|
|
45
|
-
const api = window.craft?.window
|
|
46
|
-
if (action === 'close') api?.close?.()
|
|
47
|
-
if (action === 'minimize') api?.minimize?.()
|
|
48
|
-
if (action === 'zoom') (api?.maximize || api?.zoom)?.call(api)
|
|
49
|
-
emit('windowControl', action)
|
|
50
|
-
}
|
|
51
|
-
</script>
|
|
52
|
-
|
|
53
|
-
@if(isMacos)
|
|
54
|
-
<div class="flex h-[52px] shrink-0 items-center justify-between pl-[21px] pr-[10px] [-webkit-app-region:drag]">
|
|
55
|
-
@if(showWindowControls)
|
|
56
|
-
<div class="group/lights flex items-center gap-[7px] [-webkit-app-region:no-drag]">
|
|
57
|
-
<button type="button" class="{{ trafficLightClasses }} bg-[#ff5f57]" aria-label="Close window" @click="onWindowControl('close')"></button>
|
|
58
|
-
<button type="button" class="{{ trafficLightClasses }} bg-[#febc2e]" aria-label="Minimize window" @click="onWindowControl('minimize')"></button>
|
|
59
|
-
<button type="button" class="{{ trafficLightClasses }} bg-[#28c840]" aria-label="Zoom window" @click="onWindowControl('zoom')"></button>
|
|
60
|
-
</div>
|
|
61
|
-
@else
|
|
62
|
-
<span></span>
|
|
63
|
-
@endif
|
|
64
|
-
|
|
65
|
-
<div class="flex items-center gap-[8px] [-webkit-app-region:no-drag]">
|
|
66
|
-
@foreach(actions as action)
|
|
67
|
-
<button type="button" class="{{ toolbarButtonClasses }}" aria-label="{{ action.label }}" data-sidebar-action="{{ action.id }}" @click="onAction($event)">
|
|
68
|
-
<span class="{{ action.icon }} h-[16px] w-[16px]" aria-hidden="true"></span>
|
|
69
|
-
</button>
|
|
70
|
-
@endforeach
|
|
71
|
-
<slot name="actions" />
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
@if(showSearch)
|
|
76
|
-
<div class="shrink-0 px-[10px] pb-[6px]">
|
|
77
|
-
<div class="relative">
|
|
78
|
-
<span class="i-f7-search absolute left-[8px] top-1/2 h-[13px] w-[13px] -translate-y-1/2 text-[#3c3c43]/50 dark:text-[#ebebf5]/50" aria-hidden="true"></span>
|
|
79
|
-
<input
|
|
80
|
-
type="text"
|
|
81
|
-
placeholder="{{ searchPlaceholder }}"
|
|
82
|
-
class="h-[28px] w-full rounded-[8px] border-0 bg-black/5 pl-[26px] pr-[8px] text-[13px] text-black placeholder:text-[#3c3c43]/50 focus:outline-none focus:ring-2 focus:ring-[#0088ff]/60 dark:bg-white/10 dark:text-white dark:placeholder:text-[#ebebf5]/50"
|
|
83
|
-
@input="onSearchInput($event)"
|
|
84
|
-
/>
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
@endif
|
|
88
|
-
@else
|
|
89
|
-
<div class="flex h-13 items-center gap-3 border-b border-black/5 px-4 dark:border-white/10">
|
|
90
|
-
@if(logo)
|
|
91
|
-
<img src="{{ logo }}" alt="{{ title }}" class="h-8 w-8 rounded-lg object-cover" />
|
|
92
|
-
@endif
|
|
93
|
-
<div class="min-w-0">
|
|
94
|
-
@if(title)
|
|
95
|
-
<div class="truncate text-sm font-semibold text-black/90 dark:text-white">{{ title }}</div>
|
|
96
|
-
@endif
|
|
97
|
-
@if(subtitle)
|
|
98
|
-
<div class="truncate text-xs text-black/50 dark:text-white/50">{{ subtitle }}</div>
|
|
99
|
-
@endif
|
|
100
|
-
</div>
|
|
101
|
-
<slot name="actions" />
|
|
102
|
-
</div>
|
|
103
|
-
@endif
|