Files
STCourses/packages/ui/src/primitives.tsx
T
2026-10-30 03:05:49 +01:00

47 lines
2.0 KiB
TypeScript

import type { ButtonHTMLAttributes, CSSProperties, ReactNode } from "react";
export function Pill({ variant = "default", className, ...rest }: { variant?: "default" | "primary" | "ghost" } & ButtonHTMLAttributes<HTMLButtonElement>) {
const cls = ["stc-pill", variant !== "default" && `stc-pill--${variant}`, className].filter(Boolean).join(" ");
return <button type="button" {...rest} className={cls} />;
}
export function SegmentedToggle({ options, value, onChange, label }: {
options: { value: string; label: string }[]; value: string; onChange: (v: string) => void; label: string;
}) {
return (
<div className="stc-seg" role="group" aria-label={label}>
{options.map((o) => (
<button key={o.value} type="button" aria-pressed={o.value === value} onClick={() => onChange(o.value)}>{o.label}</button>
))}
</div>
);
}
export function ProgressBar({ value, dim, label }: { value: number; dim?: boolean; label: string }) {
const pct = Math.round(Math.min(1, Math.max(0, value)) * 100);
return (
<span className={dim ? "stc-bar stc-bar--dim" : "stc-bar"} role="progressbar" aria-label={label} aria-valuemin={0} aria-valuemax={100} aria-valuenow={pct}>
<i style={{ width: `${pct}%` }} />
</span>
);
}
export function FloatingActionBar({ children }: { children: ReactNode }) {
return <div className="stc-dock" role="toolbar">{children}</div>;
}
export function monogram(title: string): string {
const words = title.trim().split(/\s+/).filter(Boolean);
const letters = words.length >= 2 ? [words[0]![0], words[1]![0]] : Array.from(words[0] ?? "?").slice(0, 2);
return letters.join("").toUpperCase();
}
export function CourseCover({ title, image, accent }: { title: string; image?: string; accent?: string }) {
const style = accent ? ({ "--cfg": accent } as CSSProperties) : undefined;
return (
<div className="stc-cover" style={style} role="img" aria-label={title}>
{image ? <img src={image} alt="" /> : <span aria-hidden="true">{monogram(title)}</span>}
</div>
);
}