// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file ListItem.tsx * @input Uses React, ReactNode, StyleXStyles, theme tokens * @output Exports ListItem component, ListItemProps type * @position Core implementation; consumed by List, index.ts, tested by List.test.tsx * * Composes Item for the shared start content + label + description + end content layout * and the invisible button/anchor interactive pattern. * * SYNC: When modified, update these files to stay in sync: * - /packages/core/src/List/List.doc.mjs * - /packages/core/src/List/List.test.tsx * - /packages/core/src/List/index.ts * - /apps/storybook/stories/List.stories.tsx * - /packages/cli/assets/templates/blocks/components/List/ (showcase blocks) */ import { use, type ReactNode } from 'react'; import * as stylex from "../style-runtime.js"; import "../theme/tokens.stylex.js"; import { colorVars, spacingVars, typeScaleVars, borderVars } from "../theme/tokens.stylex.js"; import type { BaseProps } from "../BaseProps.js"; import { ListContext } from "./ListContext.js"; import { mergeProps } from "../utils/index.js"; import { Item } from "../Item/index.js"; import { themeProps } from "../utils/themeProps.js"; // ============================================================================= // Types // ============================================================================= export interface ListItemProps extends BaseProps { /** Ref forwarded to the root element */ ref?: React.Ref; /** * 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). */ label: ReactNode; /** * Secondary description below the label. * * Accepts a plain string (single-line truncation applied automatically) * or a ReactNode for rich/multi-line content (no wrapping constraints * applied — child components control their own text behavior). */ description?: ReactNode; /** * Content rendered before the item (icon, avatar, checkbox). * Uses start/end naming for RTL support. */ startContent?: ReactNode; /** * Content rendered after the item (badge, action button, chevron). */ endContent?: ReactNode; /** * Click handler for interactive items. * Automatically enables hover/press styles when provided. */ onClick?: (e: React.MouseEvent) => void; /** * Ref to a nested control inside the item (e.g. a checkbox in * `startContent`) that already provides the item's keyboard access and * action. When set, the item becomes an enlarged click/tap target that * delegates surface clicks to that control via the `useClickableContainer` * pattern: it renders no invisible button/anchor, so the row adds no second * tab stop (WCAG 4.1.2 — one focusable control per option). Mutually * exclusive with `onClick`/`href` — when set those are ignored. */ interactiveRef?: React.RefObject; /** * URL for link items. Renders an invisible anchor element. * Automatically enables hover/press styles when provided. */ href?: string; /** * Link target (e.g., '_blank'). Only used with href. */ target?: string; /** * Link relationship. Automatically includes noopener noreferrer when * target is "_blank". */ rel?: string; /** * Whether the item is disabled. * @default false */ isDisabled?: boolean; /** * Whether the item is currently selected. * @default false */ isSelected?: boolean; } // ============================================================================= // Styles // ============================================================================= const styles = { withCounter: { kAmcRD: "xfrknyr", $$css: true as const }, withDivider: { kt9PQ7: "x92x3c3", kfdmCh: "x1q0q8m5", kL6WhQ: "xw8gpjh", $$css: true as const } }; // ============================================================================= // Marker styles — custom-rendered markers instead of native list-style-type. // Uses CSS counters for numbers (same pattern as WWW Astryx). // ============================================================================= const MARKER_DOT_SIZE = 6; const embeddedStyles = { noRadius: { kaIpWk: "x2u8bby", $$css: true as const } }; // ============================================================================= // Component // ============================================================================= /** * A list item component for use within List. * * Renders structured content with label, description, start/end content areas. * When `onClick` is provided, uses the invisible button pattern for accessibility. * When `href` is provided, uses an invisible anchor pattern. * * @example * ``` * * navigate('/profile')} /> * * ``` */ export function ListItem({ label, description, startContent, endContent, onClick, interactiveRef, href, target, rel, isDisabled = false, isSelected = false, xstyle, className, style, ref, ...restProps }: ListItemProps) { const ctx = use(ListContext); const density = ctx?.density ?? 'balanced'; const hasDividers = ctx?.hasDividers ?? false; const listStyle = ctx?.listStyle ?? 'none'; const hasMarkers = listStyle !== 'none'; const marker = listStyle === 'disc' ? : listStyle === 'circle' ? : listStyle === 'decimal' ? : null; return ; } ListItem.displayName = 'ListItem';