Patterns
Search Bar
AppSearchBar — the list-page filter strip: a keyword input, Popover-backed filter tags, and an Add-filters dropdown held in one flex shell with exactly one fixed gap.
AppSearchBar
ComponentSearch input + filter tags + Add-filter dropdown, with a fixed non-collapsing gap.
src/components/common/RequiresantdLive example
The filter strip that sits between the action bar and the table on every list page: a keyword Input with a SearchOutlined prefix (minWidth: 280 / maxWidth: 520) on the left, the active filters as closable Tags — each wrapped in a Popover holding its editor and an Apply button — and the Add filters Dropdown at the end.
AppActionBar). AppSearchBar only owns searching and filtering.Click a tag to edit its value in a popover; close both tags and the input still keeps exactly one gap to the Add-filters button.
// React — reference-fe/src/pages/index.tsx. Order is fixed:
// keyword Input → active filter Tags (in Popovers) → Add-filters Dropdown.
<AppSearchBar gap={4}>
<Input placeholder='Search code...' value={draftSearch.code}
onChange={(e) => setDraftSearch({...draftSearch, code: e.target.value})}
prefix={<SearchOutlined />} style={{minWidth: 280, maxWidth: 520}}
/>
{/* Active filter tags with Popover UI inside */}
{activeFilterKeys.map(key => (
<Popover key={key} trigger='click' open={openPopovers[key]}
content={<div><Input value={draftSearch[key]}/><Button type='primary' onClick={handleApplyFilters}>Apply</Button></div>}>
<Tag closable onClose={() => handleCloseFilter(key)}>
{filterLabelMap[key]}: {draftSearch[key]}
</Tag>
</Popover>
))}
<Dropdown menu={{items: availableFilterOptions.map(opt => ({key: opt.key, label: opt.labelKey}))}}>
<Button><i className='fa-filter'/> Add filters</Button>
</Dropdown>
</AppSearchBar>Slots & props
The Vue component (administrator-fe/src/components/common/AppSearchBar.vue) exposes three regions as slots; the React usage passes the same three regions as ordered children plus a gap prop. Whatever the stack, the order never changes: search field, filter tags, Add-filters button.
| Name | Type | Default | Description |
|---|---|---|---|
#search | slot / first child | — | The keyword Input. Always prefix={<SearchOutlined />}, allowClear, and style={{ minWidth: 280, maxWidth: 520 }}. pressEnter applies the filters. |
default | slot / children | — | The active filter tags — each a closable Tag wrapped in a click-trigger Popover that holds the filter editor (Input/Select) and an Apply button. |
#add-filter | slot / last child | — | The Add-filters Dropdown button. Its menu lists availableFilterOptions — the filters not yet active; picking one adds a new tag. |
gap | number | 4 | Gap between the regions of the flex shell (React usage: <AppSearchBar gap={4}>). |
Layout contract
Two visual invariants make every list page read the same, whether zero or five filters are applied:
AppSearchBar pins the filter tag font-size to 13px via :deep(.ant-tag), so the label never changes size between the draft state (tag just added, no value yet) and the applied state (label + value). Don’t override it per page.Rules
v-if="availableFilterOptions.length > 0" — when every filter is already active, the Add-filters button disappears instead of opening an empty menu.AppSearchBar always comes right after AppActionBar and right before the table. See the List Screen pattern for the full four-band recipe.