47 lines
2.0 KiB
TypeScript
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>
|
|
);
|
|
}
|