SearchForm
search-formBuilds collapsible query filters with reset and submit actions for data lists.
Usage
Basic usage
fields configuration driver; omit values/onChange to enter the internal uncontrolled state. Enter/query trigger onSearch.
<SearchForm
fields={[
{ name: "keyword", label: "Keywords", placeholder: "Order number / customer name" },
{ name: "status", label: "Status", type: "select", placeholder: "All",
options: [{ value: "pending", label: "Pending" }, { value: "done", label: "Completed" }] },
{ name: "owner", label: "Responsible Person", placeholder: "Name" },
]}
onSearch={(v) => console.log(v)}
onReset={() => {}}
/>Foldable + Spanning Columns
The field can be folded when it is filled with multiple rows (only the first row is displayed when collapsed by default); date-range uses colSpan to span two columns.
<SearchForm
fields={fields} // 6+ fields
collapsible // Enough fields to automatically enable "expand/collapse"
onSearch={(v) => console.log(v)}
/>Customize the number of columns and copywriting
columns controls the number of desktop columns, submitText / resetText covers the button copy.
<SearchForm
fields={fields}
columns={2}
submitText="Search"
resetText="Clear"
onSearch={(v) => console.log(v)}
/>When to use
Use SearchForm for the filter area above an admin list. Declare query fields with fields; the component arranges them in a fixed-column grid, collapses overflow to one row, and supplies Search and Reset actions. Unlike Form and ProForm, which submit business records, SearchForm emits filter parameters through onSearch. It is commonly used as ProTable's search area.
Import
import { SearchForm, planLayout, canCollapse, totalSpan } from "@hulianui/ui"Props
| Name | Type | Default | Description |
|---|---|---|---|
| fields* | SearchField[] | — | Definitions for the filter fields. |
| values | Record<string, unknown> | — | Controlled values; omit for internal state. |
| columns | number | 3 | Number of columns at desktop widths. |
| gap | number | 4 | Row and column gap (× 0.25rem). |
| collapsible | boolean | true | Enables one-row collapse when fields exceed one row; has no effect otherwise. |
| defaultCollapsed | boolean | true | Initial collapsed state. |
| loading | boolean | false | Whether the Search button is in a loading state. |
| className | string | — | Additional class name for the root element. |
Events
| Event | Type | Description |
|---|---|---|
| onSearch* | (values: Record<string, unknown>) => void | Called with the current filter values when Search is clicked or Enter is pressed. |
| onChange | (values: Record<string, unknown>) => void | Called after any field changes; use it to write back controlled values. |
| onReset | (values: Record<string, unknown>) => void | Called after reset with each field restored to its default or type-specific empty value. |
Slots
| Slot | Type | Description |
|---|---|---|
| submitText | ReactNode | Primary button label (default "\u67e5\u8be2", meaning “Search”). |
| resetText | ReactNode | Reset button label (default "\u91cd\u7f6e", meaning “Reset”). |
SearchField is a discriminated union selected by type or render; omitting both creates an input field. Shared properties are name* (the value key), label*, placeholder?, colSpan? (defaults to 1 and is capped at columns), and defaultValue?. Supported variants are:
type?: "input"+inputType?: stringtype: "number"+min?/max?/step?(forwards native input constraints)type: "number-range"+ the same constraints (the value is a tuple)type: "select"+options: { value: string; label: ReactNode }[]type: "multi-select"+options(value isstring[])type: "remote-select"+fetcher(signature is the same as RemoteSelect) +resolveValue?+multiple?type: "date"/type: "date-range"type: "datetime"/type: "datetime-range"(nativedatetime-local)
The field type determines the value shape: every *-range value is a two-item tuple [start, end], with "" for an unfilled endpoint; multi-select and a multiple remote-select use string[]; all other built-in fields use string.
After reset, each field returns to its defaultValue or its type-specific empty shape—do not assume every field resets to an empty string.
render: (ctx: { name; value; onChange }) => ReactNode(escape hatch for a custom control)
Example
const fields: SearchField[] = [
{ name: "keyword", label: "Keywords", placeholder: "Order number / customer name" },
{ name: "status", label: "Status", type: "select", placeholder: "All",
options: [
{ value: "pending", label: "Pending" },
{ value: "done", label: "Completed" },
] },
{ name: "range", label: "Created", type: "date-range", colSpan: 2 },
];
<SearchForm
fields={fields}
values={values}
onChange={setValues}
onSearch={(v) => fetchList(v)}
onReset={(v) => fetchList(v)}
/>Usage guidelines
- Controlled usage requires
valuesandonChange; without writeback, fields cannot be edited. - Expand/Collapse appears only when total field span exceeds one row. With fewer fields it is suppressed automatically.
onResetreceives values after every field returns to itsdefaultValueor type-specific empty shape, not{}. Re-query with that callback value to retain default filters.- Query operators such as `LIKE`, `BETWEEN`, and `=` do not belong in SearchForm. They are part of the backend contract. Translate values into the API request shape inside
onSearch; encoding one backend's protocol in field configuration would couple the library to that service. datetimeanddatetime-rangeuse nativedatetime-local, so values are local-time strings without a timezone, such as"2026-07-29T14:30". Do not callnew Date(...).toISOString()blindly; in UTC+8 it shifts the value by eight hours without reporting an error. Convert explicitly inonSearchif the API requires ISO timestamps.- RemoteSelect's
onChangealso returns complete options, but SearchForm stores only its first value argument. Use therenderescape hatch when the raw row is required.
Related
Form · ModalForm / DrawerForm · ProForm · StepsForm · LoginForm · Field
Playground
<SearchForm
fields={fields}
values={values}
onChange={setValues}
onSearch={(v) => console.log(v)}
onReset={() => {}}
/>