@stacksjs/components 0.2.91 → 0.2.92

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.
@@ -1,114 +1,83 @@
1
1
  <script server>
2
+ import { resolveSidebarTheme } from './themes'
3
+
2
4
  export const id = $props.id || ''
3
5
  export const label = $props.label || ''
4
- export const icon = $props.icon || ''
5
6
  export const items = $props.items || []
6
- export const expanded = $props.expanded ?? true
7
7
  export const collapsible = $props.collapsible ?? true
8
- export const showLabel = $props.showLabel ?? true
9
- export const variant = $props.variant || 'tahoe'
10
-
11
- const headerVariants = {
12
- workspace: {
13
- base: 'w-full flex items-center gap-2.5 px-4 pb-2 pt-5 rounded-[14px] text-left transition-colors duration-150 text-[13px] font-semibold uppercase tracking-[0.12em] text-zinc-400 dark:text-zinc-500',
14
- hover: 'hover:text-zinc-600 dark:hover:text-zinc-300 cursor-pointer',
15
- },
16
- desktop: {
17
- base: 'w-full flex items-center gap-2.5 px-3 pb-1.5 pt-3 rounded-[14px] text-left transition-colors duration-150 text-[12px] font-semibold uppercase tracking-[0.10em] text-zinc-500/80 dark:text-zinc-400/80',
18
- hover: 'hover:bg-white/25 dark:hover:bg-white/10 cursor-pointer',
19
- },
20
- default: {
21
- base: 'w-full flex items-center gap-2.5 px-2.5 py-1.5 rounded-md text-left transition-all duration-150 text-black/60 dark:text-white/60',
22
- hover: 'hover:bg-black/5 dark:hover:bg-white/10 cursor-pointer',
23
- },
24
- }
25
-
26
- const headerStyle = headerVariants[variant] || headerVariants.default
27
- const headerClasses = [headerStyle.base, collapsible ? headerStyle.hover : ''].filter(Boolean).join(' ')
28
- export const headerClassesValue = headerClasses
29
- export const sectionClasses = variant === 'workspace' ? 'py-1.5' : variant === 'desktop' ? 'py-1' : 'py-1'
30
- export const groupClasses = variant === 'workspace' || variant === 'desktop' ? 'space-y-1 overflow-hidden' : 'mt-1 space-y-0.5 overflow-hidden'
31
- export const labelClasses = variant === 'workspace' || variant === 'desktop'
32
- ? 'flex-1 truncate'
33
- : 'flex-1 text-[13px] font-semibold tracking-tight truncate'
34
- </script>
8
+ export const collapsed = $props.collapsed ?? false
9
+ export const theme = $props.theme || $props.variant || 'macos'
35
10
 
36
- <script client>
37
- const emit = defineEmits()
38
- const sectionId = {{ id }}
39
- const collapsibleProp = {{ collapsible }}
40
- // Reactive prop binding — parent can drive `expanded` via signals. See #1704.
41
- const isExpanded = useReactiveProp('expanded', {{ expanded }})
11
+ const themeStyle = resolveSidebarTheme(theme)
12
+ export const headerClasses = themeStyle.sectionHeader + (collapsible ? ' cursor-default' : '')
13
+ export const chevronClasses = themeStyle.sectionChevron
14
+ export const groupClasses = themeStyle.sectionGroup
42
15
 
43
- function onHeaderClick() {
44
- if (!collapsibleProp) return
45
- isExpanded.set(!isExpanded())
46
- emit('toggle', sectionId)
16
+ // Components can't render themselves recursively, so the item tree is
17
+ // flattened into ready-to-render rows here. Each row remembers its depth
18
+ // and ancestor ids — the Sidebar controller uses those to collapse whole
19
+ // subtrees.
20
+ function flatten(list, depth = 0, parents = []) {
21
+ return list.flatMap((item) => {
22
+ const children = item.children || []
23
+ const row = {
24
+ id: item.id,
25
+ label: item.label,
26
+ icon: item.icon || '',
27
+ iconColor: item.iconColor || '',
28
+ image: item.image || '',
29
+ href: item.href || '',
30
+ count: item.count ?? item.badge ?? '',
31
+ active: item.active === true,
32
+ disabled: item.disabled === true,
33
+ expandable: children.length > 0 || item.expandable === true,
34
+ expanded: item.expanded !== false,
35
+ depth,
36
+ parents: parents.join('/'),
37
+ }
38
+ return [row, ...flatten(children, depth + 1, [...parents, item.id])]
39
+ })
47
40
  }
48
41
 
49
- function onItemClick(event) {
50
- emit('itemClick', event.detail)
51
- }
52
-
53
- const arrowClass = derived(() => `i-hugeicons-arrow-right-01 w-3 h-3 transition-transform duration-200 opacity-50 ${isExpanded() ? 'rotate-90' : ''}`)
54
-
55
- defineExpose({ isExpanded })
42
+ export const rows = flatten(items)
56
43
  </script>
57
44
 
58
- <div class="{{ sectionClasses }}">
59
- @if(collapsible)
60
- <button
61
- type="button"
62
- class="{{ headerClassesValue }}"
63
- @click="onHeaderClick()"
64
- :aria-expanded="isExpanded() ? 'true' : 'false'"
65
- aria-controls="sidebar-section-{{ id }}"
66
- >
67
- @if(icon)
68
- <div class="{{ icon }} w-4 h-4 flex-shrink-0 opacity-70"></div>
69
- @endif
70
- @if(showLabel)
71
- <span class="{{ labelClasses }}">
72
- {{ label }}
73
- </span>
74
- <div :class="arrowClass()"></div>
75
- @endif
76
- </button>
77
- @else
78
- <div class="{{ headerClassesValue }}">
79
- @if(icon)
80
- <div class="{{ icon }} w-4 h-4 flex-shrink-0 opacity-70"></div>
81
- @endif
82
- @if(showLabel)
83
- <span class="{{ labelClasses }}">
84
- {{ label }}
85
- </span>
86
- @endif
87
- </div>
45
+ <section
46
+ data-sidebar-section
47
+ data-section-id="{{ id }}"
48
+ data-expanded="{{ collapsed ? 'false' : 'true' }}"
49
+ >
50
+ @if(label)
51
+ @if(collapsible)
52
+ <button type="button" data-sidebar-section-header class="{{ headerClasses }}" aria-expanded="{{ collapsed ? 'false' : 'true' }}">
53
+ <span class="truncate">{{ label }}</span>
54
+ <span data-sidebar-section-chevron class="{{ chevronClasses }}" aria-hidden="true"></span>
55
+ </button>
56
+ @else
57
+ <div class="{{ headerClasses }}">
58
+ <span class="truncate">{{ label }}</span>
59
+ </div>
60
+ @endif
88
61
  @endif
89
62
 
90
- @if(showLabel && items.length > 0)
91
- <div
92
- :show="isExpanded()"
93
- id="sidebar-section-{{ id }}"
94
- class="{{ groupClasses }}"
95
- role="group"
96
- aria-label="{{ label }}"
97
- >
98
- @foreach(item in items)
99
- <SidebarItem
100
- :id="item.id"
101
- :label="item.label"
102
- :icon="item.icon"
103
- :href="item.href"
104
- :badge="item.badge"
105
- :active="item.active"
106
- :disabled="item.disabled"
107
- :indent="variant !== 'workspace' && variant !== 'desktop'"
108
- :variant="variant"
109
- @click="onItemClick($event)"
110
- />
111
- @endforeach
112
- </div>
113
- @endif
114
- </div>
63
+ <div class="{{ groupClasses }}" role="group" @if(label)aria-label="{{ label }}"@endif>
64
+ @foreach(rows as row)
65
+ <SidebarItem
66
+ :id="row.id"
67
+ :label="row.label"
68
+ :icon="row.icon"
69
+ :iconColor="row.iconColor"
70
+ :image="row.image"
71
+ :href="row.href"
72
+ :count="row.count"
73
+ :active="row.active"
74
+ :disabled="row.disabled"
75
+ :expandable="row.expandable"
76
+ :expanded="row.expanded"
77
+ :depth="row.depth"
78
+ :parents="row.parents"
79
+ :theme="theme"
80
+ />
81
+ @endforeach
82
+ </div>
83
+ </section>
@@ -1,225 +1,153 @@
1
1
  export { default as Sidebar } from './Sidebar.stx'
2
- export { default as SidebarSection } from './SidebarSection.stx'
3
- export { default as SidebarItem } from './SidebarItem.stx'
4
- export { default as SidebarHeader } from './SidebarHeader.stx'
5
2
  export { default as SidebarFooter } from './SidebarFooter.stx'
3
+ export { default as SidebarHeader } from './SidebarHeader.stx'
4
+ export { default as SidebarItem } from './SidebarItem.stx'
5
+ export { default as SidebarSection } from './SidebarSection.stx'
6
+ export * from './themes'
6
7
 
7
- // Types
8
+ /**
9
+ * One navigation row.
10
+ *
11
+ * ```ts
12
+ * { id: 'icloud', label: 'iCloud', icon: 'i-f7-tray', iconColor: 'blue', count: 248, active: true }
13
+ * ```
14
+ */
8
15
  export interface SidebarItemData {
9
16
  id: string
10
17
  label: string
18
+ /** Iconify utility class, e.g. `i-f7-tray`. F7 icons mirror SF Symbols. */
11
19
  icon?: string
20
+ /** macOS system color name (`"blue"`, `"red"`, `"yellow"`, …) or any CSS color. */
21
+ iconColor?: string
22
+ /** Image URL rendered instead of an icon (album art, avatars). */
23
+ image?: string
12
24
  href?: string
25
+ /** Right-aligned count — rendered as plain gray text like native macOS. */
26
+ count?: string | number
27
+ /** @deprecated Use `count`. */
13
28
  badge?: string | number
14
29
  active?: boolean
15
30
  disabled?: boolean
31
+ /** Nested rows, indented and collapsible under this one. */
32
+ children?: SidebarItemData[]
33
+ /** Show a disclosure chevron even without children. */
34
+ expandable?: boolean
35
+ /** Initial disclosure state when the item has children. Defaults to true. */
36
+ expanded?: boolean
16
37
  }
17
38
 
39
+ /** A titled group of rows (e.g. "Favorites"). Untitled when `label` is empty. */
18
40
  export interface SidebarSectionData {
19
41
  id: string
20
- label: string
21
- icon?: string
42
+ label?: string
22
43
  items: SidebarItemData[]
23
- expanded?: boolean
44
+ /** Section headers collapse their group on click. Defaults to true. */
24
45
  collapsible?: boolean
46
+ /** Initial collapse state. Defaults to false (expanded). */
47
+ collapsed?: boolean
25
48
  }
26
49
 
50
+ /**
51
+ * Sidebar themes. `macos` recreates the sidebar of the latest macOS
52
+ * (Tahoe, macOS 26/27) — translucent material, Liquid Glass edge shimmer,
53
+ * 30px rows with 9px-radius highlights and plain gray counts. `tahoe`,
54
+ * `macos-tahoe` and `macos-latest` are aliases of `macos`. The remaining
55
+ * names are legacy looks kept for backwards compatibility.
56
+ */
57
+ export type SidebarThemeChoice =
58
+ | 'macos'
59
+ | 'macos-tahoe'
60
+ | 'macos-latest'
61
+ | 'tahoe'
62
+ | 'vibrancy'
63
+ | 'solid'
64
+ | 'transparent'
65
+ | 'workspace'
66
+ | 'desktop'
67
+
27
68
  export interface SidebarProps {
28
- /** Array of sidebar sections with their items */
69
+ /** Sections with their items. Omit to compose children via the default slot. */
29
70
  sections?: SidebarSectionData[]
30
- /** Whether the sidebar is collapsed */
31
- collapsed?: boolean
32
- /** Whether the sidebar can be collapsed */
33
- collapsible?: boolean
34
- /** Width of the expanded sidebar in pixels */
71
+ /** Visual theme. Defaults to `macos`. */
72
+ theme?: SidebarThemeChoice
73
+ /** @deprecated Use `theme`. */
74
+ variant?: SidebarThemeChoice
75
+ /** Expanded width in pixels. Defaults to 250 (native macOS default). */
35
76
  width?: number
36
- /** Minimum width when collapsed in pixels */
37
- minWidth?: number
38
- /** Collapse behavior: keep a compact rail or hide the sidebar entirely */
39
- collapseMode?: 'rail' | 'hidden'
40
- /** Visual style variant */
41
- variant?: SidebarVariant
42
- /** Layout placement for app shells and native-like sidebars */
43
- placement?: 'fixed' | 'sticky' | 'static'
44
- /** Position of the sidebar */
45
77
  position?: 'left' | 'right'
46
- /** Whether to show border */
78
+ /** Layout placement for app shells. Defaults to `fixed`. */
79
+ placement?: 'fixed' | 'sticky' | 'static'
47
80
  bordered?: boolean
48
- /** Light-mode tint opacity over sidebar material. Higher values reduce vibrancy. */
49
- materialOpacity?: number
50
- /** Dark-mode tint opacity over sidebar material. Higher values reduce vibrancy. */
51
- materialDarkOpacity?: number
52
- /** Force native/sidebar material appearance instead of following system mode */
53
- materialScheme?: 'system' | 'light' | 'dark'
54
- /** localStorage key used to persist collapse state */
81
+ /** Whether the sidebar starts collapsed. */
82
+ collapsed?: boolean
83
+ collapsible?: boolean
84
+ /** `hidden` slides away entirely (macOS); `rail` keeps a compact strip. */
85
+ collapseMode?: 'hidden' | 'rail'
86
+ /** Rail width when `collapseMode` is `rail`. */
87
+ minWidth?: number
88
+ /** localStorage key that persists collapse state. */
55
89
  persistKey?: string
56
- /** Optional app shell selector whose sidebar width CSS variable should be synced */
90
+ /** App-shell selector whose width CSS variable tracks the sidebar. */
57
91
  shellSelector?: string
58
- /** CSS variable written on shellSelector when the sidebar collapses or expands */
59
92
  widthVar?: string
60
- /** Optional class toggled on the document element while collapsed */
61
93
  collapsedClass?: string
62
- /** Whether hidden desktop collapse should notify Craft's web sidebar material */
63
- nativeMaterialCollapse?: boolean
64
- /** Additional CSS classes */
65
94
  className?: string
66
- /** Callback when collapse state changes */
67
95
  onCollapse?: (collapsed: boolean) => void
68
- /** Callback when a section is toggled */
69
96
  onSectionToggle?: (sectionId: string) => void
70
- /** Callback when an item is clicked */
97
+ onItemToggle?: (event: { id: string, expanded: boolean }) => void
71
98
  onItemClick?: (item: SidebarItemData, event: Event) => void
72
99
  }
73
100
 
74
101
  export interface SidebarSectionProps {
75
102
  id: string
76
- label: string
77
- icon?: string
103
+ label?: string
78
104
  items: SidebarItemData[]
79
- expanded?: boolean
80
105
  collapsible?: boolean
81
- showLabel?: boolean
82
- variant?: SidebarVariant
83
- onToggle?: (id: string) => void
84
- onItemClick?: (item: SidebarItemData, event: Event) => void
106
+ collapsed?: boolean
107
+ theme?: SidebarThemeChoice
108
+ }
109
+
110
+ export interface SidebarItemProps extends SidebarItemData {
111
+ /** Nesting depth — set by SidebarSection when flattening the tree. */
112
+ depth?: number
113
+ /** Slash-joined ancestor ids — set by SidebarSection. */
114
+ parents?: string
115
+ theme?: SidebarThemeChoice
85
116
  }
86
117
 
87
- export interface SidebarItemProps {
118
+ /** A floating toolbar or footer action button. */
119
+ export interface SidebarActionData {
88
120
  id: string
121
+ /** Iconify utility class. */
122
+ icon: string
123
+ /** Accessible label. */
89
124
  label: string
90
- icon?: string
91
- href?: string
92
- badge?: string | number
93
- active?: boolean
94
- disabled?: boolean
95
- indent?: boolean
96
- variant?: SidebarVariant
97
- onClick?: (event: Event) => void
98
125
  }
99
126
 
100
127
  export interface SidebarHeaderProps {
128
+ theme?: SidebarThemeChoice
129
+ /** Render traffic lights (wired to Craft's window API when present). */
130
+ showWindowControls?: boolean
131
+ /** Floating Liquid Glass toolbar buttons on the right. */
132
+ actions?: SidebarActionData[]
133
+ showSearch?: boolean
134
+ searchPlaceholder?: string
135
+ /** Legacy (non-macos) header content. */
101
136
  title?: string
102
137
  subtitle?: string
103
138
  logo?: string
104
- logoIcon?: string
105
- /**
106
- * Show macOS-style window controls. `auto` only renders them when Craft
107
- * exposes a custom-window-controls marker, avoiding duplicate native chrome.
108
- */
109
- showWindowControls?: boolean | 'auto'
110
- showNavigationControls?: boolean
111
- showSearch?: boolean
112
- searchPlaceholder?: string
113
- searchValue?: string
114
- collapsed?: boolean
115
- variant?: SidebarVariant
139
+ onAction?: (actionId: string) => void
116
140
  onSearch?: (value: string) => void
141
+ onWindowControl?: (action: 'close' | 'minimize' | 'zoom') => void
117
142
  }
118
143
 
119
144
  export interface SidebarFooterProps {
120
- showSettings?: boolean
121
- showThemeToggle?: boolean
122
- settingsHref?: string
123
- settingsLabel?: string
124
- collapsed?: boolean
125
- variant?: SidebarVariant
126
- actions?: Array<{
127
- label: string
128
- icon?: string
129
- href?: string
130
- onClick?: () => void
131
- }>
132
- onThemeToggle?: () => void
133
- }
134
-
135
- export type SidebarVariant = 'tahoe' | 'vibrancy' | 'solid' | 'transparent' | 'workspace' | 'desktop'
136
-
137
- export type SidebarMaterial = 'auto' | 'sidebar' | 'hud' | 'popover' | 'content'
138
-
139
- export type SidebarBackgroundEffect = 'none' | 'vibrancy' | 'shimmer'
140
-
141
- /**
142
- * Native Sidebar Configuration
143
- *
144
- * Used to configure the native macOS sidebar when running with Craft's --native-sidebar flag.
145
- * The sidebar uses NSOutlineView with vibrancy for a true Tahoe-style appearance.
146
- */
147
- export interface NativeSidebarConfig {
148
- /** Visual style variant shared with the web fallback */
149
- variant?: SidebarVariant
150
- /** Native material used by Craft desktop sidebars */
151
- material?: SidebarMaterial
152
- /** Native background treatment */
153
- backgroundEffect?: SidebarBackgroundEffect
154
- /** Whether the native sidebar should let the window background show through */
155
- allowsVibrancy?: boolean
156
- /** Light-mode tint opacity over sidebar material */
157
- materialOpacity?: number
158
- /** Dark-mode tint opacity over sidebar material */
159
- materialDarkOpacity?: number
160
- /** Force native/sidebar material appearance instead of following system mode */
161
- materialScheme?: 'system' | 'light' | 'dark'
162
- /** Header configuration */
163
- header?: {
164
- title?: string
165
- subtitle?: string
166
- icon?: string // SF Symbol name (e.g., 'house.fill', 'message.fill')
167
- }
168
- /** Search placeholder text */
169
- searchPlaceholder?: string
170
- /** Array of sidebar sections */
171
- sections: NativeSidebarSection[]
172
- /** Minimum width when collapsed */
173
- minWidth?: number
174
- /** Maximum width */
175
- maxWidth?: number
176
- /** Whether the sidebar can be collapsed */
177
- canCollapse?: boolean
178
- }
179
-
180
- export interface NativeSidebarSection {
181
- /** Unique section ID */
182
- id: string
183
- /** Section title (shown as header) */
184
- title: string
185
- /** Whether section is collapsible */
186
- collapsible?: boolean
187
- /** Whether section starts collapsed */
188
- collapsed?: boolean
189
- /** Items in this section */
190
- items: NativeSidebarItem[]
191
- }
192
-
193
- export interface NativeSidebarItem {
194
- /** Unique item ID */
195
- id: string
196
- /** Display label */
197
- label: string
198
- /** SF Symbol icon name (e.g., 'tray.fill', 'envelope.badge.fill') */
199
- icon?: string
200
- /** Badge count or text */
201
- badge?: string | number
202
- /** Tint color for the icon (hex) */
203
- tintColor?: string
204
- /** Whether this item is selected */
205
- selected?: boolean
206
- /** Whether this item is disabled */
207
- disabled?: boolean
208
- /** Nested children items */
209
- children?: NativeSidebarItem[]
210
- /** Custom data to pass to selection handler */
211
- data?: Record<string, unknown>
212
- }
213
-
214
- /**
215
- * Craft sidebar selection event
216
- * Passed to window.craft._sidebarSelectHandler when user clicks a sidebar item
217
- */
218
- export interface SidebarSelectEvent {
219
- /** ID of the selected item */
220
- itemId: string
221
- /** The full item object */
222
- item: NativeSidebarItem
223
- /** Parent section ID */
224
- sectionId?: string
145
+ theme?: SidebarThemeChoice
146
+ /** Account row, like Music's profile footer. */
147
+ avatar?: string
148
+ name?: string
149
+ detail?: string
150
+ actions?: SidebarActionData[]
151
+ onProfileClick?: () => void
152
+ onAction?: (actionId: string) => void
225
153
  }