Documentation Index
Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
Use this file to discover all available pages before exploring further.
- Implementation
- Props
- Types
Beta FeatureThis feature is currently in beta, and needs to be imported from
@servicetitan/anvil2/beta.While we hope to minimize breaking changes, they may occur due to feedback we receive or other improvements. These will always be documented in the changelog and communicated in Slack.Please reach out in the #ask-designsystem channel with any questions or feedback!Overview
SavedFiltersButton is an opt-in companion for filter UIs (typically a FilterBar) that lets users apply, save, edit, and delete preset filter selections.It is fully stateless: the consumer owns the savedFilters array and writes its own persistence (server, localStorage, etc.) by reacting to the callbacks the component fires. The component itself never mutates a preset.Visually, the trigger is a button that opens a popover listing the presets, with two footer actions:- Save Current Filter opens a Drawer with a name field that snapshots
currentFiltersinto a new preset. - Edit Saved Filters opens a Drawer listing every preset with an Edit affordance. Clicking Edit drills into a per-preset form that reuses the FilterBar adapter
renderDrawermachinery — meaning every filter type supported in the drawer (boolean, single/multi-select, date, date range, dateList, custom, asyncSelect, asyncMultiSelect) works inside the drilldown automatically.
Standalone vs. coupled to FilterBar
The component is not coupled toFilterBar. It expects two things:savedFilters— the presets to list. Each is{ id, name, filters }wherefiltersis the full schema (same shape asFilterBar’sfiltersprop) with the preset’s values baked in.currentFilters— the filter schema currently in use (so the “Save Current Filter” drawer can snapshot it).
onApplySavedFilter to whatever target state they own. With FilterBar that’s typically just setFilters(saved.filters).Async callbacks and alerts
Several mutation callbacks (onSaveCurrentFilter, onUpdateSavedFilter, onDeleteSavedFilter, onReorderSavedFilters) may return a Promise. While the promise is pending, the relevant submit button shows a loading spinner and the form is disabled.When the promise settles:- Save success: the Save drawer closes.
- Save failure: the rejection’s message surfaces as the Filter Name field’s error; the drawer stays open so the user can retry.
- Update success: the drilldown returns to the list view with a success
Alert. - Update failure: the drilldown stays open with an error
Alertabove the form so the user can adjust and retry. - Delete success or failure: the drilldown returns to the list view with the corresponding
Alert. - Reorder failure: the list view surfaces an error
Alert(success doesn’t — the new order is its own visible confirmation). See Drag-to-reorder.
alertText to customize the messages for the update, delete, and reorder-failure outcomes — the paths that surface drawer-level Alerts today. Save uses field-level errors instead of an Alert and just closes on success, so saveSuccess / saveError are reserved but not surfaced.For every failure path (save, update, delete, reorder), a thrown Error with a non-empty message takes precedence over the corresponding alertText entry — so a server response surfaced as throw new Error(serverMessage) reaches the user verbatim. Use alertText for the generic fallback when no usable message is available.Drag-to-reorder
ProvidingonReorderSavedFilters opts the Edit drawer into drag-to-reorder UI. Each row picks up a drag handle plus keyboard-accessible drop affordances (powered by DndSort, which detects the surrounding Drawer and disables sensors during its open/close animations). The callback receives the new id ordering after a drop — the consumer re-sorts their savedFilters array to match.Alert on the list view (using alertText.reorderError, per the precedence rule in Async callbacks and alerts), so reorder failures aren’t invisible to the user.Per-preset locks
EachSavedFilter accepts two optional booleans for marking individual presets immutable — handy for seeded or system presets a consumer wants to expose but not let users mutate.disableEdit— the row in the Edit drawer renders without an Edit affordance and the drilldown is unreachable.disableDelete— the preset is still editable, but the Delete Filter button is omitted from its drilldown footer.
Empty state
WhensavedFilters is empty the popover shows a subdued “No saved filters yet.” message and the Edit Saved Filters footer button is disabled — there’s nothing to edit, but the user can still hit Save Current Filter to create their first preset.Pass emptyState to override the default. The prop is string | ReactNode:- Pass a
stringto only swap the copy. The default subdued / smallTextstyling is preserved so the new message reads the same as the default. - Pass any other
ReactNode(a CTA, an illustration, a styled block) to take full control of the empty-state content.
Active-preset detection
PassactiveSavedFilterId to mark which preset (if any) reflects the user’s current selection — the matching row in the popover renders with a check affordance, and the trigger label reflects the preset’s name (when label is not set).The component does no comparison itself. Track which preset matches the live filter state in your own state, set the id when the user applies one, and clear it whenever the live filters drift (e.g. the user edits a filter directly on the bar).Validation
UsevalidateName to block submit on invalid names (e.g. duplicate detection). Return an error string to block; return undefined when valid. The currentId arg is set during the edit drilldown so you can permit the row’s own name in uniqueness checks.