import { clsx } from 'clsx'; import React, { FunctionComponent, ReactNode, useState, useEffect, useMemo } from 'react'; import { PromoCardProps } from './PromoCard'; import PromoCardContext from './PromoCardContext'; export type AriaRoleRadioGroup = 'radiogroup'; export interface PromoCardGroupProps { /** * Optional class name(s) to add to the group container. */ className?: string; /** * The PromoCard components to display inside the group. */ children: ReactNode; /** * The default checked for the group. */ defaultChecked?: string; /** * Optional ID to add to the group container. */ id?: string; /** * Whether the group is disabled or not. */ isDisabled?: boolean; /** * Optional label to display above the group. */ label?: string; /** * Optional function to call when the group value changes. */ onChange?: (value: string) => void; /** * Optional ID to add to the group container for testing purposes. */ testId?: string; } /** * PromoCardGroup component. * * A PromoCardGroup is a container for PromoCard components that allows the user to select one or more * cards from a group. It can be used to display a set of related options, and can be customized with * various props to suit different use cases. * * @param {ReactNode} children - The PromoCard components to display inside the group. * @param {string} className - Optional class name(s) to add to the group container. * @param {string} defaultChecked - The default value for the group. * @param {string} id - Optional ID to add to the group container. * @param {boolean} isDisabled=false - Whether the group is disabled or not. * @param {string} label - Optional label to display above the group. * @param {Function} onChange - Optional function to call when the group value changes. * @param {string} testId - Optional ID to add to the group container for testing purposes. * @returns {React.JSX.Element} - The PromoCardGroup component. */ const PromoCardGroup: FunctionComponent = ({ children, className, defaultChecked = '', id, isDisabled = false, onChange = () => {}, testId, }) => { const [state, setState] = useState(defaultChecked); const [containerRole, setContainerRole] = useState(null); /** * The context value for the PromoCardGroup. * * The context value is an object that contains the current state of the * group, whether the group is disabled or not, and a function to call when * the group value changes. This value is used to provide context to child * PromoCard components, allowing them to interact with the group and update * its state. */ const contextValue = useMemo(() => { const handleOnChange = (value: string) => { setState(value); onChange(value); }; return { state, isDisabled, onChange: handleOnChange }; }, [state, isDisabled, onChange]); const commonClasses = clsx( { 'np-CardGroup': true, 'is-disabled': isDisabled, }, className, ); const commonProps = { className: commonClasses, id, 'data-testid': testId, role: containerRole as AriaRoleRadioGroup | undefined, // Add the role attribute here }; useEffect(() => { setState(defaultChecked); // Collect an array of types from the children PromoCard components const types = React.Children.map(children, (child) => { if (React.isValidElement(child) && child.props.type) { return child.props.type; } return null; })?.filter((type): type is 'radio' | 'checkbox' => type !== null && type !== undefined) ?? []; // Check if all types are the same const allTypesAreTheSame = types.every((type) => type === types[0]); // If all types are the same and the type is 'radio', set the container role setContainerRole(allTypesAreTheSame && types[0] === 'radio' ? 'radiogroup' : null); }, [defaultChecked, children]); return (
{children}
); }; export default React.memo(PromoCardGroup);