@neynar/ui 1.0.0 → 1.0.2
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 +1 -1
- package/context7.json +17 -0
- package/llm/components/accordion.llm.md +205 -0
- package/llm/components/alert-dialog.llm.md +289 -0
- package/llm/components/alert.llm.md +310 -0
- package/llm/components/aspect-ratio.llm.md +110 -0
- package/llm/components/avatar.llm.md +282 -0
- package/llm/components/badge.llm.md +185 -0
- package/llm/components/blockquote.llm.md +86 -0
- package/llm/components/breadcrumb.llm.md +245 -0
- package/llm/components/button-group.llm.md +248 -0
- package/llm/components/button.llm.md +247 -0
- package/llm/components/calendar.llm.md +252 -0
- package/llm/components/card.llm.md +356 -0
- package/llm/components/carousel.llm.md +281 -0
- package/llm/components/chart.llm.md +278 -0
- package/llm/components/checkbox.llm.md +234 -0
- package/llm/components/code.llm.md +75 -0
- package/llm/components/collapsible.llm.md +271 -0
- package/llm/components/color-mode.llm.md +196 -0
- package/llm/components/combobox.llm.md +346 -0
- package/llm/components/command.llm.md +353 -0
- package/llm/components/context-menu.llm.md +368 -0
- package/llm/components/dialog.llm.md +283 -0
- package/llm/components/drawer.llm.md +326 -0
- package/llm/components/dropdown-menu.llm.md +404 -0
- package/llm/components/empty.llm.md +282 -0
- package/llm/components/field.llm.md +303 -0
- package/llm/components/first-light.llm.md +129 -0
- package/llm/components/hover-card.llm.md +278 -0
- package/llm/components/input-group.llm.md +334 -0
- package/llm/components/input-otp.llm.md +270 -0
- package/llm/components/input.llm.md +197 -0
- package/llm/components/item.llm.md +347 -0
- package/llm/components/kbd.llm.md +221 -0
- package/llm/components/label.llm.md +219 -0
- package/llm/components/menubar.llm.md +378 -0
- package/llm/components/navigation-menu.llm.md +320 -0
- package/llm/components/pagination.llm.md +337 -0
- package/llm/components/popover.llm.md +278 -0
- package/llm/components/progress.llm.md +259 -0
- package/llm/components/radio-group.llm.md +269 -0
- package/llm/components/resizable.llm.md +222 -0
- package/llm/components/scroll-area.llm.md +290 -0
- package/llm/components/select.llm.md +338 -0
- package/llm/components/separator.llm.md +129 -0
- package/llm/components/sheet.llm.md +275 -0
- package/llm/components/sidebar.llm.md +528 -0
- package/llm/components/skeleton.llm.md +140 -0
- package/llm/components/slider.llm.md +213 -0
- package/llm/components/sonner.llm.md +299 -0
- package/llm/components/spinner.llm.md +187 -0
- package/llm/components/switch.llm.md +258 -0
- package/llm/components/table.llm.md +334 -0
- package/llm/components/tabs.llm.md +245 -0
- package/llm/components/text.llm.md +108 -0
- package/llm/components/textarea.llm.md +236 -0
- package/llm/components/title.llm.md +88 -0
- package/llm/components/toggle-group.llm.md +228 -0
- package/llm/components/toggle.llm.md +235 -0
- package/llm/components/tooltip.llm.md +191 -0
- package/llm/contributing.llm.md +273 -0
- package/llm/hooks.llm.md +91 -0
- package/llm/index.llm.md +178 -0
- package/llm/theming.llm.md +381 -0
- package/llm/utilities.llm.md +97 -0
- package/llms-full.txt +15995 -0
- package/llms.txt +182 -0
- package/package.json +6 -2
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Resizable
|
|
2
|
+
|
|
3
|
+
Create resizable panel layouts with draggable handles for complex dashboard, editor, and split-view interfaces.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import {
|
|
9
|
+
ResizablePanelGroup,
|
|
10
|
+
ResizablePanel,
|
|
11
|
+
ResizableHandle,
|
|
12
|
+
} from "@neynar/ui/resizable"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Anatomy
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
<ResizablePanelGroup orientation="horizontal">
|
|
19
|
+
<ResizablePanel defaultSize={50}>
|
|
20
|
+
Content 1
|
|
21
|
+
</ResizablePanel>
|
|
22
|
+
<ResizableHandle />
|
|
23
|
+
<ResizablePanel defaultSize={50}>
|
|
24
|
+
Content 2
|
|
25
|
+
</ResizablePanel>
|
|
26
|
+
</ResizablePanelGroup>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Components
|
|
30
|
+
|
|
31
|
+
| Component | Description |
|
|
32
|
+
|-----------|-------------|
|
|
33
|
+
| ResizablePanelGroup | Root container that manages layout orientation and panel resizing |
|
|
34
|
+
| ResizablePanel | Individual resizable panel with size constraints and collapse behavior |
|
|
35
|
+
| ResizableHandle | Draggable separator between panels with optional visible grip |
|
|
36
|
+
|
|
37
|
+
## Props
|
|
38
|
+
|
|
39
|
+
### ResizablePanelGroup
|
|
40
|
+
|
|
41
|
+
| Prop | Type | Default | Description |
|
|
42
|
+
|------|------|---------|-------------|
|
|
43
|
+
| orientation | "horizontal" \| "vertical" | "horizontal" | Layout direction for panels |
|
|
44
|
+
| id | string | - | Unique ID for localStorage persistence of panel sizes |
|
|
45
|
+
| defaultLayout | number[] | - | Initial panel sizes as percentages |
|
|
46
|
+
| onLayoutChange | (layout: Record\<string, number\>) => void | - | Called when panel sizes change |
|
|
47
|
+
| disabled | boolean | false | Disable all resizing |
|
|
48
|
+
| className | string | - | Additional CSS classes |
|
|
49
|
+
|
|
50
|
+
### ResizablePanel
|
|
51
|
+
|
|
52
|
+
All panels inherit props from react-resizable-panels Panel component.
|
|
53
|
+
|
|
54
|
+
| Prop | Type | Default | Description |
|
|
55
|
+
|------|------|---------|-------------|
|
|
56
|
+
| id | string | - | Panel identifier (required for persistence) |
|
|
57
|
+
| defaultSize | number | - | Initial size as percentage of parent group |
|
|
58
|
+
| minSize | number | 0 | Minimum size as percentage |
|
|
59
|
+
| maxSize | number | 100 | Maximum size as percentage |
|
|
60
|
+
| collapsible | boolean | false | Allow panel to collapse below minSize |
|
|
61
|
+
| collapsedSize | number | 0 | Size when collapsed |
|
|
62
|
+
| onResize | (size: { asPercentage: number; inPixels: number }) => void | - | Called when panel is resized |
|
|
63
|
+
| className | string | - | Additional CSS classes |
|
|
64
|
+
|
|
65
|
+
### ResizableHandle
|
|
66
|
+
|
|
67
|
+
| Prop | Type | Default | Description |
|
|
68
|
+
|------|------|---------|-------------|
|
|
69
|
+
| withHandle | boolean | false | Display visible grip indicator |
|
|
70
|
+
| className | string | - | Additional CSS classes |
|
|
71
|
+
|
|
72
|
+
## Data Attributes
|
|
73
|
+
|
|
74
|
+
| Attribute | When Present | Applied To |
|
|
75
|
+
|-----------|--------------|------------|
|
|
76
|
+
| data-slot="resizable-panel-group" | Always | ResizablePanelGroup |
|
|
77
|
+
| data-slot="resizable-panel" | Always | ResizablePanel |
|
|
78
|
+
| data-slot="resizable-handle" | Always | ResizableHandle |
|
|
79
|
+
| aria-orientation | Inherits from parent group | All components |
|
|
80
|
+
|
|
81
|
+
## Examples
|
|
82
|
+
|
|
83
|
+
### Basic Horizontal Layout
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<ResizablePanelGroup orientation="horizontal">
|
|
87
|
+
<ResizablePanel defaultSize={40}>
|
|
88
|
+
<div className="p-4">Sidebar</div>
|
|
89
|
+
</ResizablePanel>
|
|
90
|
+
<ResizableHandle />
|
|
91
|
+
<ResizablePanel defaultSize={60}>
|
|
92
|
+
<div className="p-4">Main Content</div>
|
|
93
|
+
</ResizablePanel>
|
|
94
|
+
</ResizablePanelGroup>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Vertical Split with Handle
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
<ResizablePanelGroup orientation="vertical">
|
|
101
|
+
<ResizablePanel defaultSize={70}>
|
|
102
|
+
<div className="p-4">Editor</div>
|
|
103
|
+
</ResizablePanel>
|
|
104
|
+
<ResizableHandle withHandle />
|
|
105
|
+
<ResizablePanel defaultSize={30}>
|
|
106
|
+
<div className="p-4">Terminal Output</div>
|
|
107
|
+
</ResizablePanel>
|
|
108
|
+
</ResizablePanelGroup>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Three Panels with Constraints
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
<ResizablePanelGroup orientation="horizontal">
|
|
115
|
+
<ResizablePanel defaultSize={25} minSize={15} maxSize={40}>
|
|
116
|
+
<div className="p-4">Navigation</div>
|
|
117
|
+
</ResizablePanel>
|
|
118
|
+
<ResizableHandle withHandle />
|
|
119
|
+
<ResizablePanel defaultSize={50} minSize={30}>
|
|
120
|
+
<div className="p-4">Content</div>
|
|
121
|
+
</ResizablePanel>
|
|
122
|
+
<ResizableHandle withHandle />
|
|
123
|
+
<ResizablePanel defaultSize={25} minSize={15} maxSize={40}>
|
|
124
|
+
<div className="p-4">Inspector</div>
|
|
125
|
+
</ResizablePanel>
|
|
126
|
+
</ResizablePanelGroup>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Nested Layouts (Editor + Terminal)
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
<ResizablePanelGroup orientation="horizontal">
|
|
133
|
+
<ResizablePanel defaultSize={20} minSize={15}>
|
|
134
|
+
<div className="p-4">File Explorer</div>
|
|
135
|
+
</ResizablePanel>
|
|
136
|
+
<ResizableHandle withHandle />
|
|
137
|
+
<ResizablePanel defaultSize={80}>
|
|
138
|
+
<ResizablePanelGroup orientation="vertical">
|
|
139
|
+
<ResizablePanel defaultSize={70}>
|
|
140
|
+
<div className="p-4">Code Editor</div>
|
|
141
|
+
</ResizablePanel>
|
|
142
|
+
<ResizableHandle withHandle />
|
|
143
|
+
<ResizablePanel defaultSize={30} minSize={20}>
|
|
144
|
+
<div className="p-4">Terminal</div>
|
|
145
|
+
</ResizablePanel>
|
|
146
|
+
</ResizablePanelGroup>
|
|
147
|
+
</ResizablePanel>
|
|
148
|
+
</ResizablePanelGroup>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Persisted Layout
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
function Dashboard() {
|
|
155
|
+
return (
|
|
156
|
+
<ResizablePanelGroup
|
|
157
|
+
id="dashboard-layout"
|
|
158
|
+
orientation="horizontal"
|
|
159
|
+
>
|
|
160
|
+
<ResizablePanel id="sidebar" defaultSize={25}>
|
|
161
|
+
<div className="p-4">Sidebar</div>
|
|
162
|
+
</ResizablePanel>
|
|
163
|
+
<ResizableHandle />
|
|
164
|
+
<ResizablePanel id="main" defaultSize={75}>
|
|
165
|
+
<div className="p-4">Main Content</div>
|
|
166
|
+
</ResizablePanel>
|
|
167
|
+
</ResizablePanelGroup>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Controlled with Callbacks
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
function MonitoredPanels() {
|
|
176
|
+
const [sizes, setSizes] = useState({ left: 30, right: 70 })
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<ResizablePanelGroup
|
|
180
|
+
orientation="horizontal"
|
|
181
|
+
onLayoutChange={(layout) => {
|
|
182
|
+
setSizes({
|
|
183
|
+
left: layout.left || 0,
|
|
184
|
+
right: layout.right || 0
|
|
185
|
+
})
|
|
186
|
+
}}
|
|
187
|
+
>
|
|
188
|
+
<ResizablePanel id="left" defaultSize={30}>
|
|
189
|
+
<div className="p-4">Left: {sizes.left.toFixed(1)}%</div>
|
|
190
|
+
</ResizablePanel>
|
|
191
|
+
<ResizableHandle withHandle />
|
|
192
|
+
<ResizablePanel id="right">
|
|
193
|
+
<div className="p-4">Right: {sizes.right.toFixed(1)}%</div>
|
|
194
|
+
</ResizablePanel>
|
|
195
|
+
</ResizablePanelGroup>
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Keyboard
|
|
201
|
+
|
|
202
|
+
| Key | Action |
|
|
203
|
+
|-----|--------|
|
|
204
|
+
| Tab | Focus next handle |
|
|
205
|
+
| Shift + Tab | Focus previous handle |
|
|
206
|
+
| Arrow Keys | Resize panels (direction depends on orientation) |
|
|
207
|
+
| Enter | Expand collapsed panel |
|
|
208
|
+
| Home | Resize to minimum |
|
|
209
|
+
| End | Resize to maximum |
|
|
210
|
+
|
|
211
|
+
## Accessibility
|
|
212
|
+
|
|
213
|
+
- Resize handles are keyboard-navigable with proper focus states
|
|
214
|
+
- ARIA attributes automatically reflect orientation and state
|
|
215
|
+
- Supports keyboard resizing via arrow keys
|
|
216
|
+
- Focus ring visible on keyboard navigation
|
|
217
|
+
|
|
218
|
+
## Related
|
|
219
|
+
|
|
220
|
+
- [Collapsible](./collapsible.llm.md) - For simpler expand/collapse UI
|
|
221
|
+
- [Tabs](./tabs.llm.md) - Alternative to split views
|
|
222
|
+
- [Card](./card.llm.md) - Static panel container
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# ScrollArea Component
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
The ScrollArea component provides a customizable scrollable container with styled scrollbars, built on Base UI's ScrollArea primitive. It offers automatic overflow detection, custom scrollbar styling, and support for both vertical and horizontal scrolling.
|
|
5
|
+
|
|
6
|
+
## Component Exports
|
|
7
|
+
|
|
8
|
+
### ScrollArea
|
|
9
|
+
Main scrollable container component with custom-styled scrollbars.
|
|
10
|
+
|
|
11
|
+
**Props**: `ScrollAreaProps` (extends `ScrollAreaPrimitive.Root.Props`)
|
|
12
|
+
- `className?: string` - Additional CSS classes to apply
|
|
13
|
+
- `children: React.ReactNode` - Content to be rendered within the scrollable area
|
|
14
|
+
- All Base UI ScrollArea.Root props
|
|
15
|
+
|
|
16
|
+
**Key Features**:
|
|
17
|
+
- Automatic overflow detection
|
|
18
|
+
- Custom-styled scrollbars with border-based color
|
|
19
|
+
- Focus ring on keyboard navigation
|
|
20
|
+
- Inherits border radius from parent styling
|
|
21
|
+
- Smooth transitions for focus states
|
|
22
|
+
|
|
23
|
+
### ScrollBar
|
|
24
|
+
Custom scrollbar component that can be used independently or within ScrollArea.
|
|
25
|
+
|
|
26
|
+
**Props**: `ScrollBarProps` (extends `ScrollAreaPrimitive.Scrollbar.Props`)
|
|
27
|
+
- `className?: string` - Additional CSS classes to apply
|
|
28
|
+
- `orientation?: "vertical" | "horizontal"` - Scrollbar orientation (default: "vertical")
|
|
29
|
+
- All Base UI ScrollArea.Scrollbar props
|
|
30
|
+
|
|
31
|
+
**Key Features**:
|
|
32
|
+
- Draggable thumb for scroll control
|
|
33
|
+
- Adaptive sizing based on orientation (2.5 units height/width)
|
|
34
|
+
- Touch-optimized for mobile devices
|
|
35
|
+
- Rounded scrollbar thumb
|
|
36
|
+
|
|
37
|
+
## Usage Examples
|
|
38
|
+
|
|
39
|
+
### Basic Vertical Scrolling
|
|
40
|
+
```tsx
|
|
41
|
+
import { ScrollArea } from "@neynar/ui/scroll-area"
|
|
42
|
+
|
|
43
|
+
function ContentList() {
|
|
44
|
+
return (
|
|
45
|
+
<ScrollArea className="h-72 w-48 rounded-md border p-4">
|
|
46
|
+
<div className="space-y-4">
|
|
47
|
+
{items.map((item) => (
|
|
48
|
+
<div key={item.id}>{item.content}</div>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
</ScrollArea>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Horizontal Scrolling
|
|
57
|
+
```tsx
|
|
58
|
+
import { ScrollArea } from "@neynar/ui/scroll-area"
|
|
59
|
+
|
|
60
|
+
function HorizontalGallery() {
|
|
61
|
+
return (
|
|
62
|
+
<ScrollArea className="w-96 whitespace-nowrap rounded-md border">
|
|
63
|
+
<div className="flex gap-4 p-4">
|
|
64
|
+
{images.map((img) => (
|
|
65
|
+
<img key={img.id} src={img.url} alt={img.alt} className="h-40 w-auto" />
|
|
66
|
+
))}
|
|
67
|
+
</div>
|
|
68
|
+
</ScrollArea>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Chat-Style Scrolling
|
|
74
|
+
```tsx
|
|
75
|
+
import { ScrollArea } from "@neynar/ui/scroll-area"
|
|
76
|
+
|
|
77
|
+
function ChatMessages() {
|
|
78
|
+
return (
|
|
79
|
+
<ScrollArea className="h-[600px] rounded-lg border p-4">
|
|
80
|
+
<div className="flex flex-col gap-2">
|
|
81
|
+
{messages.map((message) => (
|
|
82
|
+
<div key={message.id} className="rounded-lg bg-muted p-3">
|
|
83
|
+
<p className="text-sm font-medium">{message.author}</p>
|
|
84
|
+
<p className="text-sm">{message.content}</p>
|
|
85
|
+
</div>
|
|
86
|
+
))}
|
|
87
|
+
</div>
|
|
88
|
+
</ScrollArea>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Sidebar Navigation
|
|
94
|
+
```tsx
|
|
95
|
+
import { ScrollArea } from "@neynar/ui/scroll-area"
|
|
96
|
+
|
|
97
|
+
function Sidebar() {
|
|
98
|
+
return (
|
|
99
|
+
<aside className="w-64 border-r">
|
|
100
|
+
<ScrollArea className="h-screen p-4">
|
|
101
|
+
<nav className="space-y-2">
|
|
102
|
+
{navItems.map((item) => (
|
|
103
|
+
<a
|
|
104
|
+
key={item.href}
|
|
105
|
+
href={item.href}
|
|
106
|
+
className="block rounded-md px-3 py-2 hover:bg-accent"
|
|
107
|
+
>
|
|
108
|
+
{item.label}
|
|
109
|
+
</a>
|
|
110
|
+
))}
|
|
111
|
+
</nav>
|
|
112
|
+
</ScrollArea>
|
|
113
|
+
</aside>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Custom Scrollbar Configuration
|
|
119
|
+
```tsx
|
|
120
|
+
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area"
|
|
121
|
+
import { ScrollBar } from "@neynar/ui/scroll-area"
|
|
122
|
+
|
|
123
|
+
function CustomScrollArea({ children }: { children: React.ReactNode }) {
|
|
124
|
+
return (
|
|
125
|
+
<ScrollAreaPrimitive.Root className="relative h-96 rounded-md border">
|
|
126
|
+
<ScrollAreaPrimitive.Viewport className="size-full p-4">
|
|
127
|
+
{children}
|
|
128
|
+
</ScrollAreaPrimitive.Viewport>
|
|
129
|
+
<ScrollBar orientation="horizontal" className="bg-muted/50" />
|
|
130
|
+
<ScrollAreaPrimitive.Corner />
|
|
131
|
+
</ScrollAreaPrimitive.Root>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Component Structure
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
ScrollArea (Root container)
|
|
140
|
+
├── Viewport (content wrapper with focus ring)
|
|
141
|
+
│ └── {children}
|
|
142
|
+
├── ScrollBar (vertical by default)
|
|
143
|
+
│ └── Thumb (draggable scrollbar handle)
|
|
144
|
+
└── Corner (intersection of scrollbars)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Styling and Theming
|
|
148
|
+
|
|
149
|
+
### Default Styles
|
|
150
|
+
- **Root**: `relative` positioning for scrollbar placement
|
|
151
|
+
- **Viewport**: Full size with inherited border radius, focus ring on keyboard navigation
|
|
152
|
+
- **ScrollBar**: 2.5 units width/height depending on orientation, touch-optimized
|
|
153
|
+
- **Thumb**: Rounded, uses `border` color from theme
|
|
154
|
+
|
|
155
|
+
### Customization Points
|
|
156
|
+
- Apply height/width constraints via `className` on ScrollArea
|
|
157
|
+
- Customize scrollbar appearance via `className` on ScrollBar
|
|
158
|
+
- Border radius is inherited from parent container
|
|
159
|
+
- Colors follow the theme's `border` color token
|
|
160
|
+
|
|
161
|
+
### Data Attributes
|
|
162
|
+
- `data-slot="scroll-area"` - Root container
|
|
163
|
+
- `data-slot="scroll-area-viewport"` - Content viewport
|
|
164
|
+
- `data-slot="scroll-area-scrollbar"` - Scrollbar container
|
|
165
|
+
- `data-slot="scroll-area-thumb"` - Scrollbar thumb
|
|
166
|
+
- `data-orientation="vertical|horizontal"` - Scrollbar orientation state
|
|
167
|
+
|
|
168
|
+
## Accessibility
|
|
169
|
+
|
|
170
|
+
### Keyboard Navigation
|
|
171
|
+
- **Arrow Keys**: Scroll viewport when focused
|
|
172
|
+
- **Page Up/Down**: Scroll by page
|
|
173
|
+
- **Home/End**: Scroll to start/end
|
|
174
|
+
- **Tab**: Focus scrollbar (visible focus ring)
|
|
175
|
+
|
|
176
|
+
### Screen Reader Support
|
|
177
|
+
- Scrollable regions are properly announced
|
|
178
|
+
- Scrollbar controls are keyboard accessible
|
|
179
|
+
- Focus management follows standard patterns
|
|
180
|
+
|
|
181
|
+
### Touch Gestures
|
|
182
|
+
- Scrollbar is touch-optimized with `touch-none` for precise dragging
|
|
183
|
+
- Natural swipe scrolling on viewport
|
|
184
|
+
|
|
185
|
+
## Common Patterns
|
|
186
|
+
|
|
187
|
+
### Fixed Height Content
|
|
188
|
+
```tsx
|
|
189
|
+
<ScrollArea className="h-96 rounded-md border">
|
|
190
|
+
{/* Content that exceeds 384px height will scroll */}
|
|
191
|
+
</ScrollArea>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Full Viewport Height
|
|
195
|
+
```tsx
|
|
196
|
+
<ScrollArea className="h-screen">
|
|
197
|
+
{/* Scrolls within viewport height */}
|
|
198
|
+
</ScrollArea>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Responsive Heights
|
|
202
|
+
```tsx
|
|
203
|
+
<ScrollArea className="h-[50vh] md:h-[70vh] rounded-md border">
|
|
204
|
+
{/* Adapts to viewport size */}
|
|
205
|
+
</ScrollArea>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Preventing Text Selection During Scroll
|
|
209
|
+
The scrollbar uses `select-none` to prevent text selection while dragging, but content remains selectable.
|
|
210
|
+
|
|
211
|
+
## Base UI Integration
|
|
212
|
+
|
|
213
|
+
This component wraps Base UI's ScrollArea primitive:
|
|
214
|
+
- **Root**: Container with scroll detection
|
|
215
|
+
- **Viewport**: Scrollable content area
|
|
216
|
+
- **Scrollbar**: Custom scrollbar UI
|
|
217
|
+
- **Thumb**: Draggable scroll indicator
|
|
218
|
+
- **Corner**: Intersection element for dual scrollbars
|
|
219
|
+
|
|
220
|
+
Base UI handles:
|
|
221
|
+
- Overflow detection and scrollbar visibility
|
|
222
|
+
- Scroll position synchronization
|
|
223
|
+
- Touch and mouse interactions
|
|
224
|
+
- Accessibility features
|
|
225
|
+
|
|
226
|
+
## Implementation Notes
|
|
227
|
+
|
|
228
|
+
### Why Not Native Scrollbars?
|
|
229
|
+
- Consistent cross-browser appearance
|
|
230
|
+
- Better theming and customization
|
|
231
|
+
- Touch-optimized interactions
|
|
232
|
+
- Accessible keyboard controls
|
|
233
|
+
|
|
234
|
+
### Performance Considerations
|
|
235
|
+
- Viewport uses hardware-accelerated transitions
|
|
236
|
+
- Scrollbar only renders when content overflows
|
|
237
|
+
- Efficient re-renders with Base UI primitives
|
|
238
|
+
|
|
239
|
+
### Browser Compatibility
|
|
240
|
+
- Modern browsers with CSS Grid and Flexbox support
|
|
241
|
+
- Touch events for mobile devices
|
|
242
|
+
- Fallback to native scrolling if JavaScript disabled
|
|
243
|
+
|
|
244
|
+
## Comparison with Similar Components
|
|
245
|
+
|
|
246
|
+
### ScrollArea vs. Native Overflow
|
|
247
|
+
- **ScrollArea**: Custom styled scrollbars, consistent appearance
|
|
248
|
+
- **Native**: Browser-dependent styling, system scrollbars
|
|
249
|
+
|
|
250
|
+
### When to Use ScrollArea
|
|
251
|
+
- Consistent design across browsers and platforms
|
|
252
|
+
- Custom scrollbar styling requirements
|
|
253
|
+
- Need for touch-optimized scrolling
|
|
254
|
+
- Keyboard navigation requirements
|
|
255
|
+
|
|
256
|
+
### When to Use Native Scrolling
|
|
257
|
+
- Simple content with minimal styling needs
|
|
258
|
+
- Performance-critical rendering (native is slightly faster)
|
|
259
|
+
- Desire for OS-native scrollbar appearance
|
|
260
|
+
|
|
261
|
+
## Migration from Radix UI
|
|
262
|
+
|
|
263
|
+
If migrating from Radix UI ScrollArea:
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
// Before (Radix UI)
|
|
267
|
+
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
|
268
|
+
|
|
269
|
+
<ScrollAreaPrimitive.Root>
|
|
270
|
+
<ScrollAreaPrimitive.Viewport>
|
|
271
|
+
{children}
|
|
272
|
+
</ScrollAreaPrimitive.Viewport>
|
|
273
|
+
<ScrollAreaPrimitive.Scrollbar orientation="vertical">
|
|
274
|
+
<ScrollAreaPrimitive.Thumb />
|
|
275
|
+
</ScrollAreaPrimitive.Scrollbar>
|
|
276
|
+
</ScrollAreaPrimitive.Root>
|
|
277
|
+
|
|
278
|
+
// After (Base UI)
|
|
279
|
+
import { ScrollArea } from "@neynar/ui/scroll-area"
|
|
280
|
+
|
|
281
|
+
<ScrollArea className="h-96">
|
|
282
|
+
{children}
|
|
283
|
+
</ScrollArea>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Key differences:
|
|
287
|
+
- Base UI uses simpler composition
|
|
288
|
+
- Automatic scrollbar rendering
|
|
289
|
+
- Focus ring styling built-in
|
|
290
|
+
- Data attributes follow `data-slot` pattern
|