78 lines
2.5 KiB
TypeScript
78 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
type ToggleButtonOption<T extends string> = {
|
|
value: T;
|
|
label: string;
|
|
className?: string;
|
|
activeClassName?: string;
|
|
inactiveClassName?: string;
|
|
disabled?: boolean;
|
|
ariaLabel?: string;
|
|
onClick?: () => void;
|
|
};
|
|
|
|
type ToggleButtonGroupProps<T extends string> = {
|
|
value?: T | null;
|
|
options: ToggleButtonOption<T>[];
|
|
onChange?: (value: T) => void;
|
|
ariaLabel?: string;
|
|
role?: "group" | "radiogroup";
|
|
className?: string;
|
|
buttonBaseClassName?: string;
|
|
buttonClassName?: string;
|
|
activeClassName?: string;
|
|
inactiveClassName?: string;
|
|
sizeClassName?: string;
|
|
};
|
|
|
|
function joinClasses(parts: Array<string | undefined | null | false>) {
|
|
return parts.filter(Boolean).join(" ");
|
|
}
|
|
|
|
export default function ToggleButtonGroup<T extends string>({
|
|
value,
|
|
options,
|
|
onChange,
|
|
ariaLabel,
|
|
role = "group",
|
|
className = "flex items-center gap-0 rounded-full border border-accent-weak bg-panel",
|
|
buttonBaseClassName = "rounded-full",
|
|
buttonClassName,
|
|
activeClassName = "btn-accent",
|
|
inactiveClassName = "text-muted",
|
|
sizeClassName = "px-3 py-2 text-xs font-semibold"
|
|
}: ToggleButtonGroupProps<T>) {
|
|
return (
|
|
<div className={className} role={role} aria-label={ariaLabel}>
|
|
{options.map(option => {
|
|
const isActive = value != null && option.value === value;
|
|
const onClick = option.onClick
|
|
? option.onClick
|
|
: onChange
|
|
? () => onChange(option.value)
|
|
: undefined;
|
|
|
|
return (
|
|
<button
|
|
key={option.value}
|
|
type="button"
|
|
className={joinClasses([
|
|
buttonBaseClassName,
|
|
sizeClassName,
|
|
buttonClassName,
|
|
isActive ? option.activeClassName ?? activeClassName : option.inactiveClassName ?? inactiveClassName,
|
|
option.className
|
|
])}
|
|
onClick={onClick}
|
|
disabled={option.disabled}
|
|
aria-pressed={value != null ? isActive : undefined}
|
|
aria-label={option.ariaLabel}
|
|
>
|
|
{option.label}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|