import React, { useLayoutEffect } from "react"; import type { ReactElement, ReactNode } from "react"; import { useSelectFieldState } from "./SelectFieldContext"; import { Text } from "../../Text"; export interface SelectLabelProps { /** * The label content. Its placement (above the trigger vs. inside it) is set by * the `labelPlacement` prop on `Select.Root`, not by where this is written. */ readonly children?: ReactNode; } /** * The field label. Rather than render in place, it **registers** its `children` * (and, when a string, the trigger's accessible name) into `Select.Root`, which * renders the content in the slot `labelPlacement` selects. Renders nothing where * it is composed. See `Select.guide.md`. */ export function SelectLabel({ children }: SelectLabelProps): null { const { registerLabel, unregisterLabel } = useSelectFieldState(); useLayoutEffect(() => { registerLabel( children, typeof children === "string" ? children : undefined, ); return unregisterLabel; }, [children, registerLabel, unregisterLabel]); return null; } /** * Internal renderer for the registered label content, shared by the above slot * (`Select.Root`) and the inside slot (`Select.Trigger`). Sizes itself from * `labelPlacement` (smaller when `inside`). Hidden from assistive tech — the * trigger carries the accessible name. */ export function FieldLabel(): ReactElement | null { const { labelContent, labelPlacement } = useSelectFieldState(); if (labelContent == null) return null; return typeof labelContent === "string" ? ( {labelContent} ) : ( <>{labelContent} ); }