iter(g327): stepper 补失败态与整条回跳
This commit is contained in:
@@ -1,23 +1,39 @@
|
||||
import { effect, h } from '@ui-arch/frame';
|
||||
import type { StepperProps } from './types.js';
|
||||
|
||||
/*** 步骤条:done/current/upcoming 三态流程指示(横向 / 竖向),clickable 时已完成步骤可点回跳并写回 active 信号 ***/
|
||||
/*** 步骤条:done/current/upcoming 三态流程指示(横向 / 竖向),clickable 时已完成步骤整条可点回跳;status='error' 的步骤独立标失败(提交没过既不是 done 也不是 current,回退 active 会让「失败」与「还没走到」混为一谈) ***/
|
||||
export function stepper(props: StepperProps): HTMLElement {
|
||||
const active = props.active;
|
||||
|
||||
/*** 回跳:写回 active 并回调;不可点或未完成一律不动(失败的步骤也不给回跳,它才是要修的那一步) ***/
|
||||
const clickStep = (i: number): void => { if (!props.clickable || i >= active()) return; // 卫语句:不可点或未完成不回跳
|
||||
active(i); if (props.onChange) props.onChange(i); }; // 写回信号并回调
|
||||
active(i); if (props.onChange) props.onChange(i); };
|
||||
|
||||
/*** 整条可点:圆点本身是可聚焦按钮,点标题文字走 li 这一层;点在按钮上交给按钮自己,避免一次点击回调两次 ***/
|
||||
const bindRowClick = (li: HTMLElement, i: number): void => {
|
||||
li.addEventListener('click', (e: MouseEvent) => {
|
||||
if ((e.target as HTMLElement).closest('button')) return; // 卫语句:圆点按钮的点击已由按钮处理
|
||||
clickStep(i);
|
||||
});
|
||||
};
|
||||
|
||||
const render = (): HTMLElement => {
|
||||
const cur = active();
|
||||
const items = props.steps.map((s, i) => {
|
||||
const state = i < cur ? 'done' : i === cur ? 'current' : 'upcoming'; // 三态:已完成 / 当前 / 未到
|
||||
const mark = state === 'done' ? '✓' : String(i + 1); // 完成打勾,其余显示序号
|
||||
const clickable = !!props.clickable && i < cur; // 仅已完成步骤可回跳
|
||||
const head = clickable
|
||||
const failed = s.status === 'error'; // 失败态优先级最高:不参与进度比较
|
||||
const state = failed ? 'error' : i < cur ? 'done' : i === cur ? 'current' : 'upcoming'; // 三态 + 失败
|
||||
const mark = failed ? '!' : state === 'done' ? '✓' : String(i + 1); // 完成打勾、失败打感叹号,其余显示序号
|
||||
const clickable = !!props.clickable && i < cur && !failed; // 仅已完成且未失败的可回跳
|
||||
const dot = clickable
|
||||
? h('button', { class: 'ua-step-dot', type: 'button', onClick: () => clickStep(i) }, mark)
|
||||
: h('span', { class: 'ua-step-dot' }, mark);
|
||||
: h('span', { class: 'ua-step-dot', ...(failed ? { role: 'img', 'aria-label': `第 ${i + 1} 步失败:${s.label}` } : {}) }, mark); // 失败的感叹号需要读屏语义,其余态序号由标题承载
|
||||
const body = h('div', { class: 'ua-step-text' }, h('span', { class: 'ua-step-label' }, s.label),
|
||||
s.description ? h('span', { class: 'ua-step-desc' }, s.description) : null);
|
||||
const li = h('li', { class: `ua-step ua-step-${state}`, 'aria-current': state === 'current' ? 'step' : null }, head, body);
|
||||
const li = h('li', {
|
||||
class: `ua-step ua-step-${state}${clickable ? ' is-clickable' : ''}`,
|
||||
'aria-current': state === 'current' ? 'step' : null,
|
||||
}, dot, body) as HTMLElement;
|
||||
if (clickable) bindRowClick(li, i);
|
||||
return li;
|
||||
});
|
||||
const cls = props.direction === 'vertical' ? 'ua-stepper is-vertical' : 'ua-stepper'; // 方向只影响布局类,渲染逻辑共用
|
||||
|
||||
@@ -232,6 +232,7 @@ export interface InputNumberProps {
|
||||
export interface StepperItem {
|
||||
label: string; // 步骤标题
|
||||
description?: string; // 步骤补充说明;缺省不渲染
|
||||
status?: 'error'; // 该步失败(红点 + 感叹号 + 读屏「失败」);缺省按进度推 done/current/upcoming
|
||||
}
|
||||
|
||||
/*** 步骤条入参 ***/
|
||||
|
||||
@@ -1397,7 +1397,12 @@
|
||||
"n": "steps",
|
||||
"t": "StepperItem[]",
|
||||
"r": 1,
|
||||
"d": "步骤列表,每项 { label, description? }"
|
||||
"d": "步骤列表,每项 { label, description?, status? }"
|
||||
},
|
||||
{
|
||||
"n": "status",
|
||||
"t": "'error'",
|
||||
"d": "写在 steps 的每一项里:该项标为失败(红点 + 感叹号 + 读屏「第 N 步:xx(失败)」),优先级高于进度态,也不参与回跳"
|
||||
},
|
||||
{
|
||||
"n": "active",
|
||||
@@ -1427,7 +1432,8 @@
|
||||
"steps": [
|
||||
"用 signal(0) 创建当前步索引,传入 active;索引之前的步骤显示 ✓ 已完成态",
|
||||
"流程推进时外部赋值即可:step(step() + 1),组件三态自动跟随",
|
||||
"clickable: true 时已完成步骤可点击回跳并写回信号,配合 onChange 同步业务状态",
|
||||
"clickable: true 时已完成步骤可点击回跳并写回信号,配合 onChange 同步业务状态;点圆点(键盘可 Tab)或标题文字都算,一次点击只回调一次",
|
||||
"某步失败时给该项加 status: 'error':圆点变红加感叹号、标题标红、读屏读「第 N 步:xx(失败)」;它既不进 done 也不占 current,因此不参与回跳——「失败」和「还没走到」只能是两种视觉",
|
||||
"侧栏或移动端传 direction: 'vertical':圆点成一列、说明文字紧贴右侧,步与步之间由圆点下方的竖线相连(末步不画线,已完成的段用主色标进度)"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1172,6 +1172,15 @@ button.ua-step-dot:hover {
|
||||
color: var(--ua-color-accent);
|
||||
box-shadow: var(--ua-focus-ring);
|
||||
}
|
||||
.ua-step-error .ua-step-dot {
|
||||
border-color: var(--ua-color-danger);
|
||||
background: color-mix(in srgb, var(--ua-color-danger) var(--ua-alpha-soft), transparent); /* 失败步骤只给淡底,不抢当前步的光环 */
|
||||
color: var(--ua-color-danger);
|
||||
font-weight: var(--ua-weight-semibold);
|
||||
}
|
||||
.ua-step.is-clickable .ua-step-text {
|
||||
cursor: pointer; /* 整条可点:文字区给出与圆点一致的手型 */
|
||||
}
|
||||
.ua-step-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1187,6 +1196,9 @@ button.ua-step-dot:hover {
|
||||
color: var(--ua-color-text);
|
||||
font-weight: var(--ua-weight-semibold);
|
||||
}
|
||||
.ua-step-error .ua-step-label {
|
||||
color: var(--ua-color-danger); /* 标题跟着标红:只标圆点时正文仍像正常步骤 */
|
||||
}
|
||||
.ua-step-desc {
|
||||
font-size: var(--ua-font-size-sm);
|
||||
color: var(--ua-color-text-muted);
|
||||
|
||||
Reference in New Issue
Block a user