<script setup lang="ts">
import { ref, useId } from 'vue'
import { Button, Highlight, Inline } from '@hina-ui/vue'
const items = ['全部', 'Galgame', '轻小说', '漫画']
const current = ref(0)
const id = useId()
</script>
<template>
<Inline gap="none" class="bg-inset relative isolate rounded-md p-1">
<Button
v-for="(item, index) in items"
:key="item"
variant="ghost"
tone="neutral"
size="sm"
:aria-current="current === index ? 'true' : undefined"
:class="current === index ? 'z-0' : 'z-[1]'"
@click="current = index"
>
<Highlight
v-if="current === index"
:id="id"
axis="x"
class="bg-surface absolute inset-0 -z-10 rounded-md shadow-sm"
/>
{{ item }}
</Button>
</Inline>
</template>
vue
用法
import { Highlight } from '@hina-ui/vue'
ts
Highlight 用于标示当前条目。它不进行任何测量:调用方通过 class 把它定位在活动条目的范围内,活动条目变化时,它由 Motion 的布局动画从原位置连续平移到新位置,而不是在一处消失、在另一处出现。
组件本身没有外观,背景色、圆角与层级全部由调用方通过 class 指定。
位移动画轴向
设置 axis="x" 只对水平位移做过渡,axis="y" 只对垂直位移做过渡。另一轴的位置直接跟随当前布局,宽高变化仍保留动画。默认 axis="both" 对两个方向的位移都做过渡。
Tabs 与 SegmentedControl 根据 orientation 自动选择轴向;Anchor 使用 y。
覆盖一段范围
将列表排成单列网格,每一项显式占据一行,高亮块同样作为网格项,通过 grid-row 覆盖整段。此时它保持挂载,跨行范围变化时由布局动画连续伸缩。
简介角色制作人员发售信息
<script setup lang="ts">
import { computed, ref } from 'vue'
import { Button, Grid, Highlight, Inline, Stack, Text } from '@hina-ui/vue'
const rows = ['简介', '角色', '制作人员', '发售信息']
const from = ref(1)
const to = ref(2)
const span = computed(
() => `${Math.min(from.value, to.value) + 1} / ${Math.max(from.value, to.value) + 2}`,
)
</script>
<template>
<Stack gap="md" class="w-full max-w-sm">
<Inline gap="sm">
<Button size="sm" variant="outline" tone="neutral" @click="from = (from + 1) % rows.length">
移动起点
</Button>
<Button size="sm" variant="outline" tone="neutral" @click="to = (to + 1) % rows.length">
移动终点
</Button>
</Inline>
<Grid :cols="1" gap="none" class="border-line relative isolate rounded-lg border p-1">
<Highlight
axis="y"
:style="{ gridRow: span }"
class="bg-accent-soft col-start-1 -z-10 rounded-md"
/>
<Text
v-for="(row, index) in rows"
:key="row"
as="span"
size="sm"
class="col-start-1 block px-3 py-1.5"
:style="{ gridRow: index + 1 }"
>
{{ row }}
</Text>
</Grid>
</Stack>
</template>
vue
行为
- 静止时的位置由 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' | 渲染的元素 |
class | string | — | 追加至根元素的类名 |