| 'use client'; |
|
|
| import { motion } from 'framer-motion'; |
| import { cn } from '@/lib/utils'; |
|
|
| export interface GlassToggleChipProps { |
| selected: boolean; |
| onToggle: () => void; |
| label: string; |
| icon?: string; |
| disabled?: boolean; |
| className?: string; |
| } |
|
|
| export function GlassToggleChip({ |
| selected, |
| onToggle, |
| label, |
| icon, |
| disabled = false, |
| className, |
| }: GlassToggleChipProps) { |
| return ( |
| <motion.button |
| onClick={onToggle} |
| disabled={disabled} |
| className={cn( |
| 'inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full', |
| 'text-sm font-medium', |
| 'border transition-all duration-200', |
| 'focus-ring', |
| 'disabled:opacity-50 disabled:cursor-not-allowed', |
| selected |
| ? cn( |
| 'bg-[var(--suggestion-bg)] border-[var(--suggestion-border)]', |
| 'text-[var(--suggestion-text)]' |
| ) |
| : cn( |
| 'bg-[var(--glass-bg-muted)] border-[var(--glass-border-subtle)]', |
| 'text-[var(--foreground)]/60', |
| 'hover:bg-[var(--glass-bg)] hover:text-[var(--foreground)]/80' |
| ), |
| className |
| )} |
| whileHover={{ scale: disabled ? 1 : 1.02 }} |
| whileTap={{ scale: disabled ? 1 : 0.98 }} |
| > |
| {icon && <span>{icon}</span>} |
| <span>{label}</span> |
| <motion.span |
| initial={false} |
| animate={{ |
| scale: selected ? 1 : 0, |
| opacity: selected ? 1 : 0, |
| }} |
| transition={{ duration: 0.15 }} |
| className="w-4 h-4 flex items-center justify-center" |
| > |
| <svg |
| className="w-3 h-3 text-[var(--suggestion-accent)]" |
| fill="none" |
| viewBox="0 0 24 24" |
| stroke="currentColor" |
| strokeWidth={3} |
| > |
| <path |
| strokeLinecap="round" |
| strokeLinejoin="round" |
| d="M5 13l4 4L19 7" |
| /> |
| </svg> |
| </motion.span> |
| </motion.button> |
| ); |
| } |
|
|
| |
| export interface ToggleChipGroupProps<T extends string> { |
| values: T[]; |
| selected: T[]; |
| onChange: (selected: T[]) => void; |
| getLabel: (value: T) => string; |
| getIcon?: (value: T) => string | undefined; |
| disabled?: boolean; |
| className?: string; |
| } |
|
|
| export function ToggleChipGroup<T extends string>({ |
| values, |
| selected, |
| onChange, |
| getLabel, |
| getIcon, |
| disabled = false, |
| className, |
| }: ToggleChipGroupProps<T>) { |
| const handleToggle = (value: T) => { |
| if (selected.includes(value)) { |
| |
| if (selected.length > 1) { |
| onChange(selected.filter((v) => v !== value)); |
| } |
| } else { |
| onChange([...selected, value]); |
| } |
| }; |
|
|
| return ( |
| <div className={cn('flex flex-wrap gap-2', className)}> |
| {values.map((value) => ( |
| <GlassToggleChip |
| key={value} |
| selected={selected.includes(value)} |
| onToggle={() => handleToggle(value)} |
| label={getLabel(value)} |
| icon={getIcon?.(value)} |
| disabled={disabled} |
| /> |
| ))} |
| </div> |
| ); |
| } |
|
|