@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.
- package/dist/Sidebar-yb115a2w.stx +458 -0
- package/dist/SidebarFooter-0aegr99p.stx +71 -0
- package/dist/SidebarHeader-ed5wb13t.stx +103 -0
- package/dist/SidebarItem-gtrcw4ky.stx +86 -0
- package/dist/SidebarSection-qf8fhy55.stx +83 -0
- package/dist/index.js +168 -12
- package/dist/ui/sidebar/index.d.ts +67 -107
- package/dist/ui/sidebar/themes.d.ts +74 -0
- package/package.json +10 -4
- package/src/ui/sidebar/Sidebar.stx +356 -228
- package/src/ui/sidebar/SidebarFooter.stx +56 -94
- package/src/ui/sidebar/SidebarHeader.stx +69 -165
- package/src/ui/sidebar/SidebarItem.stx +73 -79
- package/src/ui/sidebar/SidebarSection.stx +70 -101
- package/src/ui/sidebar/index.ts +100 -172
- package/src/ui/sidebar/themes.ts +240 -0
- package/stx-plugin.ts +19 -0
- package/dist/Sidebar-0jzasrmt.stx +0 -330
- package/dist/SidebarFooter-zsfsgcxb.stx +0 -109
- package/dist/SidebarHeader-ywpmx14d.stx +0 -199
- package/dist/SidebarItem-h53sgqzz.stx +0 -92
- package/dist/SidebarSection-g195cb4f.stx +0 -114
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar theme registry
|
|
3
|
+
*
|
|
4
|
+
* Every visual decision the sidebar makes lives here, expressed as utility
|
|
5
|
+
* classes, so a theme can be read top-to-bottom like a spec sheet.
|
|
6
|
+
*
|
|
7
|
+
* The flagship theme is `macos` — a faithful recreation of the sidebar in
|
|
8
|
+
* the latest macOS (Tahoe, macOS 26/27 "Liquid Glass"). Its metrics were
|
|
9
|
+
* measured from native apps (Mail, Music, Notes) at @2x:
|
|
10
|
+
*
|
|
11
|
+
* row pitch 32px = 30px row + 2px gap · highlight radius 9px
|
|
12
|
+
* label 13px · count 13px secondary gray · section header 11px semibold
|
|
13
|
+
* icon 17px in a fixed 22px slot · disclosure gutter 18px · child indent 16px
|
|
14
|
+
*
|
|
15
|
+
* Legacy themes (`workspace`, `desktop`, `solid`, `transparent`, `vibrancy`)
|
|
16
|
+
* are preserved verbatim from the previous variant maps. `tahoe` now aliases
|
|
17
|
+
* `macos` — it always meant "look like macOS", and now it actually does.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** macOS system colors (light appearance) for tinting sidebar icons. */
|
|
21
|
+
export const macosColors = {
|
|
22
|
+
blue: '#0088ff',
|
|
23
|
+
red: '#ff383c',
|
|
24
|
+
pink: '#ff2d55',
|
|
25
|
+
orange: '#ff8d28',
|
|
26
|
+
yellow: '#ffcc00',
|
|
27
|
+
green: '#34c759',
|
|
28
|
+
teal: '#00c3d0',
|
|
29
|
+
cyan: '#00c0e8',
|
|
30
|
+
indigo: '#6155f5',
|
|
31
|
+
purple: '#cb30e0',
|
|
32
|
+
brown: '#ac7f5e',
|
|
33
|
+
gray: '#8e8e93',
|
|
34
|
+
} as const
|
|
35
|
+
|
|
36
|
+
export type MacosColor = keyof typeof macosColors
|
|
37
|
+
|
|
38
|
+
/** Class groups a sidebar theme provides. All values are utility classes. */
|
|
39
|
+
export interface SidebarTheme {
|
|
40
|
+
/** The <aside> pane itself: material, text color, borders. */
|
|
41
|
+
pane: string
|
|
42
|
+
/** Extra overlay layers rendered inside the pane (visual only). */
|
|
43
|
+
layers: {
|
|
44
|
+
/** Liquid Glass edge highlight — a bright rim that subtly shimmers. */
|
|
45
|
+
shimmerRim?: boolean
|
|
46
|
+
/** Soft tint gradient blended over the material. */
|
|
47
|
+
tint?: string
|
|
48
|
+
}
|
|
49
|
+
/** Scrollable content area between header and footer. */
|
|
50
|
+
scrollArea: string
|
|
51
|
+
/** Section header row (e.g. "Favorites", "iCloud"). */
|
|
52
|
+
sectionHeader: string
|
|
53
|
+
/** Chevron inside the section header (revealed on hover). */
|
|
54
|
+
sectionChevron: string
|
|
55
|
+
/** Wrapper around a section's items. */
|
|
56
|
+
sectionGroup: string
|
|
57
|
+
/** One navigation row, layout + typography. State styles live below. */
|
|
58
|
+
item: {
|
|
59
|
+
base: string
|
|
60
|
+
hover: string
|
|
61
|
+
active: string
|
|
62
|
+
pressed: string
|
|
63
|
+
disabled: string
|
|
64
|
+
/** Fixed-width leading slot the disclosure chevron sits in. */
|
|
65
|
+
disclosure: string
|
|
66
|
+
/** Chevron glyph itself (rotates 90° when expanded). */
|
|
67
|
+
chevron: string
|
|
68
|
+
/** Fixed-width slot that centers the 17px icon. */
|
|
69
|
+
iconSlot: string
|
|
70
|
+
icon: string
|
|
71
|
+
/** Thumbnail images (album art, avatars) rendered instead of an icon. */
|
|
72
|
+
image: string
|
|
73
|
+
label: string
|
|
74
|
+
/** Right-aligned unread/item count — plain gray text on macOS. */
|
|
75
|
+
count: string
|
|
76
|
+
/** Indentation added per nesting depth. */
|
|
77
|
+
indentPerLevel: number
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const macos: SidebarTheme = {
|
|
82
|
+
pane: [
|
|
83
|
+
// Frosted sidebar material. In a Craft window with `webSidebarMaterial`
|
|
84
|
+
// the native vibrancy shows through; in a browser the backdrop blur
|
|
85
|
+
// approximates it over whatever sits behind the pane.
|
|
86
|
+
'bg-white/55 dark:bg-[#1e1e23]/55',
|
|
87
|
+
'backdrop-blur-[60px] backdrop-saturate-[1.8]',
|
|
88
|
+
'text-black dark:text-white',
|
|
89
|
+
'select-none',
|
|
90
|
+
].join(' '),
|
|
91
|
+
layers: {
|
|
92
|
+
shimmerRim: true,
|
|
93
|
+
},
|
|
94
|
+
scrollArea: 'flex-1 overflow-y-auto overflow-x-hidden px-[10px] pb-[10px]',
|
|
95
|
+
sectionHeader: [
|
|
96
|
+
'group/section flex w-full items-center',
|
|
97
|
+
'pl-[8px] pr-[6px] pt-[16px] pb-[5px]',
|
|
98
|
+
'text-[11px] font-semibold leading-[13px]',
|
|
99
|
+
'text-[#3c3c43]/60 dark:text-[#ebebf5]/60',
|
|
100
|
+
].join(' '),
|
|
101
|
+
sectionChevron: [
|
|
102
|
+
'i-f7-chevron-down h-[11px] w-[11px] ml-auto',
|
|
103
|
+
'text-[#3c3c43]/45 dark:text-[#ebebf5]/45',
|
|
104
|
+
'opacity-0 group-hover/section:opacity-100 transition-opacity duration-150',
|
|
105
|
+
'transition-transform duration-200',
|
|
106
|
+
].join(' '),
|
|
107
|
+
sectionGroup: 'flex flex-col gap-[2px]',
|
|
108
|
+
item: {
|
|
109
|
+
base: [
|
|
110
|
+
'flex w-full items-center',
|
|
111
|
+
'h-[30px] rounded-[9px] pl-[4px] pr-[8px]',
|
|
112
|
+
'text-[13px] leading-[16px] font-normal',
|
|
113
|
+
'text-black dark:text-white',
|
|
114
|
+
'transition-colors duration-150 ease-out',
|
|
115
|
+
'cursor-default',
|
|
116
|
+
].join(' '),
|
|
117
|
+
hover: 'hover:bg-black/4 dark:hover:bg-white/6',
|
|
118
|
+
active: 'bg-black/8 dark:bg-white/14',
|
|
119
|
+
pressed: 'active:bg-black/10 dark:active:bg-white/18',
|
|
120
|
+
disabled: 'opacity-40 pointer-events-none',
|
|
121
|
+
disclosure: 'flex h-[16px] w-[18px] shrink-0 items-center justify-center',
|
|
122
|
+
chevron: [
|
|
123
|
+
'i-f7-chevron-right h-[11px] w-[11px]',
|
|
124
|
+
'text-[#3c3c43]/55 dark:text-[#ebebf5]/55',
|
|
125
|
+
'transition-transform duration-200 ease-out',
|
|
126
|
+
].join(' '),
|
|
127
|
+
iconSlot: 'flex h-[20px] w-[22px] shrink-0 items-center justify-center mr-[7px]',
|
|
128
|
+
icon: 'h-[17px] w-[17px]',
|
|
129
|
+
image: 'h-[20px] w-[20px] rounded-[4px] object-cover shadow-sm',
|
|
130
|
+
label: 'flex-1 truncate text-left',
|
|
131
|
+
count: [
|
|
132
|
+
'ml-[8px] shrink-0 tabular-nums',
|
|
133
|
+
'text-[13px] leading-[16px]',
|
|
134
|
+
'text-[#3c3c43]/60 dark:text-[#ebebf5]/60',
|
|
135
|
+
].join(' '),
|
|
136
|
+
indentPerLevel: 16,
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Legacy themes carried over from the old per-component variant maps so
|
|
142
|
+
* existing apps keep rendering identically. New work should use `macos`.
|
|
143
|
+
*/
|
|
144
|
+
const workspace: SidebarTheme = {
|
|
145
|
+
pane: 'bg-[#f4f4f3] dark:bg-neutral-950 text-zinc-950 dark:text-white select-none',
|
|
146
|
+
layers: {},
|
|
147
|
+
scrollArea: 'flex-1 overflow-y-auto overflow-x-hidden px-5 py-5',
|
|
148
|
+
sectionHeader: 'group/section flex w-full items-center gap-2.5 px-4 pb-2 pt-5 text-[13px] font-semibold uppercase tracking-[0.12em] text-zinc-400 dark:text-zinc-500',
|
|
149
|
+
sectionChevron: 'i-f7-chevron-down h-3 w-3 ml-auto opacity-0 group-hover/section:opacity-100 transition-opacity duration-150 transition-transform duration-200',
|
|
150
|
+
sectionGroup: 'flex flex-col space-y-1',
|
|
151
|
+
item: {
|
|
152
|
+
base: 'flex w-full items-center gap-3 rounded-[16px] px-3.5 py-2 text-[14px] leading-tight transition-colors duration-150',
|
|
153
|
+
hover: 'hover:bg-[#ececea] hover:text-zinc-950 dark:hover:bg-white/10',
|
|
154
|
+
active: 'bg-[#e7e7e5] text-zinc-950 dark:bg-white/12 dark:text-white',
|
|
155
|
+
pressed: 'active:bg-[#e2e2e0] dark:active:bg-white/15',
|
|
156
|
+
disabled: 'opacity-50 pointer-events-none',
|
|
157
|
+
disclosure: 'flex h-4 w-4 shrink-0 items-center justify-center',
|
|
158
|
+
chevron: 'i-f7-chevron-right h-3 w-3 opacity-50 transition-transform duration-200',
|
|
159
|
+
iconSlot: 'flex h-5 w-5 shrink-0 items-center justify-center',
|
|
160
|
+
icon: 'h-[18px] w-[18px] text-zinc-500 dark:text-zinc-500',
|
|
161
|
+
image: 'h-5 w-5 rounded object-cover',
|
|
162
|
+
label: 'flex-1 truncate text-left text-zinc-700 dark:text-zinc-300',
|
|
163
|
+
count: 'ml-auto grid h-5 min-w-5 place-items-center rounded-full bg-white/70 px-1.5 text-[11px] font-medium text-zinc-500 shadow-sm dark:bg-white/10 dark:text-zinc-400',
|
|
164
|
+
indentPerLevel: 12,
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const desktop: SidebarTheme = {
|
|
169
|
+
pane: 'bg-[#f3f3f1]/48 dark:bg-neutral-950/32 backdrop-blur-3xl backdrop-saturate-150 text-zinc-950 dark:text-white select-none',
|
|
170
|
+
layers: {
|
|
171
|
+
tint: '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%)]',
|
|
172
|
+
},
|
|
173
|
+
scrollArea: 'flex-1 overflow-y-auto overflow-x-hidden px-5 py-2',
|
|
174
|
+
sectionHeader: 'group/section flex w-full items-center gap-2.5 px-3 pb-1.5 pt-3 rounded-[14px] text-[12px] font-semibold uppercase tracking-[0.10em] text-zinc-500/80 dark:text-zinc-400/80',
|
|
175
|
+
sectionChevron: 'i-f7-chevron-down h-3 w-3 ml-auto opacity-0 group-hover/section:opacity-100 transition-opacity duration-150 transition-transform duration-200',
|
|
176
|
+
sectionGroup: 'flex flex-col space-y-1',
|
|
177
|
+
item: {
|
|
178
|
+
base: 'flex w-full items-center gap-3 rounded-[14px] px-3 py-1.5 text-[13.5px] leading-tight transition-colors duration-150',
|
|
179
|
+
hover: 'hover:bg-white/38 hover:text-zinc-950 dark:hover:bg-white/10',
|
|
180
|
+
active: 'bg-[#e8e8e6]/90 text-zinc-950 dark:bg-white/12 dark:text-white',
|
|
181
|
+
pressed: 'active:bg-white/50 dark:active:bg-white/15',
|
|
182
|
+
disabled: 'opacity-50 pointer-events-none',
|
|
183
|
+
disclosure: 'flex h-4 w-4 shrink-0 items-center justify-center',
|
|
184
|
+
chevron: 'i-f7-chevron-right h-3 w-3 opacity-50 transition-transform duration-200',
|
|
185
|
+
iconSlot: 'flex h-5 w-5 shrink-0 items-center justify-center',
|
|
186
|
+
icon: 'h-[18px] w-[18px] text-zinc-600 dark:text-zinc-400',
|
|
187
|
+
image: 'h-5 w-5 rounded object-cover',
|
|
188
|
+
label: 'flex-1 truncate text-left text-zinc-700 dark:text-zinc-300',
|
|
189
|
+
count: 'ml-auto grid h-5 min-w-5 place-items-center rounded-full bg-white/72 px-1.5 text-[11px] font-medium text-zinc-500 shadow-sm dark:bg-white/10 dark:text-zinc-400',
|
|
190
|
+
indentPerLevel: 12,
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const solid: SidebarTheme = {
|
|
195
|
+
...macos,
|
|
196
|
+
pane: 'bg-stone-100 dark:bg-neutral-900 text-black dark:text-white select-none',
|
|
197
|
+
layers: {},
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const transparent: SidebarTheme = {
|
|
201
|
+
...macos,
|
|
202
|
+
pane: 'bg-transparent text-black dark:text-white select-none',
|
|
203
|
+
layers: {},
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const vibrancy: SidebarTheme = {
|
|
207
|
+
...macos,
|
|
208
|
+
pane: 'bg-white/50 dark:bg-black/40 backdrop-blur-3xl backdrop-saturate-200 text-black dark:text-white select-none',
|
|
209
|
+
layers: {},
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export const sidebarThemes: Record<string, SidebarTheme> = {
|
|
213
|
+
macos,
|
|
214
|
+
// Historical names people may still pass — all render the macOS look.
|
|
215
|
+
'tahoe': macos,
|
|
216
|
+
'macos-tahoe': macos,
|
|
217
|
+
'macos-latest': macos,
|
|
218
|
+
workspace,
|
|
219
|
+
desktop,
|
|
220
|
+
solid,
|
|
221
|
+
transparent,
|
|
222
|
+
vibrancy,
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export type SidebarThemeName = keyof typeof sidebarThemes
|
|
226
|
+
|
|
227
|
+
export function resolveSidebarTheme(name?: string): SidebarTheme {
|
|
228
|
+
return sidebarThemes[name || 'macos'] || sidebarThemes.macos
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Resolve an item's icon tint. Accepts a macOS system color name
|
|
233
|
+
* (`"blue"`, `"red"`, …) or any CSS color, and falls back to the
|
|
234
|
+
* theme's default icon color when unset.
|
|
235
|
+
*/
|
|
236
|
+
export function resolveIconColor(color?: string): string | undefined {
|
|
237
|
+
if (!color)
|
|
238
|
+
return undefined
|
|
239
|
+
return macosColors[color as MacosColor] || color
|
|
240
|
+
}
|
package/stx-plugin.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stx plugin shim for @stacksjs/components.
|
|
3
|
+
*
|
|
4
|
+
* Register in a project's stx config to make every library component
|
|
5
|
+
* (<Sidebar>, <Button>, <Dialog>, …) resolvable by tag name — component
|
|
6
|
+
* resolution walks subdirectories, so the grouped `src/ui/<name>/` layout
|
|
7
|
+
* works as-is:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* // stx.config.ts / config/ui.ts
|
|
11
|
+
* export default {
|
|
12
|
+
* plugins: ['@stacksjs/components/stx-plugin'],
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export default {
|
|
17
|
+
name: '@stacksjs/components',
|
|
18
|
+
components: './src/ui',
|
|
19
|
+
}
|
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
<script server>
|
|
2
|
-
export const sections = $props.sections || []
|
|
3
|
-
export const collapsed = $props.collapsed ?? false
|
|
4
|
-
export const collapsible = $props.collapsible ?? true
|
|
5
|
-
export const width = $props.width || 240
|
|
6
|
-
export const minWidth = $props.minWidth || 64
|
|
7
|
-
export const collapseMode = $props.collapseMode || 'rail'
|
|
8
|
-
export const variant = $props.variant || 'tahoe'
|
|
9
|
-
export const position = $props.position || 'left'
|
|
10
|
-
export const placement = $props.placement || 'fixed'
|
|
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')
|
|
20
|
-
export const className = $props.className || ''
|
|
21
|
-
|
|
22
|
-
const variantStyles = {
|
|
23
|
-
tahoe: {
|
|
24
|
-
bg: 'bg-white/70 dark:bg-black/50',
|
|
25
|
-
border: 'border-black/5 dark:border-white/10',
|
|
26
|
-
backdrop: 'backdrop-blur-2xl backdrop-saturate-180',
|
|
27
|
-
tint: true,
|
|
28
|
-
},
|
|
29
|
-
vibrancy: {
|
|
30
|
-
bg: 'bg-white/50 dark:bg-black/40',
|
|
31
|
-
border: 'border-white/20 dark:border-white/10',
|
|
32
|
-
backdrop: 'backdrop-blur-3xl backdrop-saturate-200',
|
|
33
|
-
tint: false,
|
|
34
|
-
},
|
|
35
|
-
solid: {
|
|
36
|
-
bg: 'bg-stone-100 dark:bg-neutral-900',
|
|
37
|
-
border: 'border-stone-200 dark:border-neutral-800',
|
|
38
|
-
backdrop: '',
|
|
39
|
-
tint: false,
|
|
40
|
-
},
|
|
41
|
-
transparent: {
|
|
42
|
-
bg: 'bg-transparent',
|
|
43
|
-
border: 'border-transparent',
|
|
44
|
-
backdrop: '',
|
|
45
|
-
tint: false,
|
|
46
|
-
},
|
|
47
|
-
workspace: {
|
|
48
|
-
bg: 'bg-[#f4f4f3] dark:bg-neutral-950',
|
|
49
|
-
border: 'border-transparent',
|
|
50
|
-
backdrop: '',
|
|
51
|
-
tint: false,
|
|
52
|
-
nav: 'px-5 py-5 space-y-1',
|
|
53
|
-
slot: 'px-5 py-5',
|
|
54
|
-
},
|
|
55
|
-
desktop: {
|
|
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',
|
|
59
|
-
tint: false,
|
|
60
|
-
shimmer: true,
|
|
61
|
-
nav: 'px-5 py-2 space-y-1',
|
|
62
|
-
slot: 'px-5 py-2',
|
|
63
|
-
},
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const style = variantStyles[variant] || variantStyles.tahoe
|
|
67
|
-
export const variantTint = style.tint
|
|
68
|
-
export const variantShimmer = style.shimmer
|
|
69
|
-
const borderSide = position === 'left' ? 'border-r' : 'border-l'
|
|
70
|
-
const borderClasses = bordered ? `${borderSide} ${style.border}` : ''
|
|
71
|
-
const positionClass = position === 'left' ? 'left-0' : 'right-0'
|
|
72
|
-
const placementClasses = {
|
|
73
|
-
fixed: `fixed inset-y-0 z-50 ${positionClass}`,
|
|
74
|
-
sticky: 'sticky top-0 h-screen',
|
|
75
|
-
static: 'relative h-full',
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export const containerClasses = [
|
|
79
|
-
placementClasses[placement] || placementClasses.fixed,
|
|
80
|
-
'flex flex-col',
|
|
81
|
-
'transition-all duration-300 ease-out',
|
|
82
|
-
'overflow-hidden',
|
|
83
|
-
style.bg,
|
|
84
|
-
style.backdrop,
|
|
85
|
-
borderClasses,
|
|
86
|
-
className,
|
|
87
|
-
].filter(Boolean).join(' ')
|
|
88
|
-
|
|
89
|
-
export const collapseBtnPosition = variant === 'desktop'
|
|
90
|
-
? (position === 'left' ? 'left-[166px]' : 'right-[166px]')
|
|
91
|
-
: (position === 'left' ? 'right-2' : 'left-2')
|
|
92
|
-
export const navClasses = style.nav || 'px-3 py-2 space-y-1'
|
|
93
|
-
export const slotClasses = style.slot || 'px-3 py-2'
|
|
94
|
-
</script>
|
|
95
|
-
|
|
96
|
-
<script client>
|
|
97
|
-
const emit = defineEmits()
|
|
98
|
-
const collapsedProp = {{ collapsed }}
|
|
99
|
-
const collapsibleProp = {{ collapsible }}
|
|
100
|
-
const widthProp = {{ width }}
|
|
101
|
-
const minWidthProp = {{ minWidth }}
|
|
102
|
-
const collapseModeProp = {{ collapseMode }}
|
|
103
|
-
const sectionsProp = {{ sections }}
|
|
104
|
-
const positionProp = {{ position }}
|
|
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 }}
|
|
114
|
-
|
|
115
|
-
const collapsed = state(collapsedProp)
|
|
116
|
-
const isHovered = state(false)
|
|
117
|
-
const hasNativeSidebar = state(false)
|
|
118
|
-
|
|
119
|
-
const showExpanded = derived(() => !collapsed() || isHovered())
|
|
120
|
-
const sidebarWidth = derived(() => {
|
|
121
|
-
if (collapsed() && collapseModeProp === 'hidden') return '0px'
|
|
122
|
-
if (collapsed() && !isHovered()) return `${minWidthProp}px`
|
|
123
|
-
return `${widthProp}px`
|
|
124
|
-
})
|
|
125
|
-
const collapseAriaLabel = derived(() => collapsed() ? 'Expand sidebar' : 'Collapse sidebar')
|
|
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
|
-
}
|
|
157
|
-
|
|
158
|
-
function onMouseEnter() {
|
|
159
|
-
if (collapsed() && collapsibleProp && collapseModeProp !== 'hidden') isHovered.set(true)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function onMouseLeave() {
|
|
163
|
-
isHovered.set(false)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function toggleCollapse() {
|
|
167
|
-
setCollapsed(!collapsed())
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function onSectionToggle(sectionId) {
|
|
171
|
-
emit('sectionToggle', sectionId)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function onItemClick(event) {
|
|
175
|
-
emit('itemClick', event.detail)
|
|
176
|
-
}
|
|
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
|
-
|
|
185
|
-
onMount(async () => {
|
|
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) {
|
|
205
|
-
try {
|
|
206
|
-
const sidebar = await nativeSidebarApi.createSidebar({
|
|
207
|
-
position: positionProp,
|
|
208
|
-
width: widthProp,
|
|
209
|
-
minWidth: minWidthProp,
|
|
210
|
-
collapseMode: collapseModeProp,
|
|
211
|
-
collapsible: collapsibleProp,
|
|
212
|
-
variant: variantProp,
|
|
213
|
-
material: variantProp === 'desktop' ? 'sidebar' : undefined,
|
|
214
|
-
backgroundEffect: variantProp === 'desktop' ? 'vibrancy' : undefined,
|
|
215
|
-
materialOpacity: materialOpacityProp,
|
|
216
|
-
materialDarkOpacity: materialDarkOpacityProp,
|
|
217
|
-
materialScheme: materialSchemeProp,
|
|
218
|
-
allowsVibrancy: variantProp === 'desktop' || variantProp === 'vibrancy' || variantProp === 'tahoe',
|
|
219
|
-
sections: sectionsProp.map(s => ({
|
|
220
|
-
id: s.id,
|
|
221
|
-
label: s.label,
|
|
222
|
-
items: s.items?.map(i => ({ id: i.id, label: i.label, icon: i.icon, href: i.href, badge: i.badge })),
|
|
223
|
-
})),
|
|
224
|
-
})
|
|
225
|
-
if (sidebar) hasNativeSidebar.set(true)
|
|
226
|
-
}
|
|
227
|
-
catch (err) {
|
|
228
|
-
console.warn('[Sidebar] Failed to create native sidebar, using web fallback:', err)
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
defineExpose({ collapsed, toggle: toggleCollapse })
|
|
234
|
-
</script>
|
|
235
|
-
|
|
236
|
-
<aside
|
|
237
|
-
:show="!hasNativeSidebar()"
|
|
238
|
-
class="{{ containerClasses }} sidebar-container"
|
|
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}`"
|
|
243
|
-
@mouseenter="onMouseEnter()"
|
|
244
|
-
@mouseleave="onMouseLeave()"
|
|
245
|
-
role="navigation"
|
|
246
|
-
aria-label="Sidebar navigation"
|
|
247
|
-
>
|
|
248
|
-
@if(variantTint)
|
|
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>
|
|
250
|
-
@endif
|
|
251
|
-
@if(variantShimmer)
|
|
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>
|
|
253
|
-
@endif
|
|
254
|
-
|
|
255
|
-
@if($slots.header)
|
|
256
|
-
<div class="flex-shrink-0 relative z-10">
|
|
257
|
-
<slot name="header" />
|
|
258
|
-
</div>
|
|
259
|
-
@endif
|
|
260
|
-
|
|
261
|
-
<div class="flex-1 overflow-y-auto overflow-x-hidden relative z-10">
|
|
262
|
-
@if(sections.length > 0)
|
|
263
|
-
<nav class="{{ navClasses }}">
|
|
264
|
-
@foreach(section in sections)
|
|
265
|
-
<SidebarSection
|
|
266
|
-
:id="section.id"
|
|
267
|
-
:label="section.label"
|
|
268
|
-
:icon="section.icon"
|
|
269
|
-
:items="section.items"
|
|
270
|
-
:expanded="section.expanded !== false"
|
|
271
|
-
:collapsible="section.collapsible !== false"
|
|
272
|
-
:showLabel="showExpanded()"
|
|
273
|
-
:variant="variant"
|
|
274
|
-
@toggle="onSectionToggle(section.id)"
|
|
275
|
-
@itemClick="onItemClick($event)"
|
|
276
|
-
/>
|
|
277
|
-
@endforeach
|
|
278
|
-
</nav>
|
|
279
|
-
@else
|
|
280
|
-
<div class="{{ slotClasses }}">
|
|
281
|
-
<slot />
|
|
282
|
-
</div>
|
|
283
|
-
@endif
|
|
284
|
-
</div>
|
|
285
|
-
|
|
286
|
-
@if($slots.footer)
|
|
287
|
-
<div class="flex-shrink-0 relative z-10">
|
|
288
|
-
<slot name="footer" />
|
|
289
|
-
</div>
|
|
290
|
-
@endif
|
|
291
|
-
|
|
292
|
-
@if(collapsible)
|
|
293
|
-
<button
|
|
294
|
-
type="button"
|
|
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"
|
|
296
|
-
@click="toggleCollapse()"
|
|
297
|
-
:aria-label="collapseAriaLabel()"
|
|
298
|
-
data-sidebar-collapse
|
|
299
|
-
data-stx-sidebar-collapse-button
|
|
300
|
-
>
|
|
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>
|
|
303
|
-
</button>
|
|
304
|
-
@endif
|
|
305
|
-
</aside>
|
|
306
|
-
|
|
307
|
-
<style scoped>
|
|
308
|
-
.overflow-y-auto::-webkit-scrollbar { width: 6px; }
|
|
309
|
-
.overflow-y-auto::-webkit-scrollbar-track { background: transparent; }
|
|
310
|
-
.overflow-y-auto::-webkit-scrollbar-thumb { background-color: rgba(0, 0, 0, 0.15); border-radius: 3px; }
|
|
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
|
-
}
|
|
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
|
-
}
|
|
327
|
-
.overflow-y-auto::-webkit-scrollbar-thumb { background-color: rgba(255, 255, 255, 0.15); }
|
|
328
|
-
.overflow-y-auto::-webkit-scrollbar-thumb:hover { background-color: rgba(255, 255, 255, 0.25); }
|
|
329
|
-
}
|
|
330
|
-
</style>
|