diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 8801cb8e9..657523844 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -270,6 +270,14 @@ selection, and `frontend-modern/src/components/shared/commandPaletteModel.ts` owns canonical command construction plus query normalization and filtering policy. Future command-palette work should extend those owners instead of pushing route construction or search policy back into the shared shell. +The shared search field now follows that same owner split. +`frontend-modern/src/components/shared/SearchField.tsx` stays the render shell, +`frontend-modern/src/components/shared/useSearchFieldState.ts` owns focused- +Escape clear/blur behavior and input-ref lifecycle, and +`frontend-modern/src/components/shared/searchFieldModel.ts` owns clear/shortcut +visibility rules plus trailing-control padding policy. Future search-field work +should extend those owners instead of pushing event behavior or layout policy +back into the shared shell. The shared pulse data grid now follows that same owner split. `frontend-modern/src/components/shared/PulseDataGrid.tsx` stays the render shell, `frontend-modern/src/components/shared/usePulseDataGridState.ts` owns diff --git a/frontend-modern/src/components/shared/SearchField.tsx b/frontend-modern/src/components/shared/SearchField.tsx index 33aebc245..48455bfc4 100644 --- a/frontend-modern/src/components/shared/SearchField.tsx +++ b/frontend-modern/src/components/shared/SearchField.tsx @@ -1,76 +1,29 @@ import { Component, Show } from 'solid-js'; +import { type SearchFieldProps } from './searchFieldModel'; +import { useSearchFieldState } from './useSearchFieldState'; -type SearchFieldKeyboardEvent = KeyboardEvent & { - currentTarget: HTMLInputElement; - target: Element; -}; - -type SearchFieldFocusEvent = FocusEvent & { - currentTarget: HTMLInputElement; - target: Element; -}; - -type SearchFieldMouseEvent = MouseEvent & { - currentTarget: HTMLButtonElement; - target: Element; -}; - -export interface SearchFieldProps { - value: string; - onChange: (value: string) => void; - placeholder?: string; - title?: string; - inputRef?: (el: HTMLInputElement) => void; - class?: string; - inputClass?: string; - disabled?: boolean; - onKeyDown?: (event: SearchFieldKeyboardEvent) => void; - onBlur?: (event: SearchFieldFocusEvent) => void; - showClearButton?: boolean; - clearOnFocusedEscape?: boolean; - shortcutHint?: string; - hasTrailingControls?: boolean; - trailingControls?: import('solid-js').JSX.Element; - onClearMouseDown?: (event: SearchFieldMouseEvent) => void; -} +export type { + SearchFieldFocusEvent, + SearchFieldKeyboardEvent, + SearchFieldMouseEvent, + SearchFieldProps, +} from './searchFieldModel'; export const SearchField: Component = (props) => { - let inputEl: HTMLInputElement | undefined; - - const showShortcutHint = () => Boolean(props.shortcutHint && !props.value); - const showClearButton = () => - (props.showClearButton ?? true) && Boolean(props.value) && !props.disabled; - - const inputPaddingRight = () => { - if (props.hasTrailingControls) return 'pr-14 sm:pr-20'; - if (showShortcutHint()) return 'pr-20 sm:pr-24'; - if (showClearButton()) return 'pr-8'; - return 'pr-8'; - }; + const search = useSearchFieldState(props); return (
{ - inputEl = el; - props.inputRef?.(el); - }} + ref={search.setInputRef} type="text" placeholder={props.placeholder ?? 'Search...'} value={props.value} disabled={props.disabled} onInput={(e) => props.onChange(e.currentTarget.value)} - onKeyDown={(e) => { - if (e.key === 'Escape' && (props.clearOnFocusedEscape ?? true)) { - if (props.value) { - props.onChange(''); - } - inputEl?.blur(); - } - props.onKeyDown?.(e); - }} - onBlur={(e) => props.onBlur?.(e)} - class={`w-full pl-8 sm:pl-9 ${inputPaddingRight()} py-1.5 sm:py-2 text-sm border border-border rounded-md + onKeyDown={search.handleKeyDown} + onBlur={search.handleBlur} + class={`w-full pl-8 sm:pl-9 ${search.inputPaddingRight()} py-1.5 sm:py-2 text-sm border border-border rounded-md bg-surface text-base-content placeholder-muted focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-400 outline-none transition-all disabled:opacity-60 disabled:cursor-not-allowed ${props.inputClass ?? ''}`} title={props.title} @@ -89,12 +42,12 @@ export const SearchField: Component = (props) => { />
- + - +