Installation
pnpm dlx @docyrus/cli add @docyrus/ui-query-builderpnpm add @react-querybuilder/dnd @atlaskit/pragmatic-drag-and-drop @dnd-kit/core react-dnd react-dnd-html5-backend react-dnd-touch-backend react-querybuilder jsonatanpx shadcn@latest add alert-dialog badge button checkbox command input label popover radio-group select switch textarea toggle-groupUsage
import {
QueryBuilderDocyrus
} from "@docyrus/ui/components/query-builder";
import type {
QueryBuilderDocyrusProps,
DocyrusQBField,
RuleGroupType,
RuleGroupTypeIC,
RuleType,
Field,
FullField,
FullOperator,
FullCombinator,
QueryBuilderProps,
ActionProps,
ValueSelectorProps,
ValueEditorProps,
NotToggleProps,
DragHandleProps,
CombinatorSelectorProps,
FieldSelectorProps,
OperatorSelectorProps,
RuleGroupTypeAny,
VersatileSelectorProps,
QueryJsonataContext,
EvaluateOptions,
UseQuery2JsonataConverterOptions,
UseQuery2JsonataConverterResult,
ConvertQueryOptions,
ConvertQueryResult,
DateBindingSpec,
OperatorApi,
FilterGroup
} from "@docyrus/ui/components/query-builder";
<QueryBuilderDocyrus
variant="default"
size="sm"
animated
draggable
showRuleNumbers
maxDepth={0}
emptyMessage={...}
showClearButton
clearButtonLabel={...}
onRuleAdd={() => {}}
onRuleRemove={() => {}}
onGroupAdd={() => {}}
onGroupRemove={() => {}}
onClear={() => {}}
enrichFieldDefaults
/>Variants
| Variant | Description |
|---|---|
default | Default style |
bordered | Bordered — rounded-lg border bg-card p-4 shadow-sm |
compact | Compact — text-xs [&_.rule]:py-1 [&_.rule]:px-2 [&_.rule]:gap-1 |
striped | Striped — [&_.rule:nth-child(even)]:bg-muted/20 |
Sizes
| Size | Description |
|---|---|
sm | Small — [&_.qb-control]:h-7 [&_.qb-control]:text-xs |
default | Default |
lg | Large — [&_.qb-control]:h-10 [&_.qb-control]:text-sm |
API Reference
| Prop | Type | Default |
|---|---|---|
variant | "default" | "bordered" | "compact" | "striped" | "default" |
size | "sm" | "default" | "lg" | "default" |
animated | boolean | — |
draggable | boolean | — |
showRuleNumbers | boolean | — |
maxDepth | number | — |
emptyMessage | ReactNode | — |
showClearButton | boolean | — |
clearButtonLabel | ReactNode | — |
onRuleAdd | () => void | — |
onRuleRemove | () => void | — |
onGroupAdd | () => void | — |
onGroupRemove | () => void | — |
onClear | () => void | — |
enrichFieldDefaults | boolean | — |
className | string | — |
Async option loading
A relation or user column has an option set too large to ship up front — the related data source's records, or the whole tenant roster. Declare asyncOptions on that field and its value editor switches to a searchable, paged picker that loads from the server:
const fields: Array<DocyrusQBField> = [
{
name: 'matter',
label: 'Matter',
fieldType: 'field-relation',
asyncOptions: {
load: async ({ search, page, pageSize, signal }) => {
const res = await client.get('/v1/apps/base/matter/items', {
filterKeyword: search,
limit: pageSize,
offset: page * pageSize
}, { signal });
return { items: res.data.map(toOption), hasMore: res.data.length === pageSize };
},
resolveByIds: async ({ ids, signal }) => {
const res = await client.get('/v1/apps/base/matter/items', { filters: idFilter(ids) }, { signal });
return res.data.map(toOption);
}
}
}
];Opt-in per field: a field that declares no asyncOptions keeps the static values behaviour exactly as before, so existing consumers are untouched. The shape mirrors the data grid's AsyncOptionsConfig, so a host that already builds a loader for the grid's filter menu can reuse the same one.
Why resolveByIds matters
Without it, a rule reopened from storage prints raw ids. Options arrive one page at a time and only while the picker is open, so a record chosen yesterday is almost never in today's first page. The picker asks for exactly the ids it cannot name, once, and remembers the answer in a sticky label cache.
maxDepth now actually applies. It used to be forwarded as maxGroupLevel, which react-querybuilder has never read — so the cap was silently inert and a consumer asking for a depth limit got unlimited nesting. It is forwarded as maxLevels now, so a previously-ignored maxDepth starts enforcing itself.
Components
| Component | Description |
|---|---|
QueryBuilderDocyrus | Public export |
Type Exports
| Type | Description |
|---|---|
QueryBuilderDocyrusProps | — |
QBAsyncOptions | Server-side option loading config for one field |
QBValueOption | One option (FlatOption) returned by an async loader |
DocyrusQBField | — |
RuleGroupType | — |
RuleGroupTypeIC | — |
RuleType | — |
Field | — |
FullField | — |
FullOperator | — |
FullCombinator | — |
QueryBuilderProps | — |
ActionProps | — |
ValueSelectorProps | — |
ValueEditorProps | — |
NotToggleProps | — |
DragHandleProps | — |
CombinatorSelectorProps | — |
FieldSelectorProps | — |
OperatorSelectorProps | — |
RuleGroupTypeAny | — |
VersatileSelectorProps | — |
QueryJsonataContext | — |
EvaluateOptions | — |
UseQuery2JsonataConverterOptions | — |
UseQuery2JsonataConverterResult | — |
ConvertQueryOptions | — |
ConvertQueryResult | — |
DateBindingSpec | — |
OperatorApi | — |
FilterGroup | — |
Type Reference
DocyrusQBField
| Field | Type | Description |
|---|---|---|
filterGroup | FilterGroup | — |
fieldType | string | — |
operatorValues | Record<string, QBOperatorValueOption[]> | — |
operatorValueEditorType | Partial<Record<string, ValueEditorType>> | — |
asyncOptions | QBAsyncOptions | Server-side option loading for this field. Set it for a relation or user column whose options cannot be shipped in values. |
QBAsyncOptions
| Field | Type | Default | Description |
|---|---|---|---|
load | (params: { search, page, pageSize, signal? }) => Promise<{ items: Array<QBValueOption>; hasMore?: boolean }> | — | Fetch one page of options. Aborted when the editor unmounts or a fresh search supersedes it. |
resolveByIds | (params: { ids: Array<string>; signal? }) => Promise<Array<QBValueOption>> | — | Labels for values that are already selected, so a rule reopened from storage doesn't print raw ids. |
pageSize | number | 25 | Items per page. |
debounceMs | number | 300 | Debounce before a new search fires. |
QueryJsonataContext
| Field | Type | Description |
|---|---|---|
activeUserId | string | — |
activeUserTeamMemberIds | string[] | — |
EvaluateOptions
| Field | Type | Description |
|---|---|---|
now | Date | number | — |
bindings | Record<string, unknown> | — |
UseQuery2JsonataConverterOptions
| Field | Type | Description |
|---|
UseQuery2JsonataConverterResult
| Field | Type | Description |
|---|---|---|
expression | string | — |
dateBindings | DateBindingSpec[] | — |
unsupportedOperators | string[] | — |
warnings | string[] | — |
isValid | boolean | — |
error | Error | null | — |
evaluate | (data: unknown, options?: EvaluateOptions) => Promise<boolean> | — |
ConvertQueryOptions
| Field | Type | Description |
|---|---|---|
resolveGroup | (field: string) => FilterGroup | undefined | — |
fieldResolver | (field: string) => string | — |
operatorOverrides | Record<string, (rule: RuleType, api: OperatorApi) => string | null> | — |
unsupportedFallback | boolean | — |
weekStartsOn | 0 | 1 | — |
ConvertQueryResult
| Field | Type | Description |
|---|---|---|
expression | string | — |
dateBindings | DateBindingSpec[] | — |
unsupportedOperators | string[] | — |
warnings | string[] | — |
DateBindingSpec
| Field | Type | Description |
|---|---|---|
id | string | — |
operator | string | — |
amount | number | — |
OperatorApi
| Field | Type | Description |
|---|---|---|
field | string | — |
str | (value: unknown) => string | — |
num | (value: unknown) => string | — |
strArr | (values: unknown[]) => string | — |
fieldRef | (name: string) => string | — |
FilterGroup
"ALPHA"