77 lines
2.1 KiB
TypeScript
77 lines
2.1 KiB
TypeScript
// components/ui/ButtonGroup.tsx
|
|
'use client';
|
|
|
|
import * as React from 'react';
|
|
import clsx from 'clsx';
|
|
|
|
export type ButtonGroupOption = {
|
|
value: string;
|
|
label: React.ReactNode;
|
|
icon?: React.ReactNode;
|
|
};
|
|
|
|
type ButtonGroupProps = {
|
|
options: ButtonGroupOption[];
|
|
value: string;
|
|
onChange?: (next: string) => void;
|
|
className?: string;
|
|
/** Wenn true: nur Anzeige, keine Klicks */
|
|
readOnly?: boolean;
|
|
};
|
|
|
|
export default function ButtonGroup({
|
|
options,
|
|
value,
|
|
onChange,
|
|
className,
|
|
readOnly = false,
|
|
}: ButtonGroupProps) {
|
|
return (
|
|
<span
|
|
className={clsx(
|
|
'isolate inline-flex rounded-md shadow-xs dark:shadow-none',
|
|
className,
|
|
)}
|
|
>
|
|
{options.map((opt, idx) => {
|
|
const isFirst = idx === 0;
|
|
const isLast = idx === options.length - 1;
|
|
const isActive = opt.value === value;
|
|
|
|
return (
|
|
<button
|
|
key={opt.value}
|
|
type="button"
|
|
disabled={readOnly}
|
|
aria-pressed={isActive}
|
|
className={clsx(
|
|
'relative inline-flex items-center px-3 py-2 text-sm font-semibold focus:z-10 inset-ring-1',
|
|
// 🔹 aktiver Button
|
|
isActive
|
|
? 'bg-indigo-600 text-white inset-ring-indigo-600 hover:bg-indigo-500 dark:bg-indigo-500 dark:text-white dark:inset-ring-indigo-400'
|
|
: 'bg-white text-gray-900 inset-ring-gray-300 hover:bg-gray-50 dark:bg-white/10 dark:text-white dark:inset-ring-gray-700 dark:hover:bg-white/20',
|
|
!isFirst && '-ml-px',
|
|
isFirst && 'rounded-l-md',
|
|
isLast && 'rounded-r-md',
|
|
readOnly && 'cursor-default opacity-90',
|
|
)}
|
|
onClick={() => {
|
|
if (readOnly) return;
|
|
if (!onChange) return;
|
|
if (opt.value === value) return;
|
|
onChange(opt.value);
|
|
}}
|
|
>
|
|
{opt.icon && (
|
|
<span className="mr-1.5 inline-flex items-center">
|
|
{opt.icon}
|
|
</span>
|
|
)}
|
|
<span>{opt.label}</span>
|
|
</button>
|
|
);
|
|
})}
|
|
</span>
|
|
);
|
|
}
|