shadcn-glass-ui 2.1.4 → 2.1.5
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/README.md +3 -3
- package/context7.json +17 -2
- package/dist/shadcn-glass-ui.css +1 -1
- package/docs/AI_USAGE.md +5 -5
- package/docs/BEST_PRACTICES.md +1 -1
- package/docs/COMPONENTS_CATALOG.md +215 -0
- package/docs/EXPORTS_MAP.json +140 -14
- package/docs/EXPORTS_STRUCTURE.md +43 -9
- package/docs/GETTING_STARTED.md +1 -1
- package/docs/REGISTRY_USAGE.md +1 -1
- package/docs/api/README.md +1 -1
- package/docs/components/SIDEBAR_GLASS.md +555 -0
- package/docs/components/SPLIT_LAYOUT_GLASS.md +304 -365
- package/package.json +3 -2
|
@@ -8,16 +8,20 @@ Responsive two-column layout component with sticky scroll behavior and glassmorp
|
|
|
8
8
|
features independent scrolling in each panel after sticky positioning activates, making it perfect
|
|
9
9
|
for documentation sites, dashboards, and analytics applications.
|
|
10
10
|
|
|
11
|
+
**API:** Compound component only (v2.2.0+). Legacy props API has been removed.
|
|
12
|
+
|
|
11
13
|
### Key Features
|
|
12
14
|
|
|
15
|
+
- **Compound Component API** - Provider, Root, Sidebar, Main, and nested components
|
|
13
16
|
- **Sticky Scroll Behavior** - Panels scroll together until reaching sticky offset, then scroll
|
|
14
17
|
independently
|
|
15
18
|
- **Responsive Design** - 2 columns on desktop, configurable mobile layouts
|
|
16
19
|
(stack/main-only/sidebar-only)
|
|
20
|
+
- **Master-Detail Pattern** - Built-in selection state via `selectedKey`
|
|
17
21
|
- **CSS Grid with minmax()** - Minimum sidebar width prevents squeezing
|
|
18
22
|
- **Glassmorphism Styling** - Configurable blur intensity (subtle/medium/strong)
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
23
|
+
- **Keyboard Shortcut** - Toggle sidebar with Cmd/Ctrl + B
|
|
24
|
+
- **URL Persistence** - Optional URL parameter sync for selection state
|
|
21
25
|
- **Theme Support** - Works with all 3 themes (glass, light, aurora)
|
|
22
26
|
|
|
23
27
|
### Browser Compatibility
|
|
@@ -31,205 +35,299 @@ for documentation sites, dashboards, and analytics applications.
|
|
|
31
35
|
|
|
32
36
|
## Installation
|
|
33
37
|
|
|
34
|
-
The component is part of the Glass UI library. Import it from the composite components
|
|
38
|
+
The component is part of the Glass UI library. Import it from the composite components:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { SplitLayoutGlass, useSplitLayout } from 'shadcn-glass-ui';
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Compound API Reference
|
|
47
|
+
|
|
48
|
+
### Component Structure
|
|
35
49
|
|
|
36
50
|
```tsx
|
|
37
|
-
|
|
38
|
-
|
|
51
|
+
<SplitLayoutGlass.Provider>
|
|
52
|
+
{' '}
|
|
53
|
+
// Context provider (required)
|
|
54
|
+
<SplitLayoutGlass.Root>
|
|
55
|
+
{' '}
|
|
56
|
+
// Grid container
|
|
57
|
+
<SplitLayoutGlass.Sidebar>
|
|
58
|
+
{' '}
|
|
59
|
+
// Sidebar panel (aside element)
|
|
60
|
+
<SplitLayoutGlass.SidebarHeader /> // Sticky header
|
|
61
|
+
<SplitLayoutGlass.SidebarContent /> // Scrollable content
|
|
62
|
+
<SplitLayoutGlass.SidebarFooter /> // Sticky footer
|
|
63
|
+
</SplitLayoutGlass.Sidebar>
|
|
64
|
+
<SplitLayoutGlass.Main>
|
|
65
|
+
{' '}
|
|
66
|
+
// Main panel (main element)
|
|
67
|
+
<SplitLayoutGlass.MainHeader /> // Sticky header
|
|
68
|
+
<SplitLayoutGlass.MainContent /> // Scrollable content
|
|
69
|
+
<SplitLayoutGlass.MainFooter /> // Sticky footer
|
|
70
|
+
</SplitLayoutGlass.Main>
|
|
71
|
+
</SplitLayoutGlass.Root>
|
|
72
|
+
<SplitLayoutGlass.Trigger /> // Toggle button (optional)
|
|
73
|
+
</SplitLayoutGlass.Provider>
|
|
39
74
|
```
|
|
40
75
|
|
|
41
76
|
---
|
|
42
77
|
|
|
43
78
|
## Props API
|
|
44
79
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
80
|
+
### Provider Props
|
|
81
|
+
|
|
82
|
+
| Prop | Type | Default | Description |
|
|
83
|
+
| --------------------- | --------------------------------------- | ---------- | ---------------------------------------- |
|
|
84
|
+
| `selectedKey` | `string \| null` | - | Controlled selected key (master-detail) |
|
|
85
|
+
| `onSelectedKeyChange` | `(key: string \| null) => void` | - | Selection change handler |
|
|
86
|
+
| `defaultSelectedKey` | `string \| null` | - | Initial selected key |
|
|
87
|
+
| `open` | `boolean` | - | Controlled sidebar open state |
|
|
88
|
+
| `onOpenChange` | `(open: boolean) => void` | - | Open state change handler |
|
|
89
|
+
| `defaultOpen` | `boolean` | `true` | Initial sidebar open state |
|
|
90
|
+
| `breakpoint` | `'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | `'md'` | Desktop layout breakpoint |
|
|
91
|
+
| `mobileMode` | `'stack' \| 'accordion' \| 'drawer'` | `'stack'` | Mobile layout behavior |
|
|
92
|
+
| `intensity` | `'subtle' \| 'medium' \| 'strong'` | `'medium'` | Glass blur intensity |
|
|
93
|
+
| `stickyOffset` | `number` | `24` | Sticky offset in pixels |
|
|
94
|
+
| `urlParamName` | `string` | - | URL param name for selection persistence |
|
|
95
|
+
| `keyboardShortcut` | `string \| false` | `'b'` | Keyboard shortcut (Cmd/Ctrl + key) |
|
|
96
|
+
|
|
97
|
+
### Root Props
|
|
98
|
+
|
|
99
|
+
| Prop | Type | Default | Description |
|
|
100
|
+
| ----------------- | ------------------------------------------------- | ----------------------------- | --------------------------------- |
|
|
101
|
+
| `ratio` | `{ sidebar: number; main: number }` | `{ sidebar: 1, main: 2 }` | Column ratio (1:2 = 33%/67%) |
|
|
102
|
+
| `minSidebarWidth` | `string` | `'300px'` | Minimum sidebar width (CSS value) |
|
|
103
|
+
| `maxSidebarWidth` | `string` | - | Maximum sidebar width (CSS value) |
|
|
104
|
+
| `gap` | `number \| { mobile?: number; desktop?: number }` | `{ mobile: 16, desktop: 24 }` | Gap between panels in pixels |
|
|
105
|
+
| `breakpoint` | `Breakpoint` | - | Overrides Provider breakpoint |
|
|
106
|
+
| `mobileLayout` | `'stack' \| 'main-only' \| 'sidebar-only'` | `'stack'` | Mobile layout mode |
|
|
107
|
+
| `className` | `string` | - | Custom className for container |
|
|
108
|
+
|
|
109
|
+
### Sidebar/Main Props
|
|
110
|
+
|
|
111
|
+
| Prop | Type | Default | Description |
|
|
112
|
+
| ----------- | -------- | ----------------------------------------- | --------------------- |
|
|
113
|
+
| `label` | `string` | `'Sidebar navigation'` / `'Main content'` | ARIA label for region |
|
|
114
|
+
| `className` | `string` | - | Custom className |
|
|
115
|
+
|
|
116
|
+
### Header/Footer Props
|
|
117
|
+
|
|
118
|
+
| Prop | Type | Default | Description |
|
|
119
|
+
| ----------- | -------- | ------- | ---------------- |
|
|
120
|
+
| `className` | `string` | - | Custom className |
|
|
121
|
+
|
|
122
|
+
### Content Props
|
|
123
|
+
|
|
124
|
+
| Prop | Type | Default | Description |
|
|
125
|
+
| ------------ | --------- | ------- | --------------------------------------------------- |
|
|
126
|
+
| `scrollable` | `boolean` | `true` | Enable ScrollArea wrapper for independent scrolling |
|
|
127
|
+
| `className` | `string` | - | Custom className |
|
|
128
|
+
|
|
129
|
+
### Trigger Props
|
|
130
|
+
|
|
131
|
+
| Prop | Type | Default | Description |
|
|
132
|
+
| --------------- | ------------------- | -------- | ------------------------------------------- |
|
|
133
|
+
| `asChild` | `boolean` | `false` | Use Radix Slot for custom trigger element |
|
|
134
|
+
| `showOnDesktop` | `boolean` | `false` | Show trigger on desktop (hidden by default) |
|
|
135
|
+
| `variant` | `'menu' \| 'panel'` | `'menu'` | Icon style (hamburger vs panel icons) |
|
|
136
|
+
| `className` | `string` | - | Custom className |
|
|
64
137
|
|
|
65
138
|
---
|
|
66
139
|
|
|
67
140
|
## Usage Examples
|
|
68
141
|
|
|
69
|
-
### Basic
|
|
142
|
+
### Basic Two-Column Layout
|
|
70
143
|
|
|
71
144
|
```tsx
|
|
72
|
-
import { SplitLayoutGlass } from '
|
|
73
|
-
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
145
|
+
import { SplitLayoutGlass } from 'shadcn-glass-ui';
|
|
74
146
|
|
|
75
147
|
export function DocsLayout() {
|
|
76
148
|
return (
|
|
77
|
-
<SplitLayoutGlass
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<
|
|
149
|
+
<SplitLayoutGlass.Provider>
|
|
150
|
+
<SplitLayoutGlass.Root>
|
|
151
|
+
<SplitLayoutGlass.Sidebar>
|
|
152
|
+
<SplitLayoutGlass.SidebarHeader>
|
|
81
153
|
<h3 className="text-lg font-semibold">Navigation</h3>
|
|
82
|
-
</
|
|
83
|
-
<
|
|
84
|
-
<
|
|
154
|
+
</SplitLayoutGlass.SidebarHeader>
|
|
155
|
+
<SplitLayoutGlass.SidebarContent>
|
|
156
|
+
<nav className="space-y-2 p-4">
|
|
85
157
|
<a href="#section-1" className="block p-2 rounded hover:bg-muted">
|
|
86
158
|
Section 1
|
|
87
159
|
</a>
|
|
88
160
|
<a href="#section-2" className="block p-2 rounded hover:bg-muted">
|
|
89
161
|
Section 2
|
|
90
162
|
</a>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
/>
|
|
163
|
+
</nav>
|
|
164
|
+
</SplitLayoutGlass.SidebarContent>
|
|
165
|
+
</SplitLayoutGlass.Sidebar>
|
|
166
|
+
<SplitLayoutGlass.Main>
|
|
167
|
+
<SplitLayoutGlass.MainContent>
|
|
168
|
+
<article className="p-6">
|
|
169
|
+
<h1 className="text-3xl font-bold mb-4">Content Title</h1>
|
|
170
|
+
<p>Your main content here...</p>
|
|
171
|
+
</article>
|
|
172
|
+
</SplitLayoutGlass.MainContent>
|
|
173
|
+
</SplitLayoutGlass.Main>
|
|
174
|
+
</SplitLayoutGlass.Root>
|
|
175
|
+
</SplitLayoutGlass.Provider>
|
|
105
176
|
);
|
|
106
177
|
}
|
|
107
178
|
```
|
|
108
179
|
|
|
109
|
-
**Result:** 33%/67% split on desktop (≥1440px), stacked on mobile
|
|
110
|
-
|
|
111
180
|
---
|
|
112
181
|
|
|
113
|
-
###
|
|
182
|
+
### Master-Detail Pattern
|
|
114
183
|
|
|
115
184
|
```tsx
|
|
116
|
-
|
|
117
|
-
ratio={{ sidebar: 1, main: 3 }} // 25% sidebar, 75% main
|
|
118
|
-
minSidebarWidth="250px" // Minimum 250px width
|
|
119
|
-
maxSidebarWidth="400px" // Maximum 400px width
|
|
120
|
-
sidebar={<Filters />}
|
|
121
|
-
main={<ProductGrid />}
|
|
122
|
-
/>
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
**Use case:** E-commerce product pages where filters need constrained width
|
|
185
|
+
import { SplitLayoutGlass, useSplitLayout } from 'shadcn-glass-ui';
|
|
126
186
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
187
|
+
function ItemList() {
|
|
188
|
+
const { selectedKey, setSelectedKey } = useSplitLayout();
|
|
189
|
+
const items = [
|
|
190
|
+
{ id: '1', name: 'Item 1' },
|
|
191
|
+
{ id: '2', name: 'Item 2' },
|
|
192
|
+
{ id: '3', name: 'Item 3' },
|
|
193
|
+
];
|
|
130
194
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
195
|
+
return (
|
|
196
|
+
<div className="space-y-2">
|
|
197
|
+
{items.map((item) => (
|
|
198
|
+
<button
|
|
199
|
+
key={item.id}
|
|
200
|
+
onClick={() => setSelectedKey(item.id)}
|
|
201
|
+
className={cn('w-full p-3 rounded text-left', selectedKey === item.id && 'bg-primary/10')}
|
|
202
|
+
>
|
|
203
|
+
{item.name}
|
|
204
|
+
</button>
|
|
205
|
+
))}
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
134
209
|
|
|
135
|
-
|
|
136
|
-
|
|
210
|
+
function ItemDetail() {
|
|
211
|
+
const { selectedKey } = useSplitLayout();
|
|
212
|
+
if (!selectedKey) return <p>Select an item</p>;
|
|
213
|
+
return <div>Details for item {selectedKey}</div>;
|
|
214
|
+
}
|
|
137
215
|
|
|
138
|
-
|
|
139
|
-
|
|
216
|
+
export function MasterDetailLayout() {
|
|
217
|
+
return (
|
|
218
|
+
<SplitLayoutGlass.Provider defaultSelectedKey="1" urlParamName="item">
|
|
219
|
+
<SplitLayoutGlass.Root ratio={{ sidebar: 1, main: 2 }}>
|
|
220
|
+
<SplitLayoutGlass.Sidebar>
|
|
221
|
+
<SplitLayoutGlass.SidebarHeader>
|
|
222
|
+
<h3>Items</h3>
|
|
223
|
+
<SplitLayoutGlass.Trigger variant="menu" />
|
|
224
|
+
</SplitLayoutGlass.SidebarHeader>
|
|
225
|
+
<SplitLayoutGlass.SidebarContent>
|
|
226
|
+
<ItemList />
|
|
227
|
+
</SplitLayoutGlass.SidebarContent>
|
|
228
|
+
</SplitLayoutGlass.Sidebar>
|
|
229
|
+
<SplitLayoutGlass.Main>
|
|
230
|
+
<SplitLayoutGlass.MainContent>
|
|
231
|
+
<ItemDetail />
|
|
232
|
+
</SplitLayoutGlass.MainContent>
|
|
233
|
+
</SplitLayoutGlass.Main>
|
|
234
|
+
</SplitLayoutGlass.Root>
|
|
235
|
+
</SplitLayoutGlass.Provider>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
140
238
|
```
|
|
141
239
|
|
|
142
240
|
---
|
|
143
241
|
|
|
144
|
-
###
|
|
145
|
-
|
|
146
|
-
#### Stack Layout (Default)
|
|
242
|
+
### Custom Ratio and Width
|
|
147
243
|
|
|
148
244
|
```tsx
|
|
149
|
-
<SplitLayoutGlass
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
245
|
+
<SplitLayoutGlass.Provider>
|
|
246
|
+
<SplitLayoutGlass.Root
|
|
247
|
+
ratio={{ sidebar: 1, main: 3 }} // 25% sidebar, 75% main
|
|
248
|
+
minSidebarWidth="250px"
|
|
249
|
+
maxSidebarWidth="400px"
|
|
250
|
+
>
|
|
251
|
+
{/* ... */}
|
|
252
|
+
</SplitLayoutGlass.Root>
|
|
253
|
+
</SplitLayoutGlass.Provider>
|
|
154
254
|
```
|
|
155
255
|
|
|
156
|
-
|
|
256
|
+
---
|
|
157
257
|
|
|
158
|
-
|
|
159
|
-
<SplitLayoutGlass
|
|
160
|
-
mobileLayout="main-only" // Hide sidebar on mobile
|
|
161
|
-
sidebar={<ComplexFilters />}
|
|
162
|
-
main={<ShoppingCart />}
|
|
163
|
-
/>
|
|
164
|
-
```
|
|
258
|
+
### Intensity Variants
|
|
165
259
|
|
|
166
|
-
|
|
260
|
+
```tsx
|
|
261
|
+
// Subtle blur (8px) - minimal glass effect
|
|
262
|
+
<SplitLayoutGlass.Provider intensity="subtle">
|
|
263
|
+
{/* ... */}
|
|
264
|
+
</SplitLayoutGlass.Provider>
|
|
167
265
|
|
|
168
|
-
|
|
266
|
+
// Medium blur (16px) - standard glass effect (default)
|
|
267
|
+
<SplitLayoutGlass.Provider intensity="medium">
|
|
268
|
+
{/* ... */}
|
|
269
|
+
</SplitLayoutGlass.Provider>
|
|
169
270
|
|
|
170
|
-
|
|
171
|
-
<SplitLayoutGlass
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
main={<DesktopContent />}
|
|
175
|
-
/>
|
|
271
|
+
// Strong blur (24px) - heavy glass effect
|
|
272
|
+
<SplitLayoutGlass.Provider intensity="strong">
|
|
273
|
+
{/* ... */}
|
|
274
|
+
</SplitLayoutGlass.Provider>
|
|
176
275
|
```
|
|
177
276
|
|
|
178
277
|
---
|
|
179
278
|
|
|
180
|
-
###
|
|
181
|
-
|
|
182
|
-
**⚠️ Important:** User must structure content with `ScrollArea` for independent scrolling
|
|
279
|
+
### Mobile Layouts
|
|
183
280
|
|
|
184
281
|
```tsx
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
<div key={item.id}>{item.name}</div>
|
|
200
|
-
))}
|
|
201
|
-
</div>
|
|
202
|
-
</ScrollArea>
|
|
203
|
-
</>
|
|
204
|
-
}
|
|
205
|
-
main={
|
|
206
|
-
<ScrollArea className="h-full">
|
|
207
|
-
<div className="p-6">{/* Long content */}</div>
|
|
208
|
-
</ScrollArea>
|
|
209
|
-
}
|
|
210
|
-
/>
|
|
282
|
+
// Stack Layout (default) - sidebar above main
|
|
283
|
+
<SplitLayoutGlass.Root mobileLayout="stack">
|
|
284
|
+
{/* ... */}
|
|
285
|
+
</SplitLayoutGlass.Root>
|
|
286
|
+
|
|
287
|
+
// Main Only - hide sidebar on mobile
|
|
288
|
+
<SplitLayoutGlass.Root mobileLayout="main-only">
|
|
289
|
+
{/* ... */}
|
|
290
|
+
</SplitLayoutGlass.Root>
|
|
291
|
+
|
|
292
|
+
// Sidebar Only - hide main on mobile
|
|
293
|
+
<SplitLayoutGlass.Root mobileLayout="sidebar-only">
|
|
294
|
+
{/* ... */}
|
|
295
|
+
</SplitLayoutGlass.Root>
|
|
211
296
|
```
|
|
212
297
|
|
|
213
|
-
**Key classes:**
|
|
214
|
-
|
|
215
|
-
- `shrink-0` - Prevents header from shrinking
|
|
216
|
-
- `flex-1 min-h-0` - Allows ScrollArea to fill remaining space
|
|
217
|
-
- `h-full` - Main ScrollArea fills entire height
|
|
218
|
-
|
|
219
298
|
---
|
|
220
299
|
|
|
221
|
-
###
|
|
300
|
+
### With Headers and Footers
|
|
222
301
|
|
|
223
302
|
```tsx
|
|
224
|
-
|
|
225
|
-
<SplitLayoutGlass
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
<
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
/>
|
|
303
|
+
<SplitLayoutGlass.Provider>
|
|
304
|
+
<SplitLayoutGlass.Root>
|
|
305
|
+
<SplitLayoutGlass.Sidebar>
|
|
306
|
+
<SplitLayoutGlass.SidebarHeader>
|
|
307
|
+
<Logo />
|
|
308
|
+
<SplitLayoutGlass.Trigger />
|
|
309
|
+
</SplitLayoutGlass.SidebarHeader>
|
|
310
|
+
<SplitLayoutGlass.SidebarContent scrollable>
|
|
311
|
+
<Navigation />
|
|
312
|
+
</SplitLayoutGlass.SidebarContent>
|
|
313
|
+
<SplitLayoutGlass.SidebarFooter>
|
|
314
|
+
<UserMenu />
|
|
315
|
+
</SplitLayoutGlass.SidebarFooter>
|
|
316
|
+
</SplitLayoutGlass.Sidebar>
|
|
317
|
+
<SplitLayoutGlass.Main>
|
|
318
|
+
<SplitLayoutGlass.MainHeader>
|
|
319
|
+
<Breadcrumbs />
|
|
320
|
+
<SearchBar />
|
|
321
|
+
</SplitLayoutGlass.MainHeader>
|
|
322
|
+
<SplitLayoutGlass.MainContent>
|
|
323
|
+
<PageContent />
|
|
324
|
+
</SplitLayoutGlass.MainContent>
|
|
325
|
+
<SplitLayoutGlass.MainFooter>
|
|
326
|
+
<Pagination />
|
|
327
|
+
</SplitLayoutGlass.MainFooter>
|
|
328
|
+
</SplitLayoutGlass.Main>
|
|
329
|
+
</SplitLayoutGlass.Root>
|
|
330
|
+
</SplitLayoutGlass.Provider>
|
|
233
331
|
```
|
|
234
332
|
|
|
235
333
|
---
|
|
@@ -237,73 +335,64 @@ export function DocsLayout() {
|
|
|
237
335
|
### Different Breakpoint
|
|
238
336
|
|
|
239
337
|
```tsx
|
|
240
|
-
<SplitLayoutGlass
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
338
|
+
<SplitLayoutGlass.Provider breakpoint="lg">
|
|
339
|
+
<SplitLayoutGlass.Root>
|
|
340
|
+
{/* Two-column layout starts at 1024px instead of 768px */}
|
|
341
|
+
</SplitLayoutGlass.Root>
|
|
342
|
+
</SplitLayoutGlass.Provider>
|
|
245
343
|
```
|
|
246
344
|
|
|
247
345
|
**Breakpoint values:**
|
|
248
346
|
|
|
249
347
|
- `sm`: 640px
|
|
250
|
-
- `md`: 768px (default)
|
|
348
|
+
- `md`: 768px (default)
|
|
251
349
|
- `lg`: 1024px
|
|
252
|
-
- `xl`:
|
|
350
|
+
- `xl`: 1280px
|
|
253
351
|
- `2xl`: 1536px
|
|
254
352
|
|
|
255
353
|
---
|
|
256
354
|
|
|
257
|
-
|
|
355
|
+
## Hook: useSplitLayout()
|
|
258
356
|
|
|
259
|
-
|
|
260
|
-
export function CareerStats() {
|
|
261
|
-
const years = [2024, 2023, 2022, 2021, 2020];
|
|
262
|
-
const [selectedYear, setSelectedYear] = useState(2024);
|
|
357
|
+
Access the SplitLayoutGlass context from any child component:
|
|
263
358
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
{/* Detailed stats */}
|
|
302
|
-
</div>
|
|
303
|
-
</ScrollArea>
|
|
304
|
-
}
|
|
305
|
-
/>
|
|
306
|
-
);
|
|
359
|
+
```tsx
|
|
360
|
+
import { useSplitLayout } from 'shadcn-glass-ui';
|
|
361
|
+
|
|
362
|
+
function MyComponent() {
|
|
363
|
+
const {
|
|
364
|
+
// Selection state (master-detail)
|
|
365
|
+
selectedKey, // Current selected key
|
|
366
|
+
setSelectedKey, // Set selected key
|
|
367
|
+
|
|
368
|
+
// Desktop state
|
|
369
|
+
isOpen, // Sidebar open state
|
|
370
|
+
setIsOpen, // Set open state
|
|
371
|
+
|
|
372
|
+
// Mobile state
|
|
373
|
+
isMobileOpen, // Mobile sidebar open
|
|
374
|
+
setMobileOpen, // Set mobile open
|
|
375
|
+
|
|
376
|
+
// Responsive
|
|
377
|
+
isMobile, // Is mobile viewport
|
|
378
|
+
breakpoint, // Current breakpoint
|
|
379
|
+
mobileMode, // Mobile layout mode
|
|
380
|
+
|
|
381
|
+
// Config
|
|
382
|
+
intensity, // Glass intensity
|
|
383
|
+
stickyOffset, // Sticky offset
|
|
384
|
+
|
|
385
|
+
// Actions
|
|
386
|
+
toggle, // Toggle sidebar
|
|
387
|
+
|
|
388
|
+
// shadcn/ui aliases
|
|
389
|
+
state, // 'expanded' | 'collapsed'
|
|
390
|
+
open, // Alias for isOpen
|
|
391
|
+
openMobile, // Alias for isMobileOpen
|
|
392
|
+
toggleSidebar, // Alias for toggle
|
|
393
|
+
} = useSplitLayout();
|
|
394
|
+
|
|
395
|
+
return <button onClick={toggle}>{isOpen ? 'Close Sidebar' : 'Open Sidebar'}</button>;
|
|
307
396
|
}
|
|
308
397
|
```
|
|
309
398
|
|
|
@@ -328,19 +417,6 @@ export function CareerStats() {
|
|
|
328
417
|
- **Sticky:** Disabled (not useful in single column)
|
|
329
418
|
- **Scrolling:** Normal document flow
|
|
330
419
|
|
|
331
|
-
**Testing responsive behavior:**
|
|
332
|
-
|
|
333
|
-
```tsx
|
|
334
|
-
// Default: 2-column layout on tablet+ (≥768px), stacks on mobile (<768px)
|
|
335
|
-
<SplitLayoutGlass sidebar={...} main={...} />
|
|
336
|
-
|
|
337
|
-
// Custom breakpoint for larger screens
|
|
338
|
-
<SplitLayoutGlass breakpoint="xl" sidebar={...} main={...} />
|
|
339
|
-
|
|
340
|
-
// Test in Storybook with viewport addon
|
|
341
|
-
// Or use browser dev tools responsive mode
|
|
342
|
-
```
|
|
343
|
-
|
|
344
420
|
---
|
|
345
421
|
|
|
346
422
|
## Sticky Scroll Behavior
|
|
@@ -370,52 +446,6 @@ export function CareerStats() {
|
|
|
370
446
|
└─────────────────────────────────────┘
|
|
371
447
|
```
|
|
372
448
|
|
|
373
|
-
### Adjusting Sticky Offset
|
|
374
|
-
|
|
375
|
-
```tsx
|
|
376
|
-
// Default (24px)
|
|
377
|
-
<SplitLayoutGlass stickyOffset={24} sidebar={...} main={...} />
|
|
378
|
-
|
|
379
|
-
// Smaller offset (16px) - panels stick closer to top
|
|
380
|
-
<SplitLayoutGlass stickyOffset={16} sidebar={...} main={...} />
|
|
381
|
-
|
|
382
|
-
// Larger offset (48px) - panels stick further from top
|
|
383
|
-
<SplitLayoutGlass stickyOffset={48} sidebar={...} main={...} />
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
---
|
|
387
|
-
|
|
388
|
-
## CSS Grid Architecture
|
|
389
|
-
|
|
390
|
-
### Grid Template Calculation
|
|
391
|
-
|
|
392
|
-
```tsx
|
|
393
|
-
// Without maxSidebarWidth (uses ratio)
|
|
394
|
-
minmax(300px, 1fr) 2fr // Sidebar: min 300px, max 33.3%
|
|
395
|
-
|
|
396
|
-
// With maxSidebarWidth
|
|
397
|
-
minmax(300px, 400px) 2fr // Sidebar: min 300px, max 400px
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
### CSS Variables
|
|
401
|
-
|
|
402
|
-
The component uses CSS variables for dynamic values:
|
|
403
|
-
|
|
404
|
-
```css
|
|
405
|
-
--grid-template: minmax(300px, 1fr) 2fr --sticky-offset: 24px
|
|
406
|
-
--sticky-max-height: calc(100vh - calc(24px * 2));
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
### Tailwind Arbitrary Values
|
|
410
|
-
|
|
411
|
-
Desktop grid is applied via Tailwind arbitrary values:
|
|
412
|
-
|
|
413
|
-
```tsx
|
|
414
|
-
xl:grid-cols-[var(--grid-template)]
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
**Why not direct values?** CSS variables allow dynamic computation based on props.
|
|
418
|
-
|
|
419
449
|
---
|
|
420
450
|
|
|
421
451
|
## Accessibility
|
|
@@ -429,38 +459,21 @@ xl:grid-cols-[var(--grid-template)]
|
|
|
429
459
|
### ARIA Labels
|
|
430
460
|
|
|
431
461
|
```tsx
|
|
432
|
-
<SplitLayoutGlass
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
462
|
+
<SplitLayoutGlass.Sidebar label="Documentation navigation">
|
|
463
|
+
{/* ... */}
|
|
464
|
+
</SplitLayoutGlass.Sidebar>
|
|
465
|
+
|
|
466
|
+
<SplitLayoutGlass.Main label="Article content">
|
|
467
|
+
{/* ... */}
|
|
468
|
+
</SplitLayoutGlass.Main>
|
|
438
469
|
```
|
|
439
470
|
|
|
440
471
|
### Keyboard Navigation
|
|
441
472
|
|
|
473
|
+
- **Cmd/Ctrl + B:** Toggle sidebar (configurable via `keyboardShortcut`)
|
|
442
474
|
- **Tab:** Cycles through focusable elements in document order
|
|
443
475
|
- **Arrow keys:** Scrolls within focused ScrollArea
|
|
444
476
|
- **Space/Page Down:** Scrolls down in focused panel
|
|
445
|
-
- **Shift+Space/Page Up:** Scrolls up in focused panel
|
|
446
|
-
|
|
447
|
-
### Focus Management
|
|
448
|
-
|
|
449
|
-
```tsx
|
|
450
|
-
// Ensure interactive elements have visible focus
|
|
451
|
-
<a href="#section" className="focus:ring-2 focus:ring-primary">
|
|
452
|
-
Section 1
|
|
453
|
-
</a>
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
### Screen Reader Testing
|
|
457
|
-
|
|
458
|
-
Test with:
|
|
459
|
-
|
|
460
|
-
- **NVDA (Windows)** - `NVDA + Down Arrow` to navigate
|
|
461
|
-
- **JAWS (Windows)** - `Insert + Down Arrow`
|
|
462
|
-
- **VoiceOver (macOS)** - `VO + Right Arrow`
|
|
463
|
-
- **TalkBack (Android)** - Swipe right
|
|
464
477
|
|
|
465
478
|
---
|
|
466
479
|
|
|
@@ -468,116 +481,42 @@ Test with:
|
|
|
468
481
|
|
|
469
482
|
### Sidebar too narrow
|
|
470
483
|
|
|
471
|
-
**Problem:** Sidebar shrinks below readable width
|
|
472
|
-
|
|
473
484
|
**Solution:** Increase `minSidebarWidth`
|
|
474
485
|
|
|
475
486
|
```tsx
|
|
476
|
-
<SplitLayoutGlass minSidebarWidth="350px"
|
|
487
|
+
<SplitLayoutGlass.Root minSidebarWidth="350px">
|
|
477
488
|
```
|
|
478
489
|
|
|
479
|
-
---
|
|
480
|
-
|
|
481
490
|
### Sidebar too wide
|
|
482
491
|
|
|
483
|
-
**Problem:** Sidebar takes too much horizontal space
|
|
484
|
-
|
|
485
492
|
**Solution:** Set `maxSidebarWidth` or adjust ratio
|
|
486
493
|
|
|
487
494
|
```tsx
|
|
488
|
-
<SplitLayoutGlass
|
|
489
|
-
maxSidebarWidth="400px" // Hard limit
|
|
490
|
-
ratio={{ sidebar: 1, main: 3 }} // Or change ratio to 25%/75%
|
|
491
|
-
sidebar={...}
|
|
492
|
-
main={...}
|
|
493
|
-
/>
|
|
495
|
+
<SplitLayoutGlass.Root maxSidebarWidth="400px" ratio={{ sidebar: 1, main: 3 }} />
|
|
494
496
|
```
|
|
495
497
|
|
|
496
|
-
---
|
|
497
|
-
|
|
498
498
|
### Content not scrolling
|
|
499
499
|
|
|
500
|
-
**
|
|
501
|
-
|
|
502
|
-
**Solution:** Ensure proper ScrollArea structure
|
|
500
|
+
**Solution:** Ensure `scrollable` prop is true (default) on Content components
|
|
503
501
|
|
|
504
502
|
```tsx
|
|
505
|
-
|
|
506
|
-
<ScrollArea>
|
|
507
|
-
<div>Content</div>
|
|
508
|
-
</ScrollArea>
|
|
509
|
-
|
|
510
|
-
// ✅ Correct - with flex-1 and min-h-0
|
|
511
|
-
<ScrollArea className="flex-1 min-h-0">
|
|
512
|
-
<div>Content</div>
|
|
513
|
-
</ScrollArea>
|
|
503
|
+
<SplitLayoutGlass.SidebarContent scrollable>{/* Long content */}</SplitLayoutGlass.SidebarContent>
|
|
514
504
|
```
|
|
515
505
|
|
|
516
|
-
---
|
|
517
|
-
|
|
518
506
|
### Sticky not working
|
|
519
507
|
|
|
520
|
-
**
|
|
508
|
+
**Possible causes:**
|
|
521
509
|
|
|
522
|
-
**
|
|
523
|
-
|
|
524
|
-
1. **Mobile viewport** - Sticky disabled on mobile (< breakpoint)
|
|
525
|
-
- Test on desktop viewport (≥1440px for default `xl` breakpoint)
|
|
510
|
+
1. **Mobile viewport** - Sticky disabled on mobile
|
|
526
511
|
2. **Parent overflow** - Parent has `overflow: hidden/auto/scroll`
|
|
527
|
-
|
|
528
|
-
3. **Z-index conflict** - Sticky element behind other content
|
|
529
|
-
- Add `z-10` or higher to `sidebarClassName`/`mainClassName`
|
|
530
|
-
|
|
531
|
-
---
|
|
532
|
-
|
|
533
|
-
### Gap not applying
|
|
534
|
-
|
|
535
|
-
**Problem:** Desktop gap doesn't change
|
|
536
|
-
|
|
537
|
-
**Solution:** Gap is applied via inline `<style>` tag and CSS variable
|
|
538
|
-
|
|
539
|
-
```tsx
|
|
540
|
-
// Check that desktop gap is defined
|
|
541
|
-
<SplitLayoutGlass
|
|
542
|
-
gap={{ mobile: 16, desktop: 32 }} // Explicitly set desktop
|
|
543
|
-
sidebar={...}
|
|
544
|
-
main={...}
|
|
545
|
-
/>
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
---
|
|
549
|
-
|
|
550
|
-
## Performance
|
|
551
|
-
|
|
552
|
-
### Bundle Size
|
|
553
|
-
|
|
554
|
-
- **Component:** ~2KB gzipped (includes all features)
|
|
555
|
-
- **Dependencies:** GlassCard (~1KB), ScrollArea (from shadcn/ui)
|
|
556
|
-
- **Total:** ~3KB gzipped
|
|
557
|
-
|
|
558
|
-
### Rendering
|
|
559
|
-
|
|
560
|
-
- **Re-renders:** Only when props change (React.memo not needed)
|
|
561
|
-
- **CSS Grid:** Hardware accelerated, no JavaScript layout calculations
|
|
562
|
-
- **Sticky:** Native CSS `position: sticky`, no scroll listeners
|
|
563
|
-
|
|
564
|
-
### Optimization Tips
|
|
565
|
-
|
|
566
|
-
```tsx
|
|
567
|
-
// Memoize expensive child content
|
|
568
|
-
const sidebar = useMemo(() => (
|
|
569
|
-
<ExpensiveNavigationTree data={data} />
|
|
570
|
-
), [data]);
|
|
571
|
-
|
|
572
|
-
<SplitLayoutGlass sidebar={sidebar} main={...} />
|
|
573
|
-
```
|
|
512
|
+
3. **Z-index conflict** - Add `z-10` to className
|
|
574
513
|
|
|
575
514
|
---
|
|
576
515
|
|
|
577
516
|
## Related Components
|
|
578
517
|
|
|
579
|
-
- **[
|
|
580
|
-
- **[
|
|
518
|
+
- **[SidebarGlass](./SIDEBAR_GLASS.md)** - Traditional collapsible sidebar (shadcn/ui compatible)
|
|
519
|
+
- **[GlassCard](./GLASS_CARD.md)** - Base glass container
|
|
581
520
|
- **[ModalGlass](./MODAL_GLASS.md)** - Modal with compound API pattern
|
|
582
521
|
|
|
583
522
|
---
|
|
@@ -586,10 +525,10 @@ const sidebar = useMemo(() => (
|
|
|
586
525
|
|
|
587
526
|
### v2.2.0 (2025-01-XX)
|
|
588
527
|
|
|
589
|
-
-
|
|
528
|
+
- Initial release with compound API
|
|
529
|
+
- Legacy props API removed
|
|
590
530
|
- 18 visual tests across 3 themes
|
|
591
|
-
- 20 unit tests with 100% coverage
|
|
592
|
-
- 10 Storybook stories with real-world examples
|
|
531
|
+
- 20+ unit tests with 100% coverage
|
|
593
532
|
- Complete documentation
|
|
594
533
|
|
|
595
534
|
---
|