ColorInput 自带一个方形的色块按钮。如果你需要的是工具栏图标、设置项或文字按钮,就把 Popover 和 ChromaPanel 组合起来使用。这样可以做出自定义的 React 颜色选择器弹出层,而不必嵌套两层浮层。
"use client";
import { ChromaPanel, Popover } from "chroma-panel";
import "chroma-panel/style.css";
import { useState } from "react";
export function BrandColorButton() {
const [anchor, setAnchor] = useState<HTMLButtonElement | null>(null);
const [open, setOpen] = useState(false);
const [color, setColor] = useState("#3366cc");
return (
<>
<button
ref={setAnchor}
type="button"
aria-haspopup="dialog"
aria-expanded={open}
onClick={() => setOpen((current) => !current)}
>
<span
aria-hidden="true"
style={{
display: "inline-block",
width: 14,
height: 14,
borderRadius: 4,
background: color,
}}
/>
Brand color
</button>
<Popover anchor={anchor} open={open} onClose={() => setOpen(false)}>
<div role="dialog" aria-label="Brand color" aria-modal="false">
<ChromaPanel
value={color}
onChange={(next) => setColor(next.hex)}
title="Brand color"
onClose={() => {
setOpen(false);
anchor?.focus();
}}
/>
</div>
</Popover>
</>
);
}用 state 而不是 ref 保存锚点
anchor 是弹出层定位时参照的元素。按钮渲染出来之前,它的值是 null。把它存在 state 里,元素可用时 Popover 才会重新渲染。
需要补上的属性
设置 aria-haspopup="dialog",并让 aria-expanded 与 open 保持同步。给触发器一个可访问名称。弹出层本身需要 role="dialog" 和自己的标签,写法见上面的示例。
给面板传入 onClose 后,点击它的红色窗口按钮就会关闭弹出层,而不是只变暗留在那里,示例中的处理函数做的就是这件事。关闭后把焦点还给触发器,则需要你自己处理。面板本身负责哪些事情,请参阅无障碍功能。
Popover 已经做好的事
你不需要自己处理点击外部或按 Escape 关闭,弹出层自带这些行为。
- 它通过 portal 渲染到
document.body中,所以任何带overflow: hidden的祖先元素都裁切不到它。 - 下方空间足够时,它显示在锚点下方,否则显示在上方;同时与视口边缘保持 8px 的距离。与锚点的间距由
offset控制,默认 8px。 - 页面滚动或窗口大小变化时,它会重新定位,并把可用空间告诉面板,面板据此限制自己的高度。
- 按 Escape 会关闭它,并把焦点移回锚点。
- 在弹出层和锚点之外按下指针,也会关闭它。
- 打开时,焦点移到其中第一个可聚焦的控件上。Tab 和 Shift+Tab 只在弹出层内循环,不会跑到后面的页面上去。
宽度小于 640px 时
宽度小于 640px 时,弹出层默认变成底部面板(bottom sheet),同时加上遮罩并锁定页面滚动。设置 sheetOnMobile={false},就能在小屏幕上继续使用锚定在按钮旁的颜色选择器下拉层。
<Popover
anchor={anchor}
open={open}
onClose={() => setOpen(false)}
sheetOnMobile={false}
>
<div role="dialog" aria-label="Brand color">
<ChromaPanel value={color} onChange={(next) => setColor(next.hex)} />
</div>
</Popover>sheetOnMobile 是 Popover 的 prop
只有 Popover 有这个 prop。ColorInput 既不接收它,也不会传递它,所以
ColorInput 在宽度小于 640px
时总是变成底部面板。想避开这一行为,就得自己实现触发器。完整的 prop 列表见
Popover 参考文档,被你替换掉的那个触发器见
ColorInput 参考文档。