diff --git a/fakemui/fakemui/inputs/ToggleButton.tsx b/fakemui/fakemui/inputs/ToggleButton.tsx index f80fcca3a..b5b4a49d6 100644 --- a/fakemui/fakemui/inputs/ToggleButton.tsx +++ b/fakemui/fakemui/inputs/ToggleButton.tsx @@ -1,30 +1,195 @@ -import React, { forwardRef } from 'react' +import React, { forwardRef, Children, cloneElement, isValidElement, createContext, useContext } from 'react' -export interface ToggleButtonProps extends React.ButtonHTMLAttributes { - children?: React.ReactNode - selected?: boolean +/** + * Context for managing toggle button group state + */ +interface ToggleButtonGroupContextValue { + value: string | string[] | null + exclusive: boolean + disabled: boolean + size: 'small' | 'medium' | 'large' + onChange: (event: React.MouseEvent, value: string) => void } +const ToggleButtonGroupContext = createContext(null) + +/** + * Hook to access toggle button group context + */ +export const useToggleButtonGroup = () => useContext(ToggleButtonGroupContext) + +export interface ToggleButtonProps extends Omit, 'value'> { + children?: React.ReactNode + /** Whether this button is selected */ + selected?: boolean + /** Value for this button in a group */ + value?: string + /** Button size */ + size?: 'small' | 'medium' | 'large' + /** Full width button */ + fullWidth?: boolean +} + +/** + * ToggleButton - A button that can be toggled on/off + * Can be used standalone or within a ToggleButtonGroup + */ export const ToggleButton = forwardRef( - ({ children, selected, className = '', ...props }, ref) => ( - - ) + ({ children, selected, value, size = 'medium', fullWidth, disabled, className = '', onClick, ...props }, ref) => { + const groupContext = useToggleButtonGroup() + + // Use context values if in a group + const isSelected = groupContext + ? (Array.isArray(groupContext.value) + ? groupContext.value.includes(value || '') + : groupContext.value === value) + : selected + const buttonSize = groupContext?.size || size + const isDisabled = groupContext?.disabled || disabled + + const handleClick = (event: React.MouseEvent) => { + if (groupContext && value !== undefined) { + groupContext.onChange(event, value) + } + onClick?.(event) + } + + const classes = [ + 'toggle-btn', + isSelected ? 'toggle-btn--selected' : '', + `toggle-btn--${buttonSize}`, + fullWidth ? 'toggle-btn--full-width' : '', + className, + ].filter(Boolean).join(' ') + + return ( + + ) + } ) ToggleButton.displayName = 'ToggleButton' -export interface ToggleButtonGroupProps extends React.HTMLAttributes { +export interface ToggleButtonGroupProps extends Omit, 'onChange'> { children?: React.ReactNode + /** Current selected value(s) */ + value?: string | string[] | null + /** Default value(s) if uncontrolled */ + defaultValue?: string | string[] | null + /** Called when selection changes */ + onChange?: (event: React.MouseEvent, value: string | string[] | null) => void + /** Only allow one selection at a time */ + exclusive?: boolean + /** Disable all buttons */ + disabled?: boolean + /** Button size for all children */ + size?: 'small' | 'medium' | 'large' + /** Stack buttons vertically */ + orientation?: 'horizontal' | 'vertical' + /** Full width group */ + fullWidth?: boolean + /** Color theme */ + color?: 'primary' | 'secondary' | 'standard' } -export const ToggleButtonGroup: React.FC = ({ children, className = '', ...props }) => ( -
- {children} -
+/** + * ToggleButtonGroup - Groups toggle buttons with shared state management + * + * @example + * ```tsx + * + * Left + * Center + * Right + * + * ``` + */ +export const ToggleButtonGroup = forwardRef( + ( + { + children, + value, + defaultValue, + onChange, + exclusive = false, + disabled = false, + size = 'medium', + orientation = 'horizontal', + fullWidth = false, + color = 'standard', + className = '', + ...props + }, + ref + ) => { + // Support uncontrolled usage + const [internalValue, setInternalValue] = React.useState( + defaultValue ?? (exclusive ? null : []) + ) + const currentValue = value !== undefined ? value : internalValue + + const handleChange = (event: React.MouseEvent, buttonValue: string) => { + let newValue: string | string[] | null + + if (exclusive) { + // Single selection mode - toggle off if clicking same, otherwise select new + newValue = currentValue === buttonValue ? null : buttonValue + } else { + // Multiple selection mode + const currentArray = Array.isArray(currentValue) ? currentValue : [] + if (currentArray.includes(buttonValue)) { + newValue = currentArray.filter(v => v !== buttonValue) + } else { + newValue = [...currentArray, buttonValue] + } + } + + if (value === undefined) { + setInternalValue(newValue) + } + onChange?.(event, newValue) + } + + const contextValue: ToggleButtonGroupContextValue = { + value: currentValue, + exclusive, + disabled, + size, + onChange: handleChange, + } + + const classes = [ + 'toggle-btn-group', + orientation === 'vertical' ? 'toggle-btn-group--vertical' : '', + fullWidth ? 'toggle-btn-group--full-width' : '', + `toggle-btn-group--${color}`, + className, + ].filter(Boolean).join(' ') + + return ( + +
+ {children} +
+
+ ) + } ) + +ToggleButtonGroup.displayName = 'ToggleButtonGroup'