使用场景
Collapsible是一个行为组件,默认开启动画效果。它被用于 Semi 的各种组件中,如:Navigation,Collapse,Tree,TreeSelect,以及Typography中。- 当上述组件不能满足需求或者需要自定义一些折叠行为时,可以使用
Collapsible来包裹需要展开或者折叠的内容。
代码演示
如何引入
import { Collapsible } from '@fibrous/semi-ui-react';
基本用法
通过 isOpen 来控制内容的展开或者折叠。
代码
import { useState } from 'react';
import { Collapsible, Button } from '@fibrous/semi-ui-react';
export default function CollapsibleDemo() {
const [isOpen, setOpen] = useState(false);
const toggle = () => {
setOpen(!isOpen);
};
const collapsed = (
<ul>
<li>
<p>Semi Design 以内容优先进行设计。</p>
</li>
<li>
<p>更容易地自定义主题。</p>
</li>
<li>
<p>适用国际化场景。</p>
</li>
<li>
<p>效率场景加入人性化关怀。</p>
</li>
</ul>
);
return (
<div>
<Button onClick={toggle} aria-expanded={isOpen} aria-controls="collapsible-basic">
Toggle
</Button>
<Collapsible isOpen={isOpen} id="collapsible-basic">
{collapsed}
</Collapsible>
</div>
);
}自定义动画时间
通过 duration 设置动画展开或者折叠的时间,也可以通过 motion 来关闭动画。
设置动画时间:
代码
import { useState } from 'react';
import { Collapsible, InputNumber, Button } from '@fibrous/semi-ui-react';
export default function CollapsibleDemo() {
const [isOpen, setOpen] = useState(false);
const [duration, setDuration] = useState(250);
const toggle = () => {
setOpen(!isOpen);
};
const collapsed = (
<ul>
<li>
<p>Semi Design 以内容优先进行设计。</p>
</li>
<li>
<p>更容易地自定义主题。</p>
</li>
<li>
<p>适用国际化场景。</p>
</li>
<li>
<p>效率场景加入人性化关怀。</p>
</li>
</ul>
);
return (
<div>
<span id="collapsible-duration-label">设置动画时间:</span>
<InputNumber aria-labelledby="collapsible-duration-label" min={0} defaultValue={250} style={{ width: 120 }} onChange={(val) => setDuration(Number(val))} step={10} />
<br />
<Button onClick={toggle} aria-expanded={isOpen} aria-controls="collapsible-duration">
Toggle
</Button>
<Collapsible isOpen={isOpen} duration={duration} id="collapsible-duration">
{collapsed}
</Collapsible>
</div>
);
}嵌套使用
代码
import { useState } from 'react';
import { Collapsible, Button } from '@fibrous/semi-ui-react';
export default function CollapsibleDemo() {
const [isOpen, setOpen] = useState(false);
const [isChildOpen, setChildOpen] = useState(false);
const collapsed = (
<ul>
<li>
<p>Semi Design 以内容优先进行设计。</p>
</li>
<li>
<p>更容易地自定义主题。</p>
</li>
<li>
<p>适用国际化场景。</p>
</li>
<li>
<p>效率场景加入人性化关怀。</p>
</li>
</ul>
);
return (
<div>
<Button onClick={() => setOpen(!isOpen)} aria-expanded={isOpen} aria-controls="collapsible-nested">
Toggle
</Button>
<br />
<Collapsible isOpen={isOpen} id="collapsible-nested">
<div>
<span>Semi Design的设计原则包括:</span>
<Button onClick={() => setChildOpen(!isChildOpen)} aria-expanded={isChildOpen} aria-controls="collapsible-nested-list">
Toggle List
</Button>
</div>
<Collapsible isOpen={isChildOpen} id="collapsible-nested-list">
{collapsed}
</Collapsible>
</Collapsible>
</div>
);
}自定义折叠高度
可以使用 collapseHeight 自定义收起的高度
代码
import { useState, type CSSProperties } from 'react';
import { Collapsible, Button } from '@fibrous/semi-ui-react';
export default function CollapsibleDemo() {
const [isOpen, setOpen] = useState(false);
const maskStyle = isOpen
? {}
: {
WebkitMaskImage: 'linear-gradient(to bottom, black 0%, rgba(0, 0, 0, 1) 60%, rgba(0, 0, 0, 0.2) 80%, transparent 100%)',
};
const collapsed = (
<ul>
<li>
<p>Semi Design 以内容优先进行设计。</p>
</li>
<li>
<p>更容易地自定义主题。</p>
</li>
<li>
<p>适用国际化场景。</p>
</li>
<li>
<p>效率场景加入人性化关怀。</p>
</li>
</ul>
);
const toggle = () => {
setOpen(!isOpen);
};
const linkStyle: CSSProperties = {
position: 'absolute',
left: 0,
right: 0,
width: '100%',
padding: 0,
border: 'none',
background: 'none',
fontSize: 'inherit',
fontFamily: 'inherit',
lineHeight: 'inherit',
textAlign: 'center',
bottom: -10,
fontWeight: 700,
cursor: 'pointer',
color: 'var(--semi-color-link)',
};
return (
<>
<Button onClick={toggle} aria-expanded={isOpen} aria-controls="collapsible-height">
Toggle
</Button>
<div style={{ position: 'relative' }}>
<Collapsible isOpen={isOpen} collapseHeight={60} style={{ ...maskStyle }} id="collapsible-height">
{collapsed}
</Collapsible>
{!isOpen && (
<button type="button" onClick={toggle} aria-expanded={isOpen} aria-controls="collapsible-height" style={{ ...linkStyle }}>
+ Show More
</button>
)}
</div>
</>
);
}API 参考
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| children | 需要展开或收起的内容。 | React.ReactNode | - |
| className | 外层 wrapper 的类名。 | string | - |
| collapseHeight | 收起时的高度,单位为像素。大于 0 时收起后仍露出部分内容。 | number | 0 |
| collapseHeightAdaptive | 内容高度小于 collapseHeight 时,收起高度是否取内容高度,即 Math.min(内容高度, collapseHeight)。 | boolean | false |
| duration | 动画执行的时间,单位为毫秒。 | number | 250 |
| fade | 是否开启淡入淡出。 | boolean | false |
| id | 内层内容元素的 id,可配合触发器的 aria-controls 使用。 | string | - |
| isOpen | 是否展开内容区域。 | boolean | false |
| keepDOM | 是否保留隐藏的内容 DOM 树,默认销毁。 | boolean | false |
| lazyRender | 配合 keepDOM 使用,为 true 时挂载时不渲染内容,首次展开后才渲染。 | boolean | false |
| motion | 是否开启动画。 | boolean | true |
| onMotionEnd | 展开或收起动画结束时调用。 | Type() => void | - |
| ref | 外层 wrapper(div)的引用。 | TypeReact.Ref<HTMLDivElement> | - |
| style | 外层 wrapper 的样式。 | React.CSSProperties | - |
其他原生属性(如 aria-*、data-*、onMouseEnter)落在外层 wrapper 上;onTransitionEnd 与组件自身的处理同时触发。
Accessibility
ARIA
- Collapsible 具有
idprops,传入的值会被设置为内层内容元素的 id(不是外层 wrapper),触发按钮用aria-controls指向它,并用aria-expanded表示当前是否展开,见下方使用示例。
import { useState } from 'react';
import { Button, Collapsible } from '@fibrous/semi-ui-react';
() => {
const collapseId = 'myCollapsible';
const [visible, setVisible] = useState(false);
return (
<>
<Button onClick={() => setVisible(!visible)} aria-expanded={visible} aria-controls={collapseId}>
{visible ? 'hide' : 'show'}
</Button>
<Collapsible isOpen={visible} id={collapseId}>
<div>hide content</div>
</Collapsible>
</>
);
};
焦点
collapseHeight为 0 时,收起的内容设为inert,读屏读不到,Tab 也到不了。collapseHeight大于 0 时,收起后露出的预览区与被裁掉的部分是同一个元素,不能只隐藏被裁掉的部分,所以整段内容保持可读、可聚焦,读屏用户能读到全文。被裁掉的部分里不要放链接、按钮等可聚焦元素,否则键盘焦点会落在看不见的位置。
FAQ
- 为什么使用 Collapsible 没有正常展开?
Collapsible 的展开高度由 CSS 计算,不依赖测量节点高度,父级为display: none时切换isOpen也能得到正确的展开高度。请先检查isOpen是否随状态更新;collapseHeight大于 0 时,收起状态本来就会露出一部分内容。