// ~/src/siteplane/components/settings-list/settings-list.tsx "use client"; import { useId, type ComponentPropsWithoutRef, type ReactNode } from "react"; import { Card } from "../card"; import { Separator } from "../separator"; import { cn } from "../../system/utils/cn"; import { slotClass } from "../../system/utils/slot-class"; export interface SettingsListProps extends Omit< ComponentPropsWithoutRef<"section">, "title" > { description?: ReactNode; title: ReactNode; /** * The heading level of the title, 1 to 6; `h2` by default, below the page's * `h1`. A list inside a section of a settings page takes the next level. The * look stays the same at every level. */ titleLevel?: 1 | 2 | 3 | 4 | 5 | 6 | undefined; } /** * What `control` receives. Spread it on a switch, a select trigger or an input: * the title becomes their accessible name. An action button keeps its own text * as its name, so pass it `aria-describedby` only; with `aria-labelledby` too, * the title would replace the button's label for assistive technology. */ export interface SettingsListControlProps { "aria-describedby"?: string | undefined; "aria-labelledby": string; } export interface SettingsListItemProps extends Omit< ComponentPropsWithoutRef<"div">, "children" | "title" > { control: (props: SettingsListControlProps) => ReactNode; description?: ReactNode; layout?: "inline" | "responsive"; tag?: ReactNode; title: ReactNode; } /** * The list is a size container: its rows, its card padding and its header * inset switch once the list itself is 36rem wide. A list in a page column * keeps the rows it had at the old viewport `md` switch; beside a sidebar or * in a narrow column it stays stacked. The gaps and the header inset are the * rules in design-rework.css. */ export function SettingsList({ children, className, description, title, titleLevel = 2, ...props }: SettingsListProps): React.ReactElement { const Title = `h${titleLevel}` as const; return (
{title} {description ? (

{description}

) : null}
{children}
); } export function SettingsListItem({ className, control, description, layout = "responsive", tag, title, ...props }: SettingsListItemProps): React.ReactElement { const descriptionId = useId(); const titleId = useId(); return (
{/* A drawn divider between rows: the list already separates them. */}
{title} {tag}
{description ? (
{description}
) : null}
{control({ "aria-describedby": description ? descriptionId : undefined, "aria-labelledby": titleId, })}
); } // ~/src/siteplane/components/settings-list/index.ts export * from "./settings-list";