'use client'
import { useId, useState } from 'react'
import { Button, Highlight, Inline } from '@hina-ui/react'
const items = ['全部', 'Galgame', '轻小说', '漫画']
export default function Demo() {
const [current, setCurrent] = useState(0)
const id = useId()
return (
<Inline gap="none" className="bg-inset relative isolate rounded-md p-1">
{items.map((item, index) => (
<Button
key={item}
variant="ghost"
tone="neutral"
size="sm"
aria-current={current === index ? 'true' : undefined}
className={current === index ? 'z-0' : 'z-[1]'}
onClick={() => setCurrent(index)}
>
{current === index && (
<Highlight
id={id}
axis="x"
className="bg-surface absolute inset-0 -z-10 rounded-md shadow-sm"
/>
)}
{item}
</Button>
))}
</Inline>
)
}
tsx
用法
import { Highlight } from '@hina-ui/react'
ts
Highlight 用于标示当前条目。它不进行任何测量:调用方通过 className 把它定位在活动条目的范围内,活动条目变化时,它由 Motion 的布局动画从原位置连续平移到新位置,而不是在一处消失、在另一处出现。
组件本身没有外观,背景色、圆角与层级全部由调用方通过 className 指定。
位移动画轴向
设置 axis="x" 只对水平位移做过渡,axis="y" 只对垂直位移做过渡。另一轴的位置直接跟随当前布局,宽高变化仍保留动画。默认 axis="both" 对两个方向的位移都做过渡。
Tabs 与 SegmentedControl 根据 orientation 自动选择轴向;Anchor 使用 y。
覆盖一段范围
将列表排成单列网格,每一项显式占据一行,高亮块同样作为网格项,通过 grid-row 覆盖整段。此时它保持挂载,跨行范围变化时由布局动画连续伸缩。
简介角色制作人员发售信息
'use client'
import { useState } from 'react'
import { Button, Grid, Highlight, Inline, Stack, Text } from '@hina-ui/react'
const rows = ['简介', '角色', '制作人员', '发售信息']
export default function Demo() {
const [from, setFrom] = useState(1)
const [to, setTo] = useState(2)
const span = `${Math.min(from, to) + 1} / ${Math.max(from, to) + 2}`
return (
<Stack gap="md" className="w-full max-w-sm">
<Inline gap="sm">
<Button
size="sm"
variant="outline"
tone="neutral"
onClick={() => setFrom((from + 1) % rows.length)}
>
移动起点
</Button>
<Button
size="sm"
variant="outline"
tone="neutral"
onClick={() => setTo((to + 1) % rows.length)}
>
移动终点
</Button>
</Inline>
<Grid cols={1} gap="none" className="border-line relative isolate rounded-lg border p-1">
<Highlight
axis="y"
style={{ gridRow: span }}
className="bg-accent-soft col-start-1 -z-10 rounded-md"
/>
{rows.map((row, index) => (
<Text
key={row}
as="span"
size="sm"
className="col-start-1 block px-3 py-1.5"
style={{ gridRow: index + 1 }}
>
{row}
</Text>
))}
</Grid>
</Stack>
)
}
tsx
行为
- 静止时的位置由 CSS 布局决定,随条目尺寸变化,不需要测量,也不需要监听尺寸变化。
- 移动与伸缩都是连续的位移,缩放过程中圆角与阴影的变形由 Motion 自动修正。
- 系统启用减弱动态效果时,直接呈现在目标位置,不做位移过渡。
- 位移过程中高亮块位于目标条目内部。若各条目各自构成层叠上下文,应给非活动条目更高的层级(例如
z-[1]),活动条目使用z-0,使高亮块从其他条目的文字下方经过。 - 服务端渲染输出的就是真实的高亮块,首屏与水合后的状态一致。
无障碍
- 组件带有
aria-hidden,仅作装饰,不进入无障碍树。 - 当前位置必须另有语义表达,例如活动条目上的
aria-current或者aria-pressed,不能仅依赖高亮块。
API
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id | string | — | 共享布局动画的标识,在条目之间移动时必须提供 |
axis | 'x' | 'y' | 'both' | 'both' | 位移动画的轴向,不影响宽高动画 |
as | 'div' | 'li' | 'div' | 渲染的元素 |
className | string | — | 追加至根元素的类名 |