Rating(评分)用来收集分数(这次入住体验如何)或展示分数(平均评分)。每颗星本质上是一个单选框,因此它的行为与单选框组一致:只有一个值、只有一个 Tab 停留点。需要在数值区间里连续取值时用滑块。
组合
Rating
└── RatingItemRating 会把它的 children 按星星数量重复渲染,所以里面只放一个 RatingItem。不传 children 时,它渲染没有图标的 RatingItem。
用法
import { Rating, RatingItem } from "@ui-sdk/react/rating";
<Rating aria-label="评分" defaultValue={3}>
<RatingItem><span aria-hidden="true">★</span></RatingItem>
</Rating>;用 aria-label 为评分命名,或放进字段里由 FieldLabel 命名。不传 defaultValue 时初始没有分数,Tab 落在第一颗星上。选星星的 radio 覆盖在图标之上,图标自己不需要任何事件。
填充与半星
每颗星都知道当前显示的分数覆盖了自己多少:CSS 变量 --rating-fill 从 0 到 1,data-filled 标记被完全覆盖的星,data-partial 标记分数落在中间的那颗星。
[data-slot="rating-item"] {
color: var(--color-border);
}
[data-slot="rating-item"][data-filled] {
color: var(--color-warning);
}
[data-slot="rating-item"][data-partial] > span {
background: linear-gradient(to right, var(--color-warning) calc(var(--rating-fill) * 100%), var(--color-border) 0);
background-clip: text;
color: transparent;
}step={0.5} 把每颗星分成两半,每一半都是独立的 radio,名字类似「2.5 星」,点星的左半边就选半分。也可以把 children 写成函数,按这一项的状态渲染:
<Rating aria-label="评分" step={0.5} defaultValue={3.5}>
<RatingItem>
{({ fill }) => <span aria-hidden="true">{fill === 1 ? "★" : "☆"}</span>}
</RatingItem>
</Rating>项的状态提供 index(从 1 开始)、fill、checked、highlighted、disabled、readOnly。
指针预览与清除
指针在星星上移动时会预览它下面的分数:--rating-fill 跟随指针,data-highlighted 标记预览覆盖到的星星,移开后恢复显示真实分数。触屏没有预览,轻点直接落分。
deselectable 允许清除分数:再次点击已选中的那颗星,或在它上面按 Space,值回到 null。
<Rating aria-label="评分" defaultValue={4} deselectable>
<RatingItem><span aria-hidden="true">★</span></RatingItem>
</Rating>只读展示平均分
readOnly 用来展示不可修改的分数,任意小数都能显示,例如 4.2 会让第五颗星填充五分之一。此时整个评分对屏幕阅读器是一张图片,内部 radio 被隐藏,名字应由 getValueLabel 提供,这样名字里带上分数(用 aria-label 会把它覆盖掉)。
<Rating value={4.2} readOnly getValueLabel={(value, max) => `评分 ${value} 分,满分 ${max} 分`}>
<RatingItem><span aria-hidden="true">★</span></RatingItem>
</Rating>disabled 则让每颗星都不可聚焦、不可修改,且不提交值。
受控、控制器与表单
const [value, setValue] = React.useState<number | null>(null);
<Rating aria-label="评分" value={value} max={10} loop onValueChange={(next) => setValue(next)}
getValueLabel={(value, max) => `${max} 分中的 ${value} 分`}>
<RatingItem />
</Rating>;max 设置星星数量,loop 让方向键首尾循环,getValueLabel(value, max) 为每个 radio 命名(默认输出类似「3 星」)。details.reason 区分 item-press、list-navigation、reset、restore 与 imperative,details.cancel() 保留当前分数。用 createRatingController() 创建控制器可调用 getValue / setValue / focus,每个挂载的评分用各自的控制器;禁用与只读时 setValue 无效。参见控制器。
每个 radio 都带一个隐藏的原生 radio,所以评分参与表单:传 name 才提交分数,没有 name 或没有分数时不提交任何数据;required 在没有分数时阻止提交并给出浏览器提示,form 关联其他表单 id,重置回到 defaultValue。参见表单。
样式与状态
- 根
rating:data-disabled、data-readonly、data-required。 - 星
rating-item:data-filled(整颗被覆盖)、data-partial(部分覆盖)、data-checked(分数落在这颗)、data-highlighted(指针预览覆盖到)、data-disabled、data-readonly。 - CSS 变量:
rating-item上的--rating-fill,取值 0 到 1,表示这颗星被覆盖的比例。
[data-slot="rating-item"][data-highlighted] { color: var(--color-warning); }
[data-slot="rating"][data-readonly] [data-slot="rating-item"] { cursor: default; }半星与平均分都靠 calc(var(--rating-fill) * 100%) 来画,例如渐变加星形遮罩,或上面对文字裁剪渐变的写法。详见样式与动画。
API
Rating 常用属性:
| 属性 | 类型 | 说明 |
|---|---|---|
value |
number | null |
受控分数,null 表示还没打分。 |
defaultValue |
number | null |
非受控初始分数,重置回到它。 |
onValueChange |
(value, details) => void |
分数将要变化时触发,可取消。 |
max |
number |
星星数量。 |
step |
number |
最小变化量,以星为单位,0.5 即半星。 |
deselectable |
boolean |
再次点击已选中的星可清除分数。 |
readOnly / disabled |
boolean |
只读展示、完全禁用。 |
required |
boolean |
没有分数时阻止提交。 |
name / form |
string |
提交字段名(不传则不提交)与关联表单 id。 |
loop |
boolean |
方向键是否首尾循环。 |
getValueLabel |
(value, max) => string |
为每个 radio 与只读评分命名。 |
controller |
object |
createRatingController() 创建的控制器。 |
根的状态还提供 highlighted(number | null),即指针正在预览的分数。
键盘与无障碍
评分是一个 radio 组,遵循 WAI-ARIA radio group 模式。每颗星为它能选的每个分数持有一个 radio 角色的按钮,以及参与表单的隐藏原生 radio;星形图标要加 aria-hidden,因为 radio 本身已有名字。只读评分改用 img 角色,名字来自 getValueLabel。
| 按键 | 行为 |
|---|---|
Tab |
进入评分,落在已选中的星或第一颗,整组只停一次。 |
| 方向键 | 按 step 移到上一个或下一个分数并选中,默认停在两端,loop 时循环。 |
Space |
选中当前星;若已选中且设了 deselectable,则清除分数。 |
Enter |
提交所在表单。 |