import * as stylex from "@stylexjs/stylex"; import type { CompiledStyles } from "@stylexjs/stylex/lib/StyleXTypes"; // This seems like a hack import { type CSSProperties, memo } from "react"; import { controlColor } from "./theme.stylex"; import { color, size } from "./tokens.stylex"; /** * Omit `max` and `value` to make the progress bar indeterminate. */ export interface ProgressBarProps { /** * This attribute specifies how much of the task that has been completed. It must be a valid floating point number between 0 and max, or between 0 and 1 if max is omitted. * If there is no value attribute, the progress bar is indeterminate; this indicates that an activity is ongoing with no indication of how long it is expected to take. */ value?: number; /** * This attribute describes how much work the task indicated by the progress element requires. * The max attribute, if present, must have a value greater than 0 and be a valid floating point number. The default value is `1`. */ max?: number; /** * When set to true, will be `display: inline;` */ inline?: boolean; label?: string; error?: boolean; } const styles = stylex.create({ bar: { display: "block", height: size.px1, width: "100%", backgroundColor: color.gray400, }, inline: { display: "inline", }, error: { backgroundColor: controlColor.progressErrorBackground, }, indeterminate: { overflow: "hidden", }, }); const indeterminate = stylex.keyframes({ "0%": { transform: "translateX(0) scaleX(0)", }, "40%": { transform: "translateX(0) scaleX(0.4)", }, "100%": { transform: "translateX(100%) scaleX(0.5)", }, }); const innerStyles = stylex.create({ inner: { height: "100%", width: "var(--progress-width)", transition: "width 0.25s ease-in-out", backgroundColor: controlColor.buttonTertiaryColor, }, error: { backgroundColor: controlColor.progressErrorBackground, }, indeterminate: { width: "100%", transformOrigin: "0% 50%", animationName: indeterminate, animationDuration: "2s", animationTimingFunction: "linear", animationIterationCount: "infinite", }, }); const clamp = (value: number, min: number, max: number) => value < min ? min : value > max ? max : value; export default memo(function ProgressBar(props: ProgressBarProps) { // Not using because it was a hell to style. This is far more reliable. if (props.max === undefined && props.value === undefined) { const outerProps = stylex.props( styles.bar, props.inline && styles.inline, styles.indeterminate, ); const innerProps = stylex.props( innerStyles.inner, props.error && innerStyles.error, innerStyles.indeterminate, ); return ( // biome-ignore lint/a11y/useFocusableInteractive: Nope
); } const max = props.max ?? 1; const value = props.value ?? 0; const percentage = Math.round(clamp(value / max, 0, 1) * 100); const style = { // biome-ignore lint/style/useTemplate: Nope "--progress-width": percentage + "%", } as unknown as CSSProperties; return ( // biome-ignore lint/a11y/useFocusableInteractive: Nope // biome-ignore lint/a11y/useSemanticElements: Normal progress bar is a hell to style
); });