shadcn-glass-ui 2.1.2 → 2.1.4
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 +2 -2
- package/dist/cli/index.cjs +1 -1
- package/dist/components.cjs +4 -4
- package/dist/components.js +1 -1
- package/dist/hooks.cjs +2 -2
- package/dist/index.cjs +1659 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1651 -4
- package/dist/index.js.map +1 -1
- package/dist/r/registry.json +36 -0
- package/dist/r/sidebar-context.json +35 -0
- package/dist/r/sidebar-glass.json +40 -0
- package/dist/r/sidebar-menu.json +39 -0
- package/dist/r/split-layout-accordion.json +24 -0
- package/dist/r/split-layout-context.json +21 -0
- package/dist/r/split-layout-glass.json +25 -0
- package/dist/shadcn-glass-ui.css +1 -1
- package/dist/{theme-context-BHXYJ4RE.cjs → theme-context-Y98bGvcm.cjs} +2 -2
- package/dist/{theme-context-BHXYJ4RE.cjs.map → theme-context-Y98bGvcm.cjs.map} +1 -1
- package/dist/themes.cjs +1 -1
- package/dist/{trust-score-card-glass-CGXmOIfq.cjs → trust-score-card-glass-2rjz00d_.cjs} +47 -5
- package/dist/trust-score-card-glass-2rjz00d_.cjs.map +1 -0
- package/dist/{trust-score-card-glass-L9g0qamo.js → trust-score-card-glass-zjkx4OC2.js} +3 -3
- package/dist/trust-score-card-glass-zjkx4OC2.js.map +1 -0
- package/dist/{use-focus-CeNHOiBa.cjs → use-focus-DbpBEuee.cjs} +2 -2
- package/dist/{use-focus-CeNHOiBa.cjs.map → use-focus-DbpBEuee.cjs.map} +1 -1
- package/dist/{use-wallpaper-tint-Bt2G3g1v.cjs → use-wallpaper-tint-DbawS9zh.cjs} +2 -2
- package/dist/{use-wallpaper-tint-Bt2G3g1v.cjs.map → use-wallpaper-tint-DbawS9zh.cjs.map} +1 -1
- package/dist/{utils-LYxxWvUn.cjs → utils-XlyXIhuP.cjs} +2 -2
- package/dist/{utils-LYxxWvUn.cjs.map → utils-XlyXIhuP.cjs.map} +1 -1
- package/dist/utils.cjs +1 -1
- package/docs/components/SPLIT_LAYOUT_GLASS.md +607 -0
- package/package.json +1 -2
- package/dist/trust-score-card-glass-CGXmOIfq.cjs.map +0 -1
- package/dist/trust-score-card-glass-L9g0qamo.js.map +0 -1
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
# SplitLayoutGlass
|
|
2
|
+
|
|
3
|
+
Responsive two-column layout component with sticky scroll behavior and glassmorphism styling.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`SplitLayoutGlass` provides a modern split-panel layout inspired by MDN, GitHub Docs, and Linear. It
|
|
8
|
+
features independent scrolling in each panel after sticky positioning activates, making it perfect
|
|
9
|
+
for documentation sites, dashboards, and analytics applications.
|
|
10
|
+
|
|
11
|
+
### Key Features
|
|
12
|
+
|
|
13
|
+
- **Sticky Scroll Behavior** - Panels scroll together until reaching sticky offset, then scroll
|
|
14
|
+
independently
|
|
15
|
+
- **Responsive Design** - 2 columns on desktop, configurable mobile layouts
|
|
16
|
+
(stack/main-only/sidebar-only)
|
|
17
|
+
- **CSS Grid with minmax()** - Minimum sidebar width prevents squeezing
|
|
18
|
+
- **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
|
|
21
|
+
- **Theme Support** - Works with all 3 themes (glass, light, aurora)
|
|
22
|
+
|
|
23
|
+
### Browser Compatibility
|
|
24
|
+
|
|
25
|
+
- Chrome 89+ (CSS Grid minmax support)
|
|
26
|
+
- Firefox 87+
|
|
27
|
+
- Safari 14.1+
|
|
28
|
+
- Edge 89+
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
The component is part of the Glass UI library. Import it from the composite components directory:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { SplitLayoutGlass } from '@/components/glass/composite/split-layout-glass';
|
|
38
|
+
import { ScrollArea } from '@/components/ui/scroll-area'; // Required for scrollable content
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Props API
|
|
44
|
+
|
|
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.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Usage Examples
|
|
68
|
+
|
|
69
|
+
### Basic Usage
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { SplitLayoutGlass } from '@/components/glass/composite/split-layout-glass';
|
|
73
|
+
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
74
|
+
|
|
75
|
+
export function DocsLayout() {
|
|
76
|
+
return (
|
|
77
|
+
<SplitLayoutGlass
|
|
78
|
+
sidebar={
|
|
79
|
+
<>
|
|
80
|
+
<div className="shrink-0 p-4 border-b border-border">
|
|
81
|
+
<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">
|
|
85
|
+
<a href="#section-1" className="block p-2 rounded hover:bg-muted">
|
|
86
|
+
Section 1
|
|
87
|
+
</a>
|
|
88
|
+
<a href="#section-2" className="block p-2 rounded hover:bg-muted">
|
|
89
|
+
Section 2
|
|
90
|
+
</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
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Result:** 33%/67% split on desktop (≥1440px), stacked on mobile
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### Custom Ratio and Width
|
|
114
|
+
|
|
115
|
+
```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
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### Intensity Variants
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
// Subtle blur (8px) - minimal glass effect
|
|
133
|
+
<SplitLayoutGlass intensity="subtle" sidebar={...} main={...} />
|
|
134
|
+
|
|
135
|
+
// Medium blur (16px) - standard glass effect (default)
|
|
136
|
+
<SplitLayoutGlass intensity="medium" sidebar={...} main={...} />
|
|
137
|
+
|
|
138
|
+
// Strong blur (24px) - heavy glass effect
|
|
139
|
+
<SplitLayoutGlass intensity="strong" sidebar={...} main={...} />
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
### Mobile Layouts
|
|
145
|
+
|
|
146
|
+
#### Stack Layout (Default)
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
<SplitLayoutGlass
|
|
150
|
+
mobileLayout="stack" // Sidebar above main on mobile
|
|
151
|
+
sidebar={<Navigation />}
|
|
152
|
+
main={<Article />}
|
|
153
|
+
/>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### Main Only
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
<SplitLayoutGlass
|
|
160
|
+
mobileLayout="main-only" // Hide sidebar on mobile
|
|
161
|
+
sidebar={<ComplexFilters />}
|
|
162
|
+
main={<ShoppingCart />}
|
|
163
|
+
/>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Use case:** Shopping cart where filters aren't needed on mobile
|
|
167
|
+
|
|
168
|
+
#### Sidebar Only
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
<SplitLayoutGlass
|
|
172
|
+
mobileLayout="sidebar-only" // Hide main on mobile
|
|
173
|
+
sidebar={<MobileMenu />}
|
|
174
|
+
main={<DesktopContent />}
|
|
175
|
+
/>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### With Scrollable Content (Recommended Pattern)
|
|
181
|
+
|
|
182
|
+
**⚠️ Important:** User must structure content with `ScrollArea` for independent scrolling
|
|
183
|
+
|
|
184
|
+
```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
|
+
/>
|
|
211
|
+
```
|
|
212
|
+
|
|
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
|
+
---
|
|
220
|
+
|
|
221
|
+
### Custom Gap
|
|
222
|
+
|
|
223
|
+
```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
|
+
/>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### Different Breakpoint
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
<SplitLayoutGlass
|
|
241
|
+
breakpoint="lg" // Two-column layout starts at 1024px instead of 1440px
|
|
242
|
+
sidebar={<TOC />}
|
|
243
|
+
main={<Article />}
|
|
244
|
+
/>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Breakpoint values:**
|
|
248
|
+
|
|
249
|
+
- `sm`: 640px
|
|
250
|
+
- `md`: 768px (default) - Tablet and above
|
|
251
|
+
- `lg`: 1024px
|
|
252
|
+
- `xl`: 1440px
|
|
253
|
+
- `2xl`: 1536px
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
### Real-World: GitHub Analytics
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
export function CareerStats() {
|
|
261
|
+
const years = [2024, 2023, 2022, 2021, 2020];
|
|
262
|
+
const [selectedYear, setSelectedYear] = useState(2024);
|
|
263
|
+
|
|
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
|
+
);
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Responsive Behavior
|
|
313
|
+
|
|
314
|
+
### Desktop (≥ breakpoint)
|
|
315
|
+
|
|
316
|
+
- **Layout:** Two columns side by side
|
|
317
|
+
- **Sticky:** Both panels become sticky after scrolling past offset
|
|
318
|
+
- **Max Height:** `calc(100vh - offset * 2)` constrains panel height
|
|
319
|
+
- **Scrolling:** Each panel scrolls independently within max-height
|
|
320
|
+
- **Grid:** Uses CSS Grid with `minmax()` for sidebar width
|
|
321
|
+
|
|
322
|
+
### Mobile (< breakpoint)
|
|
323
|
+
|
|
324
|
+
- **Layout:** Determined by `mobileLayout` prop
|
|
325
|
+
- `stack`: Single column, sidebar above main
|
|
326
|
+
- `main-only`: Hide sidebar, show only main
|
|
327
|
+
- `sidebar-only`: Hide main, show only sidebar
|
|
328
|
+
- **Sticky:** Disabled (not useful in single column)
|
|
329
|
+
- **Scrolling:** Normal document flow
|
|
330
|
+
|
|
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
|
+
---
|
|
345
|
+
|
|
346
|
+
## Sticky Scroll Behavior
|
|
347
|
+
|
|
348
|
+
### How It Works
|
|
349
|
+
|
|
350
|
+
1. **Initial scroll:** Both panels scroll together with page
|
|
351
|
+
2. **Reaching offset:** Panels become `position: sticky` at `top: {stickyOffset}px`
|
|
352
|
+
3. **Independent scroll:** Each panel scrolls independently within `max-height` constraint
|
|
353
|
+
4. **No sync:** Panels do NOT scroll together to end of document
|
|
354
|
+
|
|
355
|
+
### Visual Diagram
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
┌─────────────────────────────────────┐
|
|
359
|
+
│ Browser Viewport │
|
|
360
|
+
│ ┌─────────────────────────────┐ │
|
|
361
|
+
│ │ Offset (24px) │ │
|
|
362
|
+
│ ├─────────────┬───────────────┤ │ ← Sticky point
|
|
363
|
+
│ │ Sidebar │ Main │ │
|
|
364
|
+
│ │ (sticky) │ (sticky) │ │
|
|
365
|
+
│ │ │ │ │
|
|
366
|
+
│ │ [scroll] │ [scroll] │ │
|
|
367
|
+
│ │ ↕ │ ↕ │ │
|
|
368
|
+
│ └─────────────┴───────────────┘ │
|
|
369
|
+
│ Offset (24px) │
|
|
370
|
+
└─────────────────────────────────────┘
|
|
371
|
+
```
|
|
372
|
+
|
|
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
|
+
---
|
|
420
|
+
|
|
421
|
+
## Accessibility
|
|
422
|
+
|
|
423
|
+
### Semantic HTML
|
|
424
|
+
|
|
425
|
+
- **`<aside>`** for sidebar - Navigation landmark
|
|
426
|
+
- **`<main>`** for main content - Main landmark
|
|
427
|
+
- Screen readers announce these regions automatically
|
|
428
|
+
|
|
429
|
+
### ARIA Labels
|
|
430
|
+
|
|
431
|
+
```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
|
+
/>
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Keyboard Navigation
|
|
441
|
+
|
|
442
|
+
- **Tab:** Cycles through focusable elements in document order
|
|
443
|
+
- **Arrow keys:** Scrolls within focused ScrollArea
|
|
444
|
+
- **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
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## Troubleshooting
|
|
468
|
+
|
|
469
|
+
### Sidebar too narrow
|
|
470
|
+
|
|
471
|
+
**Problem:** Sidebar shrinks below readable width
|
|
472
|
+
|
|
473
|
+
**Solution:** Increase `minSidebarWidth`
|
|
474
|
+
|
|
475
|
+
```tsx
|
|
476
|
+
<SplitLayoutGlass minSidebarWidth="350px" sidebar={...} main={...} />
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
### Sidebar too wide
|
|
482
|
+
|
|
483
|
+
**Problem:** Sidebar takes too much horizontal space
|
|
484
|
+
|
|
485
|
+
**Solution:** Set `maxSidebarWidth` or adjust ratio
|
|
486
|
+
|
|
487
|
+
```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
|
+
/>
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
### Content not scrolling
|
|
499
|
+
|
|
500
|
+
**Problem:** Content overflows but doesn't scroll
|
|
501
|
+
|
|
502
|
+
**Solution:** Ensure proper ScrollArea structure
|
|
503
|
+
|
|
504
|
+
```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>
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
### Sticky not working
|
|
519
|
+
|
|
520
|
+
**Problem:** Panels don't stick at top
|
|
521
|
+
|
|
522
|
+
**Causes & Solutions:**
|
|
523
|
+
|
|
524
|
+
1. **Mobile viewport** - Sticky disabled on mobile (< breakpoint)
|
|
525
|
+
- Test on desktop viewport (≥1440px for default `xl` breakpoint)
|
|
526
|
+
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
|
+
```
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
## Related Components
|
|
578
|
+
|
|
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)
|
|
581
|
+
- **[ModalGlass](./MODAL_GLASS.md)** - Modal with compound API pattern
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
## Changelog
|
|
586
|
+
|
|
587
|
+
### v2.2.0 (2025-01-XX)
|
|
588
|
+
|
|
589
|
+
- ✨ Initial release of SplitLayoutGlass
|
|
590
|
+
- 18 visual tests across 3 themes
|
|
591
|
+
- 20 unit tests with 100% coverage
|
|
592
|
+
- 10 Storybook stories with real-world examples
|
|
593
|
+
- Complete documentation
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
## Support
|
|
598
|
+
|
|
599
|
+
- **GitHub Issues:** [Report bugs](https://github.com/yhooi2/shadcn-glass-ui-library/issues)
|
|
600
|
+
- **Storybook:** [View live examples](https://yhooi2.github.io/shadcn-glass-ui-library/)
|
|
601
|
+
- **Documentation:** [CLAUDE.md](../../CLAUDE.md) for AI assistants
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## License
|
|
606
|
+
|
|
607
|
+
MIT License - Part of [shadcn-glass-ui-library](https://github.com/yhooi2/shadcn-glass-ui-library)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shadcn-glass-ui",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "2.1.
|
|
4
|
+
"version": "2.1.4",
|
|
5
5
|
"description": "Glassmorphism UI library for React - AI-friendly with 57 components, strict TypeScript, and comprehensive docs",
|
|
6
6
|
"author": "Yhooi2",
|
|
7
7
|
"license": "MIT",
|
|
@@ -188,7 +188,6 @@
|
|
|
188
188
|
"@storybook/addon-docs": "^10.1.0",
|
|
189
189
|
"@storybook/addon-links": "^10.1.4",
|
|
190
190
|
"@storybook/addon-mcp": "^0.1.3",
|
|
191
|
-
"@storybook/addon-onboarding": "^10.1.0",
|
|
192
191
|
"@storybook/addon-vitest": "^10.1.0",
|
|
193
192
|
"@storybook/react-vite": "^10.1.0",
|
|
194
193
|
"@testing-library/react": "^16.3.0",
|