iter(g284): 新增 progressRing 环形进度组件
This commit is contained in:
@@ -85,3 +85,4 @@ export { loading } from './loading.js';
|
||||
export { relativeTime } from './relativeTime.js';
|
||||
export { sparkline } from './sparkline.js';
|
||||
export { heatmap } from './heatmap.js';
|
||||
export { progressRing } from './progressRing.js';
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { effect, h } from '@ui-arch/frame';
|
||||
import type { ProgressRingProps } from './types.js';
|
||||
|
||||
const NS = 'http://www.w3.org/2000/svg';
|
||||
// 缺省尺寸与环宽:96px 装得下百分比数字,8px 环宽在小尺寸下也不显笨
|
||||
const SIZE = 96;
|
||||
const THICK = 8;
|
||||
|
||||
/*** 建 SVG 元素并写属性:svg 必须 createElementNS,属性只能 setAttribute ***/
|
||||
function svgEl(tag: string, attrs: Record<string, string | number>): SVGElement {
|
||||
const el = document.createElementNS(NS, tag);
|
||||
for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v));
|
||||
return el;
|
||||
}
|
||||
|
||||
/*** 环形进度:SVG 双圆(底环 + 进度弧)靠 dasharray 画弧,value 变化时弧长与文字同步 ***/
|
||||
export function progressRing(props: ProgressRingProps): HTMLElement {
|
||||
const size = props.size ?? SIZE;
|
||||
const thick = props.thickness ?? THICK;
|
||||
const mid = size / 2;
|
||||
const r = (size - thick) / 2; // 半径要扣掉环宽,否则弧会超出画布
|
||||
const c = 2 * Math.PI * r; // 周长:dasharray 的分母
|
||||
const read = (): number => (typeof props.value === 'function' ? props.value() : props.value); // 数值 / 信号同一口径
|
||||
const svg = svgEl('svg', { viewBox: `0 0 ${size} ${size}`, width: size, height: size, 'aria-hidden': 'true' });
|
||||
const track = svgEl('circle', { cx: mid, cy: mid, r, fill: 'none', 'stroke-width': thick, class: 'ua-ring-track' });
|
||||
// 弧从 12 点方向顺时针长:整圆旋转 -90°,否则默认从 3 点位置起步
|
||||
const arc = svgEl('circle', {
|
||||
cx: mid, cy: mid, r, fill: 'none', 'stroke-width': thick,
|
||||
class: 'ua-ring-arc', 'stroke-dasharray': `${0} ${c}`, 'stroke-linecap': 'round',
|
||||
transform: `rotate(-90 ${mid} ${mid})`,
|
||||
});
|
||||
const text = h('span', { class: 'ua-ring-text' });
|
||||
const wrap = h(
|
||||
'div',
|
||||
{
|
||||
class: 'ua-ring',
|
||||
role: 'progressbar',
|
||||
'aria-valuemin': '0',
|
||||
'aria-valuemax': '100',
|
||||
style: `width:${size}px;height:${size}px`,
|
||||
},
|
||||
svg,
|
||||
text,
|
||||
);
|
||||
svg.append(track, arc); // 底环先画,进度弧盖在上面
|
||||
|
||||
/*** 重绘:把百分比夹到 0-100,弧长按比例裁,文字与 aria-valuenow 同步 ***/
|
||||
const paint = (): void => {
|
||||
const pct = Math.min(100, Math.max(0, Math.round(read())));
|
||||
arc.setAttribute('stroke-dasharray', `${(c * pct) / 100} ${c}`); // 只画这一段,剩下留空
|
||||
if (props.showText !== false) text.textContent = props.label ?? `${pct}%`;
|
||||
wrap.setAttribute('aria-valuenow', String(pct));
|
||||
};
|
||||
effect(paint);
|
||||
return wrap;
|
||||
}
|
||||
@@ -1110,3 +1110,12 @@ export interface HeatmapProps {
|
||||
ariaLabel?: string; // 可访问名;缺省「最近 N 周活跃度」
|
||||
formatValue?: (count: number, date: string) => string; // 悬停提示文案;缺省「日期 · 数量」
|
||||
}
|
||||
|
||||
/*** 环形进度入参 ***/
|
||||
export interface ProgressRingProps {
|
||||
value: number | Signal<number>; // 当前进度 0-100;超出范围自动夹取,信号变化时自动重绘
|
||||
size?: number; // 外径(px);缺省 96
|
||||
thickness?: number; // 环宽(px);缺省 8
|
||||
showText?: boolean; // 是否显示中心文字;缺省 true(关掉时请自行保证 aria 可读)
|
||||
label?: string; // 中心文字;缺省「N%」
|
||||
}
|
||||
|
||||
@@ -99,7 +99,8 @@
|
||||
"loading(props, ...children):区域加载遮罩,内容原地降透明度 + 转圈文案,带延迟出场避免快请求闪烁",
|
||||
"relativeTime(props):把时间戳渲染成「3 分钟前」并自动刷新,超阈值改显示绝对日期,title 恒给精确值",
|
||||
"sparkline(props):迷你趋势线,把数值序列压成折线(可填充 + 末点标记),无坐标轴无交互",
|
||||
"heatmap(props):日历热力图,按周列 × 星期行铺格子、四档浓度表示当天数量,悬停看明细"
|
||||
"heatmap(props):日历热力图,按周列 × 星期行铺格子、四档浓度表示当天数量,悬停看明细",
|
||||
"progressRing(props):环形进度,SVG 双圆靠 dasharray 画弧 + 中心百分比,value 可传信号自动重绘"
|
||||
],
|
||||
"keyTypes": [
|
||||
"ButtonProps",
|
||||
@@ -4410,7 +4411,47 @@
|
||||
"格子颜色是相对深浅:按序列最大值归一成四档——所以它说的是「哪天更密」,不是绝对值高低",
|
||||
"默认 12 周、11px 格子;嵌进窄卡片就调小 weeks,看不出格子边界就调大 cellSize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "progressRing",
|
||||
"title": "环形进度 ProgressRing",
|
||||
"demo": "sec-ring",
|
||||
"props": [
|
||||
{
|
||||
"n": "value",
|
||||
"t": "number | Signal<number>",
|
||||
"d": "当前进度 0-100;超出范围自动夹取,信号变化时自动重绘"
|
||||
},
|
||||
{
|
||||
"n": "size",
|
||||
"t": "number",
|
||||
"d": "外径(px);缺省 96"
|
||||
},
|
||||
{
|
||||
"n": "thickness",
|
||||
"t": "number",
|
||||
"d": "环宽(px);缺省 8"
|
||||
},
|
||||
{
|
||||
"n": "showText",
|
||||
"t": "boolean",
|
||||
"d": "是否显示中心文字;缺省 true(关掉时请自行保证 aria 可读)"
|
||||
},
|
||||
{
|
||||
"n": "label",
|
||||
"t": "string",
|
||||
"d": "中心文字;缺省「N%」"
|
||||
}
|
||||
],
|
||||
"example": "progressRing({ value: pct, size: 96 })",
|
||||
"steps": [
|
||||
"value 给数字就是静态环,给信号就是活环:不用自己算弧长,组件按 0-100 换算 dasharray",
|
||||
"0 点固定在 12 点方向顺时针走,和进度条的心理模型一致,不需要额外传旋转参数",
|
||||
"要放进表格或卡片角落就用 size: 56 + showText: false 的小环,文字由附近的主体数字承担",
|
||||
"换色在父级设 color:进度弧读 currentColor,底环固定描边色(--ua-color-border),不引入第二个色源",
|
||||
"这是个「环形进度条」,不是仪表盘:只表达 0-100 单一进度,多段占比请用饼图类组件"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5872,6 +5872,35 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); }
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* ---- 环形进度:底环走描边色,进度弧跟随 currentColor,中心文字用等宽数字 ---- */
|
||||
.ua-ring {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--ua-color-accent); /* 弧色跟随文字色:父级改 color 即可换色,不引入第二个色源 */
|
||||
}
|
||||
|
||||
.ua-ring svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ua-ring-track {
|
||||
stroke: var(--ua-color-border);
|
||||
}
|
||||
|
||||
.ua-ring-arc {
|
||||
stroke: currentColor;
|
||||
transition: stroke-dasharray var(--ua-duration-slower) var(--ua-ease); /* 只动 dasharray:不产生空间位移,减少动效规则下无副作用 */
|
||||
}
|
||||
|
||||
.ua-ring-text {
|
||||
position: absolute;
|
||||
color: var(--ua-color-text);
|
||||
font-size: var(--ua-font-size-sm);
|
||||
font-variant-numeric: tabular-nums; /* 等宽数字:进度跳到 100% 时文字不会左右晃 */
|
||||
}
|
||||
|
||||
/* ---- 减少动效:旋转 / 缩放一律退化为瞬时 ----
|
||||
前两类(无限动画、悬停位移)已有专门兜底,但 rotate / scale 过渡属于第三类空间动效:
|
||||
折叠箭头旋转、卡片按压缩放都靠它提供手感。减少动效下把过渡属性收窄成「只留颜色类」,
|
||||
|
||||
Reference in New Issue
Block a user