shadcn-glass-ui 2.1.4 → 2.2.0

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 (38) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +13 -13
  3. package/context7.json +30 -4
  4. package/dist/cli/index.cjs +1 -1
  5. package/dist/components.cjs +4 -4
  6. package/dist/components.d.ts +18 -4
  7. package/dist/components.js +1 -1
  8. package/dist/hooks.cjs +2 -2
  9. package/dist/index.cjs +2320 -997
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.js +2284 -996
  12. package/dist/index.js.map +1 -1
  13. package/dist/shadcn-glass-ui.css +1 -1
  14. package/dist/{theme-context-Y98bGvcm.cjs → theme-context-D_cb9KzA.cjs} +2 -2
  15. package/dist/{theme-context-Y98bGvcm.cjs.map → theme-context-D_cb9KzA.cjs.map} +1 -1
  16. package/dist/themes.cjs +1 -1
  17. package/dist/{trust-score-card-glass-2rjz00d_.cjs → trust-score-card-glass-CTsEVRD3.cjs} +178 -35
  18. package/dist/{trust-score-card-glass-2rjz00d_.cjs.map → trust-score-card-glass-CTsEVRD3.cjs.map} +1 -1
  19. package/dist/{trust-score-card-glass-zjkx4OC2.js → trust-score-card-glass-CUStm4o_.js} +86 -15
  20. package/dist/{trust-score-card-glass-zjkx4OC2.js.map → trust-score-card-glass-CUStm4o_.js.map} +1 -1
  21. package/dist/{use-focus-DbpBEuee.cjs → use-focus--Hw2nevi.cjs} +2 -2
  22. package/dist/{use-focus-DbpBEuee.cjs.map → use-focus--Hw2nevi.cjs.map} +1 -1
  23. package/dist/{use-wallpaper-tint-DbawS9zh.cjs → use-wallpaper-tint-B4oMQsXQ.cjs} +2 -2
  24. package/dist/{use-wallpaper-tint-DbawS9zh.cjs.map → use-wallpaper-tint-B4oMQsXQ.cjs.map} +1 -1
  25. package/dist/{utils-XlyXIhuP.cjs → utils-BqeJ4aco.cjs} +2 -2
  26. package/dist/{utils-XlyXIhuP.cjs.map → utils-BqeJ4aco.cjs.map} +1 -1
  27. package/dist/utils.cjs +1 -1
  28. package/docs/AI_USAGE.md +5 -5
  29. package/docs/BEST_PRACTICES.md +1 -1
  30. package/docs/COMPONENTS_CATALOG.md +215 -0
  31. package/docs/EXPORTS_MAP.json +140 -14
  32. package/docs/EXPORTS_STRUCTURE.md +43 -9
  33. package/docs/GETTING_STARTED.md +1 -1
  34. package/docs/REGISTRY_USAGE.md +1 -1
  35. package/docs/api/README.md +1 -1
  36. package/docs/components/SIDEBAR_GLASS.md +555 -0
  37. package/docs/components/SPLIT_LAYOUT_GLASS.md +304 -365
  38. package/package.json +6 -3
@@ -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
- - **Flexible Ratios** - Customizable sidebar-to-main width ratios
20
- - **Semantic HTML** - Uses `<aside>` and `<main>` elements with ARIA labels
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 directory:
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
- import { SplitLayoutGlass } from '@/components/glass/composite/split-layout-glass';
38
- import { ScrollArea } from '@/components/ui/scroll-area'; // Required for scrollable content
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
- | Prop | Type | Default | Description |
46
- | ------------------ | ------------------------------------------------- | ----------------------------- | -------------------------------------------------------- |
47
- | `sidebar` | `ReactNode` | Required | Sidebar content (left column on desktop) |
48
- | `main` | `ReactNode` | Required | Main content (right column on desktop) |
49
- | `ratio` | `{ sidebar: number; main: number }` | `{ sidebar: 1, main: 2 }` | Sidebar to main ratio in fr units (e.g., 1:2 = 33%/67%) |
50
- | `minSidebarWidth` | `string` | `"300px"` | Minimum sidebar width (prevents squeezing) |
51
- | `maxSidebarWidth` | `string` | `undefined` | Maximum sidebar width (optional constraint) |
52
- | `gap` | `number \| { mobile?: number; desktop?: number }` | `{ mobile: 16, desktop: 24 }` | Gap between panels in pixels |
53
- | `breakpoint` | `'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | `'md'` (768px) | Breakpoint for desktop layout (tablet and above) |
54
- | `mobileLayout` | `'stack' \| 'main-only' \| 'sidebar-only'` | `'stack'` | Layout behavior on mobile |
55
- | `stickyOffset` | `number` | `24` | Sticky offset from viewport top in pixels (desktop only) |
56
- | `intensity` | `'subtle' \| 'medium' \| 'strong'` | `'medium'` | Glass blur intensity |
57
- | `sidebarLabel` | `string` | `"Sidebar navigation"` | ARIA label for sidebar region |
58
- | `mainLabel` | `string` | `"Main content"` | ARIA label for main region |
59
- | `className` | `string` | - | Custom className for container |
60
- | `sidebarClassName` | `string` | - | Custom className for sidebar |
61
- | `mainClassName` | `string` | - | Custom className for main |
62
-
63
- All other standard HTML `div` attributes are supported via spread props.
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 Usage
142
+ ### Basic Two-Column Layout
70
143
 
71
144
  ```tsx
72
- import { SplitLayoutGlass } from '@/components/glass/composite/split-layout-glass';
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
- sidebar={
79
- <>
80
- <div className="shrink-0 p-4 border-b border-border">
149
+ <SplitLayoutGlass.Provider>
150
+ <SplitLayoutGlass.Root>
151
+ <SplitLayoutGlass.Sidebar>
152
+ <SplitLayoutGlass.SidebarHeader>
81
153
  <h3 className="text-lg font-semibold">Navigation</h3>
82
- </div>
83
- <ScrollArea className="flex-1 min-h-0">
84
- <div className="p-4 space-y-2">
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
- {/* More navigation items */}
92
- </div>
93
- </ScrollArea>
94
- </>
95
- }
96
- main={
97
- <ScrollArea className="h-full">
98
- <div className="p-6">
99
- <h1 className="text-3xl font-bold mb-4">Content Title</h1>
100
- <p>Your main content here...</p>
101
- </div>
102
- </ScrollArea>
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
- ### Custom Ratio and Width
182
+ ### Master-Detail Pattern
114
183
 
115
184
  ```tsx
116
- <SplitLayoutGlass
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
- ### Intensity Variants
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
- ```tsx
132
- // Subtle blur (8px) - minimal glass effect
133
- <SplitLayoutGlass intensity="subtle" sidebar={...} main={...} />
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
- // Medium blur (16px) - standard glass effect (default)
136
- <SplitLayoutGlass intensity="medium" sidebar={...} main={...} />
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
- // Strong blur (24px) - heavy glass effect
139
- <SplitLayoutGlass intensity="strong" sidebar={...} main={...} />
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
- ### Mobile Layouts
145
-
146
- #### Stack Layout (Default)
242
+ ### Custom Ratio and Width
147
243
 
148
244
  ```tsx
149
- <SplitLayoutGlass
150
- mobileLayout="stack" // Sidebar above main on mobile
151
- sidebar={<Navigation />}
152
- main={<Article />}
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
- #### Main Only
256
+ ---
157
257
 
158
- ```tsx
159
- <SplitLayoutGlass
160
- mobileLayout="main-only" // Hide sidebar on mobile
161
- sidebar={<ComplexFilters />}
162
- main={<ShoppingCart />}
163
- />
164
- ```
258
+ ### Intensity Variants
165
259
 
166
- **Use case:** Shopping cart where filters aren't needed on mobile
260
+ ```tsx
261
+ // Subtle blur (8px) - minimal glass effect
262
+ <SplitLayoutGlass.Provider intensity="subtle">
263
+ {/* ... */}
264
+ </SplitLayoutGlass.Provider>
167
265
 
168
- #### Sidebar Only
266
+ // Medium blur (16px) - standard glass effect (default)
267
+ <SplitLayoutGlass.Provider intensity="medium">
268
+ {/* ... */}
269
+ </SplitLayoutGlass.Provider>
169
270
 
170
- ```tsx
171
- <SplitLayoutGlass
172
- mobileLayout="sidebar-only" // Hide main on mobile
173
- sidebar={<MobileMenu />}
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
- ### With Scrollable Content (Recommended Pattern)
181
-
182
- **⚠️ Important:** User must structure content with `ScrollArea` for independent scrolling
279
+ ### Mobile Layouts
183
280
 
184
281
  ```tsx
185
- <SplitLayoutGlass
186
- sidebar={
187
- <>
188
- {/* Fixed header - won't scroll */}
189
- <div className="shrink-0 p-4 border-b border-border">
190
- <h3 className="font-semibold">Sidebar Header</h3>
191
- <button>Action Button</button>
192
- </div>
193
-
194
- {/* Scrollable content */}
195
- <ScrollArea className="flex-1 min-h-0">
196
- <div className="p-4 space-y-2">
197
- {/* Long list of items */}
198
- {items.map((item) => (
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
- ### Custom Gap
300
+ ### With Headers and Footers
222
301
 
223
302
  ```tsx
224
- // Single value for all breakpoints
225
- <SplitLayoutGlass gap={20} sidebar={...} main={...} />
226
-
227
- // Different gap for mobile/desktop
228
- <SplitLayoutGlass
229
- gap={{ mobile: 12, desktop: 32 }}
230
- sidebar={...}
231
- main={...}
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
- breakpoint="lg" // Two-column layout starts at 1024px instead of 1440px
242
- sidebar={<TOC />}
243
- main={<Article />}
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) - Tablet and above
348
+ - `md`: 768px (default)
251
349
  - `lg`: 1024px
252
- - `xl`: 1440px
350
+ - `xl`: 1280px
253
351
  - `2xl`: 1536px
254
352
 
255
353
  ---
256
354
 
257
- ### Real-World: GitHub Analytics
355
+ ## Hook: useSplitLayout()
258
356
 
259
- ```tsx
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
- return (
265
- <SplitLayoutGlass
266
- ratio={{ sidebar: 1, main: 2 }}
267
- sidebar={
268
- <>
269
- <div className="shrink-0 p-4 border-b">
270
- <h3 className="text-lg font-semibold">Career Summary</h3>
271
- <BadgeGlass variant="success">Active</BadgeGlass>
272
- </div>
273
- <ScrollArea className="flex-1 min-h-0">
274
- <div className="p-3 space-y-2">
275
- {years.map((year) => (
276
- <button
277
- key={year}
278
- onClick={() => setSelectedYear(year)}
279
- className={cn(
280
- 'w-full p-3 rounded-lg text-left transition-colors',
281
- year === selectedYear && 'bg-primary/10'
282
- )}
283
- >
284
- <div className="flex items-center justify-between mb-2">
285
- <span className="font-semibold">{year}</span>
286
- <BadgeGlass variant="default" size="sm">
287
- {getCommits(year)}
288
- </BadgeGlass>
289
- </div>
290
- <ProgressGlass value={getProgress(year)} size="sm" />
291
- </button>
292
- ))}
293
- </div>
294
- </ScrollArea>
295
- </>
296
- }
297
- main={
298
- <ScrollArea className="h-full">
299
- <div className="p-6">
300
- <h1 className="text-2xl font-bold mb-4">{selectedYear} Contribution Details</h1>
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
- sidebarLabel="Documentation navigation" // Announces as "Documentation navigation navigation"
434
- mainLabel="Article content" // Announces as "Article content main"
435
- sidebar={...}
436
- main={...}
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" sidebar={...} main={...} />
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
- **Problem:** Content overflows but doesn't scroll
501
-
502
- **Solution:** Ensure proper ScrollArea structure
500
+ **Solution:** Ensure `scrollable` prop is true (default) on Content components
503
501
 
504
502
  ```tsx
505
- // Wrong - missing flex-1 and min-h-0
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
- **Problem:** Panels don't stick at top
508
+ **Possible causes:**
521
509
 
522
- **Causes & Solutions:**
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
- - Remove overflow from parent containers
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
- - **[GlassCard](./GLASS_CARD.md)** - Base glass container used by SplitLayoutGlass
580
- - **[ScrollArea](https://ui.shadcn.com/docs/components/scroll-area)** - Scrollable area (shadcn/ui)
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
- - Initial release of SplitLayoutGlass
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
  ---