coder/site/src/components/Conditionals/ChooseOne.tsx

49 lines
1.9 KiB
TypeScript

import { Children, PropsWithChildren } from "react";
export interface CondProps {
condition?: boolean;
}
/**
* Wrapper component that attaches a condition to a child component so that ChooseOne can
* determine which child to render. The last Cond in a ChooseOne is the fallback case and
* should not have a condition.
* @param condition boolean expression indicating whether the child should be rendered, or undefined
* @returns child. Note that Cond alone does not enforce the condition; it should be used inside ChooseOne.
*/
export const Cond = ({
children,
}: PropsWithChildren<CondProps>): JSX.Element => {
return <>{children}</>;
};
/**
* Wrapper component for rendering exactly one of its children. Wrap each child in Cond to associate it
* with a condition under which it should be rendered. If no conditions are met, the final child
* will be rendered.
* @returns one of its children, or null if there are no children
* @throws an error if its last child has a condition prop, or any non-final children do not have a condition prop
*/
export const ChooseOne = ({
children,
}: PropsWithChildren): JSX.Element | null => {
const childArray = Children.toArray(children) as JSX.Element[];
if (childArray.length === 0) {
return null;
}
const conditionedOptions = childArray.slice(0, childArray.length - 1);
const defaultCase = childArray[childArray.length - 1];
if (defaultCase.props.condition !== undefined) {
throw new Error(
"The last Cond in a ChooseOne was given a condition prop, but it is the default case.",
);
}
if (conditionedOptions.some((cond) => cond.props.condition === undefined)) {
throw new Error(
"A non-final Cond in a ChooseOne does not have a condition prop or the prop is undefined.",
);
}
const chosen = conditionedOptions.find((child) => child.props.condition);
return chosen ?? defaultCase;
};