@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.
Files changed (188) hide show
  1. package/dist/{Sidebar-yb115a2w.stx → Sidebar-4hn7vw4n.stx} +18 -0
  2. package/dist/index.js +5 -5
  3. package/package.json +5 -6
  4. package/src/components/CodeBlock.stx +0 -54
  5. package/src/components/Footer.stx +0 -66
  6. package/src/components/Hero.stx +0 -25
  7. package/src/components/Installation.stx +0 -37
  8. package/src/components/index.ts +0 -4
  9. package/src/composables/index.ts +0 -4
  10. package/src/composables/useCopyCode.ts +0 -73
  11. package/src/composables/useDarkMode.ts +0 -100
  12. package/src/composables/useSEO.ts +0 -230
  13. package/src/composables/utilities/index.ts +0 -7
  14. package/src/composables/utilities/useClickOutside.ts +0 -72
  15. package/src/composables/utilities/useFocusTrap.ts +0 -159
  16. package/src/composables/utilities/useKeyboard.ts +0 -124
  17. package/src/composables/utilities/useLocalStorage.ts +0 -132
  18. package/src/composables/utilities/useMediaQuery.ts +0 -118
  19. package/src/index.ts +0 -99
  20. package/src/stx.d.ts +0 -4
  21. package/src/ui/accordion/Accordion.stx +0 -146
  22. package/src/ui/accordion/AccordionItem.stx +0 -37
  23. package/src/ui/accordion/index.ts +0 -47
  24. package/src/ui/audio/Audio.stx +0 -50
  25. package/src/ui/audio/README.md +0 -41
  26. package/src/ui/audio/index.ts +0 -13
  27. package/src/ui/auth/Login.stx +0 -161
  28. package/src/ui/auth/README.md +0 -142
  29. package/src/ui/auth/Signup.stx +0 -159
  30. package/src/ui/auth/TwoFactorChallenge.stx +0 -111
  31. package/src/ui/auth/index.ts +0 -32
  32. package/src/ui/avatar/Avatar.stx +0 -77
  33. package/src/ui/avatar/index.ts +0 -12
  34. package/src/ui/badge/Badge.stx +0 -62
  35. package/src/ui/badge/index.ts +0 -10
  36. package/src/ui/breadcrumb/Breadcrumb.stx +0 -79
  37. package/src/ui/breadcrumb/index.ts +0 -15
  38. package/src/ui/button/Button.stx +0 -74
  39. package/src/ui/button/README.md +0 -80
  40. package/src/ui/button/index.ts +0 -85
  41. package/src/ui/calendar/Calendar.stx +0 -189
  42. package/src/ui/calendar/README.md +0 -254
  43. package/src/ui/calendar/index.ts +0 -14
  44. package/src/ui/card/Card.stx +0 -68
  45. package/src/ui/card/index.ts +0 -13
  46. package/src/ui/checkbox/Checkbox.stx +0 -86
  47. package/src/ui/checkbox/index.ts +0 -16
  48. package/src/ui/combobox/Combobox.stx +0 -96
  49. package/src/ui/combobox/ComboboxButton.stx +0 -25
  50. package/src/ui/combobox/ComboboxInput.stx +0 -32
  51. package/src/ui/combobox/ComboboxOption.stx +0 -49
  52. package/src/ui/combobox/ComboboxOptions.stx +0 -16
  53. package/src/ui/combobox/README.md +0 -150
  54. package/src/ui/combobox/index.ts +0 -40
  55. package/src/ui/command-palette/CommandPalette.stx +0 -81
  56. package/src/ui/command-palette/CommandPaletteItem.stx +0 -27
  57. package/src/ui/command-palette/README.md +0 -42
  58. package/src/ui/command-palette/index.ts +0 -15
  59. package/src/ui/dialog/Dialog.stx +0 -41
  60. package/src/ui/dialog/DialogBackdrop.stx +0 -9
  61. package/src/ui/dialog/DialogDescription.stx +0 -11
  62. package/src/ui/dialog/DialogPanel.stx +0 -16
  63. package/src/ui/dialog/DialogTitle.stx +0 -11
  64. package/src/ui/dialog/README.md +0 -121
  65. package/src/ui/dialog/index.ts +0 -32
  66. package/src/ui/drawer/Drawer.stx +0 -120
  67. package/src/ui/drawer/README.md +0 -90
  68. package/src/ui/drawer/index.ts +0 -9
  69. package/src/ui/dropdown/Dropdown.stx +0 -82
  70. package/src/ui/dropdown/DropdownButton.stx +0 -29
  71. package/src/ui/dropdown/DropdownItem.stx +0 -35
  72. package/src/ui/dropdown/DropdownItems.stx +0 -18
  73. package/src/ui/dropdown/README.md +0 -114
  74. package/src/ui/dropdown/index.ts +0 -29
  75. package/src/ui/form/Form.stx +0 -95
  76. package/src/ui/form/index.ts +0 -67
  77. package/src/ui/heatmap/Heatmap.stx +0 -294
  78. package/src/ui/image/Image.stx +0 -165
  79. package/src/ui/image/README.md +0 -164
  80. package/src/ui/image/index.ts +0 -22
  81. package/src/ui/input/EmailInput.stx +0 -30
  82. package/src/ui/input/NumberInput.stx +0 -126
  83. package/src/ui/input/PasswordInput.stx +0 -143
  84. package/src/ui/input/SearchInput.stx +0 -44
  85. package/src/ui/input/TextInput.stx +0 -132
  86. package/src/ui/input/index.ts +0 -180
  87. package/src/ui/listbox/Listbox.stx +0 -101
  88. package/src/ui/listbox/ListboxButton.stx +0 -28
  89. package/src/ui/listbox/ListboxLabel.stx +0 -11
  90. package/src/ui/listbox/ListboxOption.stx +0 -49
  91. package/src/ui/listbox/ListboxOptions.stx +0 -16
  92. package/src/ui/listbox/README.md +0 -135
  93. package/src/ui/listbox/index.ts +0 -39
  94. package/src/ui/navigator/Navigator.stx +0 -113
  95. package/src/ui/navigator/README.md +0 -352
  96. package/src/ui/navigator/index.ts +0 -20
  97. package/src/ui/notification/Notification.stx +0 -117
  98. package/src/ui/notification/README.md +0 -114
  99. package/src/ui/notification/index.ts +0 -12
  100. package/src/ui/pagination/Pagination.stx +0 -150
  101. package/src/ui/pagination/index.ts +0 -11
  102. package/src/ui/payment/Checkout.stx +0 -31
  103. package/src/ui/payment/DefaultPaymentMethod.stx +0 -62
  104. package/src/ui/payment/PaymentMethods.stx +0 -104
  105. package/src/ui/payment/README.md +0 -345
  106. package/src/ui/payment/SubscriptionCheckout.stx +0 -323
  107. package/src/ui/payment/index.ts +0 -59
  108. package/src/ui/popover/Popover.stx +0 -60
  109. package/src/ui/popover/PopoverButton.stx +0 -29
  110. package/src/ui/popover/PopoverPanel.stx +0 -23
  111. package/src/ui/popover/README.md +0 -91
  112. package/src/ui/popover/index.ts +0 -22
  113. package/src/ui/portal/Portal.stx +0 -57
  114. package/src/ui/portal/index.ts +0 -6
  115. package/src/ui/progress/Progress.stx +0 -131
  116. package/src/ui/progress/index.ts +0 -12
  117. package/src/ui/radio/Radio.stx +0 -77
  118. package/src/ui/radio/index.ts +0 -15
  119. package/src/ui/radio-group/README.md +0 -139
  120. package/src/ui/radio-group/RadioGroup.stx +0 -60
  121. package/src/ui/radio-group/RadioGroupDescription.stx +0 -11
  122. package/src/ui/radio-group/RadioGroupLabel.stx +0 -11
  123. package/src/ui/radio-group/RadioGroupOption.stx +0 -58
  124. package/src/ui/radio-group/index.ts +0 -30
  125. package/src/ui/select/README.md +0 -180
  126. package/src/ui/select/Select.stx +0 -82
  127. package/src/ui/select/index.ts +0 -21
  128. package/src/ui/sidebar/Sidebar.stx +0 -458
  129. package/src/ui/sidebar/SidebarFooter.stx +0 -71
  130. package/src/ui/sidebar/SidebarHeader.stx +0 -103
  131. package/src/ui/sidebar/SidebarItem.stx +0 -86
  132. package/src/ui/sidebar/SidebarSection.stx +0 -83
  133. package/src/ui/sidebar/index.ts +0 -153
  134. package/src/ui/sidebar/themes.ts +0 -240
  135. package/src/ui/skeleton/Skeleton.stx +0 -48
  136. package/src/ui/skeleton/index.ts +0 -11
  137. package/src/ui/spinner/Spinner.stx +0 -109
  138. package/src/ui/spinner/index.ts +0 -9
  139. package/src/ui/stepper/README.md +0 -36
  140. package/src/ui/stepper/Stepper.stx +0 -17
  141. package/src/ui/stepper/StepperStep.stx +0 -57
  142. package/src/ui/stepper/index.ts +0 -19
  143. package/src/ui/storage/README.md +0 -193
  144. package/src/ui/storage/index.ts +0 -347
  145. package/src/ui/switch/README.md +0 -114
  146. package/src/ui/switch/Switch.stx +0 -72
  147. package/src/ui/switch/index.ts +0 -10
  148. package/src/ui/table/README.md +0 -39
  149. package/src/ui/table/Table.stx +0 -14
  150. package/src/ui/table/TableBody.stx +0 -10
  151. package/src/ui/table/TableCell.stx +0 -10
  152. package/src/ui/table/TableHead.stx +0 -10
  153. package/src/ui/table/TableHeader.stx +0 -20
  154. package/src/ui/table/TableRow.stx +0 -11
  155. package/src/ui/table/index.ts +0 -35
  156. package/src/ui/tabs/TabPanel.stx +0 -25
  157. package/src/ui/tabs/Tabs.stx +0 -202
  158. package/src/ui/tabs/index.ts +0 -50
  159. package/src/ui/teleport/Teleport.stx +0 -65
  160. package/src/ui/teleport/index.ts +0 -7
  161. package/src/ui/textarea/Textarea.stx +0 -108
  162. package/src/ui/textarea/index.ts +0 -20
  163. package/src/ui/tooltip/Tooltip.stx +0 -71
  164. package/src/ui/tooltip/index.ts +0 -10
  165. package/src/ui/transition/README.md +0 -30
  166. package/src/ui/transition/Transition.stx +0 -102
  167. package/src/ui/transition/index.ts +0 -25
  168. package/src/ui/video/README.md +0 -47
  169. package/src/ui/video/Video.stx +0 -45
  170. package/src/ui/video/index.ts +0 -16
  171. package/src/ui/virtual-list/VirtualList.stx +0 -67
  172. package/src/ui/virtual-list/index.ts +0 -10
  173. package/src/ui/virtual-table/VirtualTable.stx +0 -103
  174. package/src/ui/virtual-table/index.ts +0 -21
  175. package/src/utils/PROP_VALIDATION.md +0 -458
  176. package/src/utils/THEME_SYSTEM.md +0 -702
  177. package/src/utils/accessibility.ts +0 -841
  178. package/src/utils/animation/easing.ts +0 -368
  179. package/src/utils/animation/helpers.ts +0 -223
  180. package/src/utils/animation/index.ts +0 -5
  181. package/src/utils/animation/keyframes.ts +0 -230
  182. package/src/utils/error-handling.ts +0 -475
  183. package/src/utils/highlighter.ts +0 -93
  184. package/src/utils/i18n.ts +0 -634
  185. package/src/utils/keep-alive.ts +0 -819
  186. package/src/utils/lazy-loading.ts +0 -530
  187. package/src/utils/prop-validation.ts +0 -478
  188. 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