// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file CommandPaletteInput.tsx * @input Uses React, StyleX, Icon, CommandPaletteContext, DialogContext * @output Exports CommandPaletteInput component and props * @position Search input for the command palette * * SYNC: When modified, update these files to stay in sync: * - /apps/storybook/stories/CommandPalette.stories.tsx * - /packages/cli/assets/templates/blocks/components/CommandPalette/ (showcase blocks) */ import { useCallback, useEffect, useRef, type ReactNode } from 'react'; import * as stylex from "../style-runtime.js"; import { Icon } from "../Icon/index.js"; import { Spinner } from "../Spinner/index.js"; import { mergeProps } from "../utils/index.js"; import "../theme/tokens.stylex.js"; import { colorVars, typeScaleVars, spacingVars, typographyVars } from "../theme/tokens.stylex.js"; import { useCommandPaletteContext } from "./CommandPaletteContext.js"; import { useDialogContext } from "../Dialog/DialogContext.js"; import type { BaseProps } from "../BaseProps.js"; import { themeProps } from "../utils/themeProps.js"; import { useTranslator } from "../i18n/index.js"; import { useMergedRefs } from "../hooks/useMergedRefs.js"; const styles = { wrapper: { k1xSpc: "x78zum5", kGNEyG: "x6s0dn4", kOIVth: "x1txdalj", kg3NbH: "x1pzlopt", k8WAf4: "x8o8v82", kmuXW: "x2lah0s", $$css: true as const } }; export interface CommandPaletteInputProps extends Omit, 'onChange'> { /** Ref forwarded to the input element (for focus management). */ ref?: React.Ref; /** * The current search value. * When omitted inside CommandPalette, reads from context. */ value?: string; /** * Called when the search value changes. * When omitted inside CommandPalette, writes to context. */ onValueChange?: (value: string) => void; /** * Placeholder text for the input. * @default 'Search...' */ placeholder?: string; /** * Accessible label for the combobox input, announced by screen readers. * Falls back to the placeholder text (`'Search…'` by default), since a * placeholder alone is not a reliable accessible name. */ label?: string; /** * Whether to auto-focus the input when mounted. * @default true */ hasAutoFocus?: boolean; /** * Content rendered at the trailing end of the input, after the spinner. * Use for clear buttons, keyboard shortcuts, or other trailing actions. * The spinner (when busy) appears immediately before this content with a 4px gap. */ endContent?: ReactNode; /** Native onChange handler for the input element. */ onChange?: React.ChangeEventHandler; } /** * Search input for the command palette. * * Renders a search icon and a text input. Auto-focuses when mounted * so users can start typing immediately. * * When used inside CommandPalette, automatically wires to the * context for search state and keyboard navigation (via useCombobox). * Can also be used standalone with explicit value/onValueChange props. * * @compositionHint Place as the first child of CommandPalette. * * @example * ``` * * * * ``` */ export function CommandPaletteInput({ value: controlledValue, onValueChange, placeholder: placeholderFromProps, label, hasAutoFocus = true, endContent, onChange, onKeyDown, ref, xstyle, className, style, ...props }: CommandPaletteInputProps) { const t = useTranslator(); const placeholder = placeholderFromProps ?? t('@astryx.commandPalette.input.placeholder'); const ctx = useCommandPaletteContext(); const dialogContext = useDialogContext(); const inputRef = useRef(null); // Use context values as fallback const value = controlledValue ?? ctx?.search; const handleValueChange = onValueChange ?? ctx?.setSearch; // When rendered inside an inline dialog, disable auto-focus by default // to avoid stealing focus from the surrounding page. const effectiveAutoFocus = hasAutoFocus && dialogContext?.isInline !== true; // Auto-focus on mount useEffect(() => { if (effectiveAutoFocus && inputRef.current) { requestAnimationFrame(() => { inputRef.current?.focus(); }); } }, [effectiveAutoFocus]); // Keyboard navigation — delegates to useCombobox via context const handleKeyDown = useCallback((e: React.KeyboardEvent) => { onKeyDown?.(e); if (e.defaultPrevented) { return; } // Delegate to useCombobox's keyboard handler from context ctx?.onKeyDown(e); }, [ctx, onKeyDown]); return
= 0 ? ctx.getItemId(ctx.highlightedIndex) : undefined} // A placeholder alone is not a reliable accessible name; give the // combobox an explicit one (consumer aria-label via rest props wins). aria-label={label ?? placeholder} placeholder={placeholder} value={value} data-autofocus={effectiveAutoFocus || undefined} onChange={e => { handleValueChange?.(e.target.value); onChange?.(e); }} onKeyDown={handleKeyDown} className="x98rzlu xeuugli x1a2a7pz xjbqb8w x1tgivj0 x9ynric xjm74w1 xf9zqsd xw6l6zx x1717udv xeyghm5" {...props} /> {(ctx?.isBusy || endContent) && {ctx?.isBusy && } {endContent} }{' '}
; } CommandPaletteInput.displayName = 'CommandPaletteInput';