可调整面板 · Resizable
允许用户拖动调整相邻面板的大小。
安装与前置条件
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0
需要 React 19+,按顺序导入基础 CSS 和主题,并设置 Theme Provider。参见开始使用。
示例与使用方式
- 可调整面板 · 综合展示 1:首先体验这个示例,再查看代码理解组件的组合方式。
- 可调整面板 · 交互示例 2:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
API 与上游详细参考
优先使用 0.7.1 官方中文说明;上游尚未提供中文的条目保留英文。属性名、类型及默认值保持原生 API,不把示例状态名称当成组件属性。
Resizable
Hook-based resizable panel system. useResizable() manages size state and ResizeHandle provides the interactive pill-grip separator. Pass resize props to existing layout components via their resizable prop.
使用建议
- Use percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) when a percentage needs exactly one pixel floor or ceiling. Reuse Table’s pixel() helper; numbers, exact Npx, and pixel(value) remain pixels. State, persistence, callbacks, paint, and ARIA remain resolved pixels.
- Use useResizable() with existing Astryx layout components. Pass the returned props to the resizable prop on LayoutPanel or SideNav.
- Provide an accessible label on each ResizeHandle when multiple handles exist (e.g. "Resize sidebar", "Resize terminal").
- 避免:Wrap panels in extra container components for resize. The hook-first architecture avoids extra DOM; use it directly on existing components.
主题 API
{
"targets": [
{
"className": "astryx-resize-handle",
"visualProps": [
"direction"
]
},
{
"className": "astryx-resize-handle-pill"
}
]
}
useResizable
Hook that manages resize state for one or more panel regions. Returns size, isCollapsed, collapse/expand/resize methods, and props to pass to handles.
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| defaultSize | SizeValue | ResizableSize | 250 | Initial size. Numbers, exact Npx, and pixel(value) are pixels; exact N% has no additional pixel bound; percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds exactly one and resolves once. |
| minSize | ResizableSize | 50 | Live minimum. Numbers, exact Npx, and pixel(value) remain pixels; exact N% has no additional pixel bound; percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds exactly one. |
| maxSize | ResizableSize | Infinity | Live maximum. Numbers, exact Npx, and pixel(value) remain pixels; exact N% has no additional pixel bound; percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds exactly one. |
| collapsible | boolean | false | Whether the region can collapse to size 0. |
| collapsedSize | number | 40 | Pixel threshold that triggers collapse during drag. |
| snaps | number[] | Pixel values to snap to during resize. | |
| shrinkOrder | number | Cascade priority: lower number shrinks first. | |
| autoSaveId | string | Key for persisting sizes and collapse state to localStorage. |
Structured fixed pixels
const region = useResizable({
defaultSize: pixel(333),
containerRef,
});
One-time structured default
import {useRef} from 'react';
import {ResizeHandle, useResizable} from '@astryxdesign/core/Resizable';
import {percent, pixel} from '@astryxdesign/core/Resizable/utils';
const containerRef = useRef<HTMLDivElement>(null);
const region = useResizable({
defaultSize: percent(40, {min: pixel(333)}),
containerRef,
});
<ResizeHandle direction="horizontal" resizable={region.props} />;
Live percentage floor
const region = useResizable({
defaultSize: 0,
minSize: percent(40, {min: pixel(333)}),
containerRef,
});
Live percentage ceiling
const region = useResizable({
defaultSize: 500,
maxSize: percent(10, {max: pixel(400)}),
containerRef,
});
Resize Handle
Draggable separator between panels. Pill-grip design: invisible at rest, visible on hover (0.6 opacity), fully opaque during drag (1.0). Keyboard-accessible.
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| direction | 'horizontal' | 'vertical' | 'horizontal' | Layout direction: determines cursor and indicator orientation. |
| isReversed | boolean | false | Reverse drag direction. Use when the handle controls a panel on the end/right/bottom side. |
| isDisabled | boolean | false | Whether the handle is interactive. |
| hasDivider | boolean | false | Show a full-length 1px divider line through the handle. Use when adjacent panels share the same background. |
| isAlwaysVisible | boolean | true | Show the pill grip at rest instead of only on hover. Use when discoverability is important. |
| pillPlacement | 'start' | 'end' | 'center' | 'auto' | 'auto' | Which side of the divider the pill sits on. auto = content side (derived from isReversed), flips when collapsed. start = left/top, end = right/bottom, center = centered on divider. |
| label | string | 'Resize handle' | Accessible label for the separator. |
| resizable * | ResizableProps | Resize props from useResizable: connects handle to panel. Carries the region's axis ('horizontal' | 'vertical'), which must match this handle's direction. | |
| children | ReactNode | Custom handle content. Overrides the default pill + divider. | |
| xstyle | StyleXStyles | StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value, not an inline style object like style={{}}. |
组件结构
- Handle: Focusable separator that owns pointer and keyboard resize interaction.
- Grip pill: Default visible grip indicator; custom handle content can replace it.
- Grab zone: Invisible enlarged pointer region aligned with the grip or divider.
useResizable
Hook for adding drag-to-resize behavior to layout regions. Supports single-region and multi-region configurations with snap points, collapsible panels, localStorage persistence, and cascade resize ordering.
Parameters
| Parameter | Type | Description |
|---|---|---|
| defaultSize | SizeValue | ResizableSize | Initial size. Numbers, exact "Npx", and pixel(value) are pixels. Exact "N%" has no additional pixel bound. percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds one pixel floor or ceiling. A percentage resolves ONCE into pixels — against containerRef when supplied, against the viewport otherwise — and does not track its basis afterwards. The released broad number | string type remains compatible; runtime validation is authoritative. |
| minSize | ResizableSize | Minimum size. Numbers, exact "Npx", and pixel(value) remain pixels; exact "N%" has no additional pixel bound; percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds exactly one. Percentage minimums re-resolve when their basis changes and clamp the current pixel selection. |
| maxSize | ResizableSize | Maximum size. Numbers, exact "Npx", and pixel(value) remain pixels; exact "N%" has no additional pixel bound; percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds exactly one. Percentage maximums re-resolve when their basis changes and clamp the current pixel selection. |
| containerRef | RefObject<HTMLElement | null> | The element a percentage is a share of. Caller-owned: the hook never infers one. Omitted, percentages use the viewport, which is the released behaviour. The ref may point at a different element over time — the basis follows it. Until that element is actually laid out (not yet mounted, display:none, detached) percentages use a temporary 1200px basis rather than its zero measurement, and nothing is persisted from it. |
| direction | 'horizontal' | 'vertical' | Which axis this region resizes along. Selects the container's inline or block content-box size as the percentage basis, and must match the direction given to ResizeHandle. |
| collapsible | boolean | Whether dragging below the collapsed threshold collapses the region to zero. |
| snaps | number[] | Pixel values to snap to during drag. |
| autoSaveId | string | Key for localStorage persistence of size and collapse state across sessions. |
| defaultIsCollapsed | boolean | Initial collapse state (uncontrolled). A persisted entry wins over it. |
| isCollapsed | boolean | Controlled collapse state. collapse(), expand() and a drag past the threshold then report through onCollapseChange instead of changing state internally. |
| onCollapseChange | (isCollapsed: boolean) => void | Called once per collapse state change, via drag or programmatically. |
Returns
[
{
"name": "size",
"type": "number",
"description": "Current size in pixels."
},
{
"name": "isCollapsed",
"type": "boolean",
"description": "Whether the region is currently collapsed."
},
{
"name": "collapse",
"type": "() => void",
"description": "Programmatically collapse the region."
},
{
"name": "expand",
"type": "() => void",
"description": "Expand from collapsed state."
},
{
"name": "resize",
"type": "(size: number) => void",
"description": "Resize to a specific pixel value."
},
{
"name": "props",
"type": "ResizableProps",
"description": "Props to spread on the resizable component or pass to ResizeHandle."
}
]
使用建议
- Use percent(40, {min: pixel(333)}) for a 40% size with a 333px floor, or percent(10, {max: pixel(400)}) for a 10% size with a 400px ceiling. The options argument is required and carries a floor XOR a ceiling.
- A structured default is an initial choice only; a structured minSize or maxSize remains live. State, persistence, callbacks, resize(), paint, and ARIA all use resolved pixel numbers.
- Import percent and Table’s same pixel binding from @astryxdesign/core/Resizable/utils when constructing configuration in a Server Component; the root package exposes one pixel symbol and one percent symbol without collision.
- Use with Layout or AppShell sidebar for resizable navigation panels.
- Set autoSaveId to persist user-chosen sizes across page reloads.
- 避免:Set minSize too small; content becomes unreadable. Prefer collapsible for panels that can hide entirely.