Skip to main content

Hooks

useCollapsibleState

The useCollapsibleState hook can be used to create custom collapsible rows in a table. It requires an id and an array of forRows to manage the state of the collapsible rows. The hook exposes expanded (whether the current row is expanded), expandedIds an array of expanded rows, and handleClickToggle to manage the state of the collapsible rows.

Result
Loading...
Live Editor
function CustomCollapsibleRow({ id, forRows, children, label, ...props }) {
	const { expanded, handleClickToggle } = useCollapsibleState(forRows);
	return (
		<TableRow {...props}>
			<TableCell colSpan="4" className="p-0">
				<div
					aria-label={label}
					role="button"
					onClick={handleClickToggle}
					className="d-flex align-items-center px-3 py-2 w-100 bg-gray-100 text-gray-800 text-uppercase section-header-md"
				>
					{expanded ? (
						<span className="icon-chevron-up fs-3xl me-2" />
					) : (
						<span className="icon-chevron-down fs-3xl me-2" />
					)}{' '}
					{children}
				</div>
			</TableCell>
		</TableRow>
	);
}

function Example() {
	const [toggleOneRows, setToggleOneRows] = React.useState(['area-1-1']);
	const [toggleTwoRows, setToggleTwoRows] = React.useState(['area-2-1']);
	const [toggleThreeRows, setToggleThreeRows] = React.useState(['area-3-1']);
	const [toggleFourRows, setToggleFourRows] = React.useState(['area-4-1']);

	return (
		<>
			<DropdownButton title="Add Row">
				<Dropdown.Item
					onClick={() => {
						setToggleOneRows([...toggleOneRows, `area-1-${toggleOneRows.length + 1}`]);
					}}
				>
					Plan Changes
				</Dropdown.Item>
				<Dropdown.Item
					onClick={() => {
						setToggleTwoRows([...toggleTwoRows, `area-2-${toggleTwoRows.length + 1}`]);
					}}
				>
					Metrics
				</Dropdown.Item>
				<Dropdown.Item
					onClick={() => {
						setToggleThreeRows([...toggleThreeRows, `area-3-${toggleThreeRows.length + 1}`]);
					}}
				>
					Goals
				</Dropdown.Item>
				<Dropdown.Item
					onClick={() => {
						setToggleFourRows([...toggleFourRows, `area-4-${toggleFourRows.length + 1}`]);
					}}
				>
					Other
				</Dropdown.Item>
			</DropdownButton>

			<Table collapsible>
				<TableHeader>
					<TableRow>
						<TableCell> </TableCell>
						<TableCell>Header 2</TableCell>
						<TableCell>Header 3</TableCell>
						<TableCell>Header 4</TableCell>
					</TableRow>
				</TableHeader>
				<TableBody>
					<CustomCollapsibleRow id="toggle-1" forRows={toggleOneRows} label="Toggle Plan Changes">
						Plan Changes
					</CustomCollapsibleRow>
					{toggleOneRows.map((rowId) => (
						<TableRow key={rowId} id={rowId} collapsible>
							<TableCell colSpan="4">{rowId}</TableCell>
						</TableRow>
					))}

					<CustomCollapsibleRow id="toggle-2" forRows={toggleTwoRows} label="Toggle Metrics">
						Metrics
					</CustomCollapsibleRow>
					{toggleTwoRows.map((rowId) => (
						<TableRow key={rowId} id={rowId} collapsible>
							<TableCell colSpan="4">{rowId}</TableCell>
						</TableRow>
					))}

					<CustomCollapsibleRow id="toggle-3" forRows={toggleThreeRows} label="Toggle Goals">
						Goals
					</CustomCollapsibleRow>
					{toggleThreeRows.map((rowId) => (
						<TableRow key={rowId} id={rowId} collapsible>
							<TableCell colSpan="4">{rowId}</TableCell>
						</TableRow>
					))}

					<CustomCollapsibleRow id="toggle-4" forRows={toggleFourRows} label="Toggle Other">
						Other
					</CustomCollapsibleRow>
					{toggleFourRows.map((rowId) => (
						<TableRow key={rowId} id={rowId} collapsible>
							<TableCell colSpan="4">{rowId}</TableCell>
						</TableRow>
					))}
				</TableBody>
			</Table>
		</>
	);
}

render(<Example />);

"Nested" Collapsible Rows

It's possible to use the hook to create collapsible rows that are conceptually nested within other collapsible rows. Manually calling the setExpandedIds function to remove "nested" rows.

Result
Loading...
Live Editor
function CustomCollapsibleRow({ id, forRows, children, label, nestedRows, ...props }) {
	const { expanded, expandedIds, setExpandedIds, handleClickToggle } = useCollapsibleState(forRows);

	useEffect(() => {
		if (!expanded) {
			setExpandedIds((prev) => prev.filter((id) => !nestedRows.includes(id)));
		}
	}, [expanded, setExpandedIds]);

	return (
		<TableRow {...props}>
			<TableCell colSpan="4" className="p-0">
				<div
					aria-label={label}
					role="button"
					onClick={handleClickToggle}
					className="d-flex align-items-center px-3 py-2 w-100 bg-gray-100 text-gray-800 text-uppercase section-header-md"
				>
					{expanded ? (
						<span className="icon-chevron-up fs-3xl me-2" />
					) : (
						<span className="icon-chevron-down fs-3xl me-2" />
					)}{' '}
					{children}
				</div>
			</TableCell>
		</TableRow>
	);
}

function Example() {
	return (
		<>
			<Table collapsible>
				<TableHeader>
					<TableRow>
						<TableCell> </TableCell>
						<TableCell>Header 2</TableCell>
						<TableCell>Header 3</TableCell>
						<TableCell>Header 4</TableCell>
					</TableRow>
				</TableHeader>
				<TableBody>
					<CustomCollapsibleRow
						id="toggle-1"
						forRows={['area-1']}
						nestedRows={['area-1-1', 'area-1-2']}
						label="Toggle Plan Changes"
					>
						Plan Changes
					</CustomCollapsibleRow>
					<TableRow id="area-1" collapsible>
						<TableCell colSpan="3">Area 1</TableCell>
						<TableCell>
							<TableRowToggle forRows={['area-1-1', 'area-1-2']} />
						</TableCell>
					</TableRow>

					<TableRow id="area-1-1" collapsible>
						<TableCell>1</TableCell>
						<TableCell>2</TableCell>
						<TableCell>3</TableCell>
						<TableCell>4</TableCell>
					</TableRow>
					<TableRow id="area-1-2" collapsible>
						<TableCell>1</TableCell>
						<TableCell>2</TableCell>
						<TableCell>3</TableCell>
						<TableCell>4</TableCell>
					</TableRow>
				</TableBody>
			</Table>
		</>
	);
}

render(<Example />);