2026-03-29 09:17:14 +07:00

67 lines
2.0 KiB
TypeScript

import type { JSX } from "solid-js";
import { splitProps } from "solid-js";
export interface ProgressProps extends JSX.HTMLAttributes<HTMLDivElement> {
/** Current value. Pass null for indeterminate. */
value?: number | null;
/** Maximum value. @default 100 */
max?: number;
/** Custom label function for aria-valuetext. */
getValueLabel?: (value: number, max: number) => string;
}
/**
* Displays the progress of a task. Supports determinate and indeterminate states.
* Indeterminate when value is null or undefined.
*/
export function Progress(props: ProgressProps): JSX.Element {
const [local, rest] = splitProps(props, ["value", "max", "getValueLabel"]);
const max = () => local.max ?? 100;
const isIndeterminate = () => local.value == null;
const currentValue = (): number | null => local.value ?? null;
const percentage = () => {
const v = currentValue();
if (v === null) return null;
return Math.round((v / max()) * 100);
};
const valueLabel = (): string | undefined => {
const v = currentValue();
if (v === null) return undefined;
if (local.getValueLabel) return local.getValueLabel(v, max());
const pct = percentage();
return pct === null ? undefined : `${pct}%`;
};
const dataState = (): string => {
return isIndeterminate() ? "indeterminate" : "complete";
};
const valueNow = (): number | undefined => {
const v = currentValue();
return isIndeterminate() || v === null ? undefined : v;
};
const dataValue = (): number | undefined => {
const v = currentValue();
return isIndeterminate() || v === null ? undefined : v;
};
return (
// biome-ignore lint/a11y/useFocusableInteractive: progressbar is read-only, not interactive
<div
role="progressbar"
aria-valuemin={0}
aria-valuemax={max()}
aria-valuenow={valueNow()}
aria-valuetext={valueLabel()}
data-state={dataState()}
data-value={dataValue()}
data-max={max()}
{...rest}
/>
);
}