File size: 1,960 Bytes
9b906ea | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | import type { ReactNode } from "react";
import { cn } from "#/utils/utils";
interface SegmentedToggleOption<T extends string> {
value: T;
label: string;
icon?: ReactNode;
}
interface SegmentedToggleProps<T extends string> {
value: T;
options: SegmentedToggleOption<T>[];
onChange: (value: T) => void;
ariaLabel: string;
testId?: string;
className?: string;
/** Stretch the control and give each option an equal share of the width. */
equalWidth?: boolean;
}
/**
* Lightweight 2-state segmented control used for the files-tab toggles
* ("Rich"/"Plain") and a few other compact two-way choices.
*/
export function SegmentedToggle<T extends string>({
value,
options,
onChange,
ariaLabel,
testId,
className,
equalWidth = false,
}: SegmentedToggleProps<T>) {
return (
<div
role="radiogroup"
aria-label={ariaLabel}
data-testid={testId}
className={cn(
equalWidth ? "flex w-full" : "inline-flex",
"items-center rounded-md bg-[var(--oh-surface-raised)] p-0.5 text-xs",
className,
)}
>
{options.map((option) => {
const isActive = option.value === value;
return (
<button
key={option.value}
type="button"
role="radio"
aria-checked={isActive}
data-testid={
testId ? `${testId}-option-${option.value}` : undefined
}
onClick={() => onChange(option.value)}
className={cn(
"inline-flex items-center gap-1.5 px-2 py-0.5 rounded cursor-pointer transition-colors",
equalWidth && "flex-1 justify-center text-center",
isActive
? "bg-[var(--oh-interactive-hover)] text-white"
: "text-[var(--oh-muted)] hover:text-white",
)}
>
{option.icon}
{option.label}
</button>
);
})}
</div>
);
}
|