Files
dndbeyond_src/ddb_main/components/CheckboxGroup/CheckboxGroup.tsx
T

196 lines
5.3 KiB
TypeScript

import clsx from "clsx";
import { orderBy } from "lodash";
import { ChangeEvent, FC, HTMLAttributes, ReactNode } from "react";
import { Accordion, AccordionProps } from "../Accordion";
import { Checkbox } from "../Checkbox";
import styles from "./styles.module.css";
export interface CheckboxInfo {
description?: string;
disabled?: boolean;
initiallyEnabled: boolean;
label: string;
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
onChangeConfirm?: (
newIsEnabled: boolean,
accept: () => void,
reject: () => void
) => void;
sortOrder?: number;
value?: string | number;
}
interface CheckboxGroupProps
extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
accordionDefaultOpen?: boolean;
accordionSummary?: string;
checkboxes: Array<CheckboxInfo>;
disabled?: boolean;
description?: ReactNode;
name: string;
onToggleAll?: (e: ChangeEvent<HTMLInputElement>) => void;
showAccordion?: boolean;
themed?: boolean;
toggleAllLabel?: string;
title: ReactNode;
variant?: "default" | "inline" | "collapse";
}
export const CheckboxGroup: FC<CheckboxGroupProps> = ({
accordionSummary = "Options",
accordionDefaultOpen,
checkboxes,
className,
disabled,
description,
name,
onToggleAll,
showAccordion,
themed,
title,
toggleAllLabel = "Enable All Options",
variant = "default",
...props
}) => {
const isInlineVariant = variant === "inline";
const isCollapseVariant = variant === "collapse";
const sortedCheckboxes = orderBy(checkboxes, "sortOrder");
const allChecksEnabled = sortedCheckboxes.every(
(checkbox) => checkbox.initiallyEnabled
);
// Determine whether to render a div or an Accordion
const ContentWrapper = showAccordion ? Accordion : "div";
// Set props specific to Accordion if needed
const accordionProps = showAccordion
? {
summary: accordionSummary,
forceShow: accordionDefaultOpen,
variant: "text",
}
: {};
const checkboxList = sortedCheckboxes.map((checkbox, i) => {
const id = `${name}-${i}`;
return (
<div
key={i}
className={styles.checkboxOption}
data-testid="form-checkbox-group"
>
<Checkbox
className={styles.checkbox}
id={id}
name={name}
checked={checkbox.initiallyEnabled}
disabled={disabled || checkbox.disabled}
themed={themed}
onChange={checkbox.onChange}
onChangeConfirm={checkbox.onChangeConfirm}
value={checkbox.value}
/>
<div className={styles.optionContent}>
<label className={styles.label} htmlFor={id}>
{checkbox.label}
</label>
{checkbox.description && (
<div
className={styles.description}
dangerouslySetInnerHTML={{ __html: checkbox.description }}
/>
)}
</div>
</div>
);
});
/** Handle Collapse Variant separately since it's structure is different */
if (isCollapseVariant)
return (
<Accordion
className={clsx([
styles.checkboxGroup,
styles.collapse,
themed && styles.themed,
className,
])}
summary={<p className={styles.checkboxGroupTitle}>{title}</p>}
variant="text"
forceShow={accordionDefaultOpen}
{...props}
>
{description && (
<div className={styles.checkboxGroupDescription}>{description}</div>
)}
{onToggleAll && (
<Checkbox
className={clsx([styles.checkbox, styles.toggleAll])}
id={`${name}-toggle-all`}
checked={allChecksEnabled}
aria-label="Check/Uncheck all"
onChange={onToggleAll}
themed={themed}
label={toggleAllLabel}
/>
)}
{checkboxList}
</Accordion>
);
return (
<div
className={clsx([
styles.checkboxGroup,
styles[variant],
themed && styles.themed,
className,
])}
{...props}
>
<header>
<div className={styles.checkboxGroupHeader}>
<p className={styles.checkboxGroupTitle}>{title}</p>
{isInlineVariant && checkboxes.length < 2 && (
<Checkbox
className={styles.checkbox}
id={`${name}-0`}
name={name}
checked={checkboxes[0].initiallyEnabled}
disabled={disabled || checkboxes[0].disabled}
themed={themed}
onChange={checkboxes[0].onChange}
onChangeConfirm={checkboxes[0].onChangeConfirm}
value={checkboxes[0].value}
/>
)}
</div>
{description && (
<div className={styles.checkboxGroupDescription}>{description}</div>
)}
</header>
{!isInlineVariant && onToggleAll && (
<Checkbox
className={clsx([styles.checkbox, styles.toggleAll])}
id={`${name}-toggle-all`}
checked={allChecksEnabled}
aria-label="Check/Uncheck all"
onChange={onToggleAll}
themed={themed}
label={toggleAllLabel}
/>
)}
{!isInlineVariant && (
<ContentWrapper
className={styles.checkboxOptions}
{...(accordionProps as AccordionProps)}
>
{checkboxList}
</ContentWrapper>
)}
</div>
);
};