/**
* Accessibility utility functions for fpkit components.
*
* These utilities support WCAG 2.1 Level AA compliance for disabled states
* and other accessibility features.
*/
/**
* CSS properties for disabled state styling.
*
* Returns a CSS-in-JS compatible style object for programmatic styling
* of disabled elements. Meets WCAG 1.4.3 contrast minimum (3:1 for UI components).
*
* @param {boolean} isDisabled - Whether the element is disabled
* @returns {React.CSSProperties} Style object with disabled state properties
*
* @example
* const MyComponent = ({ disabled }) => {
* const disabledStyles = getDisabledStyles(disabled);
* return
Content
;
* };
*
* @see {@link https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum WCAG 1.4.3 - Contrast (Minimum)}
*/
export function getDisabledStyles(isDisabled: boolean): React.CSSProperties {
if (!isDisabled) {
return {};
}
return {
// hsl(0 0% 40%) = #666666, provides 3:1 contrast on white (#ffffff)
color: 'var(--disabled-color, hsl(0 0% 40%))',
// CSS custom properties require string casting for TypeScript compatibility
opacity: 'var(--disabled-opacity, 0.6)' as unknown as number,
cursor: 'var(--disabled-cursor, not-allowed)' as unknown as React.CSSProperties['cursor'],
};
}
/**
* Wraps a single event handler to prevent execution when disabled.
*
* This is a generic utility for advanced use cases where the `useDisabledState`
* hook cannot be used (e.g., class components, custom event types).
*
* @template E - The React synthetic event type
* @param {Function | undefined} handler - The event handler to wrap
* @param {boolean} isDisabled - Whether to prevent handler execution
* @returns {Function | undefined} Wrapped handler or undefined if no handler provided
*
* @example
* // Custom event handler in class component
* class MyComponent extends React.Component {
* handleCustomEvent = wrapEventHandler(this.onCustomEvent, this.props.disabled);
* }
*
* @example
* // Custom event type not supported by useDisabledState
* const handleCustom = wrapEventHandler(
* (e: CustomEvent) => console.log('custom'),
* disabled
* );
*/
export function wrapEventHandler(
handler: ((event: E) => void) | undefined,
isDisabled: boolean
): ((event: E) => void) | undefined {
if (!handler) {
return undefined;
}
return (event: E) => {
if (isDisabled) {
event.preventDefault();
event.stopPropagation();
return;
}
handler(event);
};
}
/**
* Resolves the effective disabled state from multiple props.
*
* Handles backward compatibility with legacy `isDisabled` prop.
* The `disabled` prop takes precedence when both are provided.
*
* @param {boolean | undefined} disabled - Modern disabled prop
* @param {boolean | undefined} isDisabled - Legacy disabled prop (deprecated)
* @returns {boolean} The resolved disabled state (defaults to false)
*
* @example
* const MyComponent = ({ disabled, isDisabled }) => {
* const isActuallyDisabled = resolveDisabledState(disabled, isDisabled);
* // Use isActuallyDisabled for logic
* };
*
* @example
* // disabled takes precedence
* resolveDisabledState(true, false); // true
* resolveDisabledState(false, true); // false
* resolveDisabledState(undefined, true); // true
*/
export function resolveDisabledState(
disabled: boolean | undefined,
isDisabled: boolean | undefined
): boolean {
// disabled prop takes precedence, fall back to isDisabled, default to false
return disabled ?? isDisabled ?? false;
}