// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file CheckboxListItem.tsx * @input Uses React, CheckboxInput, ListItem, CheckboxListContext * @output Exports CheckboxListItem component, CheckboxListItemProps * @position Core implementation; consumed by index.ts, tested by CheckboxList.test.tsx * * SYNC: When modified, update these files to stay in sync: * - /packages/core/src/CheckboxList/CheckboxList.doc.mjs * - /packages/core/src/CheckboxList/CheckboxList.test.tsx * - /packages/core/src/CheckboxList/index.ts * - /apps/storybook/stories/CheckboxList.stories.tsx * - /packages/cli/assets/templates/blocks/components/CheckboxList/ (showcase blocks) */ import { use, useId, useRef, type ReactNode } from 'react'; import * as stylex from "../style-runtime.js"; import type { StyleXStyles } from "../style-runtime.js"; import "../theme/tokens.stylex.js"; import { colorVars } from "../theme/tokens.stylex.js"; import type { BaseProps } from "../BaseProps.js"; import { CheckboxInput } from "../CheckboxInput/CheckboxInput.js"; import type { CheckboxInputProps } from "../CheckboxInput/CheckboxInput.js"; import { ListItem } from "../List/ListItem.js"; import { ListContext } from "../List/ListContext.js"; import { CheckboxListContext } from "./CheckboxListContext.js"; import { useTranslator } from "../i18n/index.js"; import { ItemDescriptionContext } from "../Item/ItemDescriptionContext.js"; // ============================================================================= // Styles // ============================================================================= const styles = { selected: { kWkggS: "xgcxg3y", $$css: true as const } }; // ============================================================================= // Types // ============================================================================= export interface CheckboxListItemProps extends BaseProps { /** * Primary text label for the item. * * Accepts a plain string (single-line truncation applied automatically) * or a ReactNode for rich content (no truncation constraints — * child components control their own text behavior). Links and buttons in * the label keep their own behavior; only non-interactive row clicks * delegate to the checkbox. * * A string names the checkbox directly. A ReactNode names it from its * visible text through `aria-labelledby`; if that text is absent, pass * `aria-label`. When visible text is present, an override must retain every * visible word so speech-input users can say what they see. */ label: ReactNode; /** * Plain-text accessible name for the checkbox, replacing the one derived * from `label`. * * A string `label` names the checkbox directly, and a rich (ReactNode) * `label` names it from its visible text through `aria-labelledby`. Pass * `aria-label` when that text is absent. When visible text is present, the * override must retain every visible word so speech-input users can say what * they see. It replaces the derived name and applies to the checkbox control, * not the row. * * @example * ``` * Pro plan } * aria-label="Pro plan Recommended option" * value="pro" * /> * ``` */ 'aria-label'?: string; /** * Identity key for collection mode (REQUIRED inside CheckboxList). * Throws a runtime error if missing when used inside CheckboxList. */ value?: string; /** * Secondary content below the label. Accepts a plain string or a ReactNode. * Exposed as the checkbox's accessible description through * `aria-describedby`, so assistive technology can tell it is the explanation * for this choice rather than unrelated row text. */ description?: ReactNode; /** * Content rendered after the label area. */ endContent?: ReactNode; /** * Whether this individual item is disabled. * @default false */ isDisabled?: boolean; /** * Whether this item is in a loading state. Renders a spinner inside the * checkbox and blocks interaction on this item only. * * In collection mode, this is also driven automatically: when the parent * `CheckboxList` has a `changeAction`, the toggled item shows its * spinner while that promise is pending. * @default false */ isLoading?: boolean; /** * Direct checked state (standalone mode only). * Ignored when inside CheckboxList. */ isChecked?: boolean | 'indeterminate'; /** * Direct check handler (standalone mode only). * Ignored when inside CheckboxList. */ onCheck?: (checked: boolean) => void; /** Ref forwarded to the root element */ ref?: React.Ref; } // ============================================================================= // Component // ============================================================================= /** * The row's visible description is the checkbox's accessible description. Item * renders that element and owns its id, and publishes the id through * ItemDescriptionContext; this reads it from inside the slot Item renders. The * row cannot wrap the description in an id'd element instead — that turns a * plain string into a ReactNode and drops the single-line truncation ListItem * documents — and a public `descriptionId` prop would fail * `spec:AST-002/DEC-1`, since the caller decides nothing Item cannot derive. * * This component owns `aria-describedby`, so the prop is omitted rather than * accepted and overwritten: a caller passing one would otherwise lose the id * silently. */ function DescribedCheckboxInput(props: Omit) { const describedBy = use(ItemDescriptionContext); return ; } /** * A checkbox item for use within CheckboxList (collection mode) * or List (standalone mode). * * In collection mode, checked state is derived from the parent's value array. * In standalone mode, uses isChecked/onCheck props directly. * * Composes ListItem internally — gets density, dividers, hover/press, * focus, and container alignment for free. * * @example * ``` * * * ``` */ export function CheckboxListItem({ label, 'aria-label': ariaLabel, value, description, endContent, isDisabled: isItemDisabled = false, isLoading: isItemLoading = false, isChecked, onCheck, ref, xstyle, className, style, onClick: onClickProp, ...restProps }: CheckboxListItemProps) { const t = useTranslator(); const ctx = use(CheckboxListContext); if (ctx && ctx.value !== undefined && value === undefined) { throw new Error('CheckboxListItem requires a `value` prop when used inside CheckboxList with a value array.'); } // Accessible name for the checkbox. A string `label` (or an explicit // `aria-label`) becomes the text of CheckboxInput's visually hidden // `