196 lines
5.3 KiB
TypeScript
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>
|
|
);
|
|
};
|