Hooks

useDocyrusDataGallery

One-call wiring of a Docyrus data source to a fully configured DataGallery + toolbar (DataGridViewSelect, search, filters, sort, group, gallery display menu, card config menu) — including row fetching, saved views, pivot filters, and automatic card field detection.

Installation

pnpm dlx @docyrus/cli add @docyrus/hooks-use-docyrus-data-gallery
Required Packages(6 packages)
pnpm add @docyrus/app-utils @docyrus/api-client @tanstack/react-query @tanstack/react-table @tanstack/react-virtual react-querybuilder

This hook is distributed as source. It requires an authenticated RestApiClient from @docyrus/api-client and a QueryClientProvider from @tanstack/react-query somewhere above your component tree.

Overview

useDocyrusDataGallery composes useDocyrusDataGrid and layers a gallery-specific presentation on top. The result is a ready-to-render setup with:

  • Backend wiring — data source schema fetch, items query, saved views, side filters, pivot filters (all from the data-grid hook).
  • Tenant-aware formatting — inherited from the composed grid hook. Date / datetime / number cells pick up the formatters installed by <DocyrusTenantProvider> automatically; explicit formatDate / formatDateTime / formatNumber props still win when supplied.
  • Gallery toolbar — DataGridViewSelect (server-backed saved views), debounced search, DataGridFilterMenu/SortMenu/GroupMenu, plus the gallery-specific DataGalleryDisplayMenu (variant / cover style / column count / density) and DataGalleryCardConfigMenu (field bindings).
  • Auto-detected card config — title, description, avatar, cover image, badge, and timeline field bindings are picked from data source metadata via detectCardConfigFromFields.
  • Virtualization — independent card grid virtualizer, responsive column count.
import { useDocyrusDataGallery } from '@/hooks/use-docyrus-data-gallery';
import { DataGallery } from '@/components/docyrus/data-gallery';

function ProductGallery() {
  const {
    galleryProps, toolbar, cardConfig, items, reload
  } = useDocyrusDataGallery({
    appSlug: 'sales',
    dataSourceSlug: 'products',
    collection: useProductCollection()
  });

  return (
    <div className="flex flex-col gap-3">
      {toolbar}
      <DataGallery {...galleryProps} cardConfig={cardConfig} />
    </div>
  );
}

Auto-Detected Card Config

When cardConfig is omitted, the hook scans the data source's field metadata and picks the first matching field for each slot. The detection prefers slugs that match known hints (e.g. name, cover, status) and falls back to field-type heuristics:

SlotSlug hintsField types
titleFieldname, title, subject, labelfield-text, field-identity, field-textarea, field-display, field-number
descriptionFielddescription, summary, bio, tagline, subtitlefield-textarea, field-text
coverImageFieldcover, cover_image, image, thumbnail, banner, photofield-image
avatarFieldavatar, profile_photo, profile_picture, profile_imagefield-avatar
badgeFieldstatus, state, priority, stage, phasefield-status, field-select, field-enum, field-radioGroup, field-tagSelect
timelineFielddue, due_date, deadline, start, end, launchfield-date, field-dateTime
bodyFields—All visible field slugs minus the ones already bound to a slot above minus the SYSTEM_AUDIT_SLUGS set (see below)

Override any slot via cardConfig. The hook merges the override on top of the auto-detection — supplying cardConfig={{ titleField: 'project_code' }} overrides only the title binding and lets the other slots auto-fill from metadata.

Conservative avatar picking

avatarField deliberately only matches explicit avatar slugs / field-avatar types. Audit columns like record_owner, created_by, last_modified_by aren't candidates even though they're user fields — in many tenants a single admin owns every record, so picking one of them as the avatar would put the same user on every card. Users can still bind any column to the avatar slot manually via the Card fields menu.

Body field auto-fill skips system audit slugs

SYSTEM_AUDIT_SLUGS keeps record-metadata columns out of the default body list:

id, created_at, created_on, updated_at, last_modified_on,
created_by, last_modified_by, record_owner,
parent_data_source_id, parent_record_id,
tenant_view_id, tenant_data_source_id, editor_view_id, sort_order,
data, document, mentions, followers

autonumber_id is also skipped. These columns stay accessible via the Card fields menu / TanStack column visibility — they're just dropped from the default body layout so the first paint reads as a curated card rather than a raw record dump.

Saved Views

Gallery and data-grid share saved view storage. The hook wires DataGridViewSelect against useDocyrusDataViewSelect, so:

  • Switching between table and gallery views uses the same view tabs.
  • A view's columnVisibility, columnOrder, sorting, columnFilters, grouping, filterQuery all apply identically.
  • The view's displayMode field is honored if you flip between <DataGrid> and <DataGallery> at the app level.
  • Gallery-specific state (cardConfig, displayConfig) is persisted to the view's galleryCardConfig / galleryDisplayConfig fields on SavedDataGridView. Saving a view from the gallery captures the current variant, cover style, column count, card field bindings, etc.; switching back to that view later re-hydrates the gallery to match.

Hydration order

When the active view changes the hook hydrates gallery state in this order:

  1. View's saved gallery state (activeView.galleryCardConfig / galleryDisplayConfig) — applies as-is.
  2. Caller-supplied props (cardConfig / displayConfig options) — applied when the view didn't save anything for a slot.
  3. Auto-detected defaults — detectCardConfigFromFields runs against the data source schema.

The hook tracks the last-applied view id internally, so re-renders with the same active view never re-run hydration (your toolbar edits stay sticky). Switching views resets the gallery state to the new view's saved snapshot.

Saving

The view-select toolbar (DataGridViewSelect) builds the standard view payload (columns, sorting, filters, …). useDocyrusDataGallery wraps onViewSave and onViewCreate to inject the current cardConfig and displayConfig before the payload reaches the backend. Both the built-in toolbar and the externally-rendered gridViewSelectProps use the wrapped callbacks, so consumers building custom toolbars get the same persistence behavior automatically.

Pivot Filters

Pass pivotFilters to render <DocyrusPivotFilterGroup> strips above the toolbar. The hook AND-merges every pivot's combined rule into the items query, identically to useDocyrusDataGrid:

const { galleryProps, toolbar } = useDocyrusDataGallery({
  appSlug: 'sales',
  dataSourceSlug: 'products',
  collection,
  pivotFilters: [
    { fieldSlug: 'category' },
    { fieldSlug: 'status' }
  ]
});

Side Filters

Pass enableSideFilters + sideFiltersConfig to add a left-rail filter panel. The hook exposes sideFilters, sideFiltersExpanded, setSideFiltersExpanded for layout integration — same wiring as the data grid.

Stale Schema Escape Hatch

excludeFieldSlugs drops a list of field slugs from the data source metadata before the columns, list params, view editor, filter menu, and exports consume it. Use it when the backend metadata still advertises a field that the underlying DB column no longer has — those requests return column "X" does not exist 500s otherwise. Inherited from useDocyrusDataGrid:

const galleryHook = useDocyrusDataGallery({
  appSlug: 'base',
  dataSourceSlug: 'organization',
  /*
   * Backend metadata cleanup is the proper fix — this is the
   * frontend bypass while you wait for that PR to land.
   */
  excludeFieldSlugs: ['single_selection', 'legacy_text']
});

The hook memoizes the exclusion by content (joined slug string), not array identity, so passing inline literals doesn't trigger re-fetches.

Search Pipeline

The gallery owns its own search input and debounces locally (250 ms). The debounced keyword is forwarded to the items query via listParams.filterKeyword — the inner useDocyrusDataGrid is configured with enableSearch: false so the table doesn't also run a client-side globalFilter on top of the server-filtered rows (double-filter regression).

The hook exposes both the immediate (searchInput) and debounced (searchKeyword) values for consumers building a custom toolbar.

Pivot Strip Composition

useDocyrusDataGrid exposes its interactive <DocyrusPivotFilterGroup> element as pivotFiltersStrip so downstream hooks can compose it without re-running pivot state. useDocyrusDataGallery stacks that element above the gallery's controls toolbar automatically — pivot pill counts and cross-filter behavior are identical to the grid.

API Reference

Options

The hook composes useDocyrusDataGrid internally, so it also accepts every option that hook forwards — including the DB-free metadata options below — alongside the gallery-specific props in this table.

DB-free metadata. Inherited through useDocyrusDataGrid → useDocyrusDataViewSelect: dataSource (inject a pre-resolved schema → skips the getBySlug fetch), enableDataViews: false (skip the /views fetch), and dataSourceExpand (tune/drop the schema expand param). Pass dataSource together with data (pre-resolved rows) to render cards with no metadata requests. See DB-free metadata.

PropTypeDefaultDescription
collectionDocyrusDataGridCollection<TData>—TanStack DB collection. The hook calls collection.list(listParams) to fetch rows.
dataArray<TData>—Pre-resolved rows. Skips the internal items query.
appSlugstring—Required for data source schema and view fetches.
dataSourceSlugstring—Required for data source schema and view fetches.
listParamsDocyrusDataGridListParams—Extra query params merged on top of view-derived ones.
pivotFiltersReadonlyArray<DocyrusPivotFilterGroupField>—Render pivot strips above the toolbar.
enableSideFiltersbooleanfalseEnable the side-panel filter rail.
sideFiltersConfigDocyrusDataGridSideFiltersConfig<TData>—Side panel config.
cardConfigDataGalleryCardConfigSerializableauto-detectedInitial card field bindings.
onCardConfigChange(config) => void—Fires whenever the card config changes.
displayConfigPartial<DataGalleryDisplayConfig>—Initial display config.
onDisplayConfigChange(config) => void—Fires whenever the display config changes.
enableSearchInputbooleantrueToggle the toolbar search input.
enableFilterMenubooleantrueToggle the filter menu.
enableSortMenubooleantrueToggle the sort menu.
enableGroupMenubooleantrueToggle the group menu.
enableDisplayMenubooleantrueToggle the gallery display menu.
enableCardConfigMenubooleantrueToggle the card config menu.
enableViewSelectbooleantrueToggle the view picker.
enableReloadButtonbooleantrueToggle the reload button.
viewSelectVariant'horizontal-tabs' | 'dropdown' | 'vertical-tabs''horizontal-tabs'View picker variant.
onAdd() => void—When supplied, renders a primary "Add" button on the toolbar's right edge.
addLabelstring'Add'Label for the add button.
minCardWidthnumber260Min card width for the 'flex' column count auto-fit.
estimatedCardHeightnumber360Initial virtualizer estimate — measureElement replaces per-row after first paint.
excludeFieldSlugsReadonlyArray<string>—Drop stale field slugs from the data source schema before downstream consumers see it. See the Stale Schema Escape Hatch section.
overscannumber3Virtualizer overscan rows.
usersReadonlyArray<CellUserOption>—Tenant users for field-userSelect cells. Forwarded to the grid.
formatDate, formatDateTime, formatNumberfunctions—Tenant-aware formatters. Forwarded to the grid.
bulkActionsfalse | ReadonlyArray<DocyrusDataGridBulkAction>['update', 'delete', 'export']Action bar bulk actions.
extraBulkActionsArray<DataGalleryAction<TData>>—Extra row-selection actions.

Returns

KeyTypeDescription
galleryPropsDocyrusDataGalleryProps<TData>Spread onto <DataGallery>. Includes table, displayConfig, virtualizer, containerRef, etc.
toolbarReactNodePre-wired gallery toolbar element. Render above <DataGallery>.
cardConfigDataGalleryCardConfigSerializableCurrent card field bindings.
setCardConfig(updater) => voidCard config setter.
displayConfigDataGalleryDisplayConfigCurrent display config.
setDisplayConfig(updater) => voidDisplay config setter.
searchInputstringImmediate search input value.
searchKeywordstringDebounced search keyword sent to the backend.
setSearchInput(value) => voidSearch input setter.
itemsArray<TData>Resolved rows.
fieldsArray<FullField>Data source fields (filter-friendly shape).
dataSourceDataSource | undefinedResolved data source metadata.
activeViewIdstringCurrent saved view id.
setActiveViewId(viewId) => voidView setter.
viewsArray<SavedDataGridView>Available saved views.
pivotFilterRuleArray<PivotFilterRule> | nullCombined pivot filter rule.
sideFiltersReactNodeSide panel element (or null).
sideFiltersExpanded, setSideFiltersExpanded, sideFiltersQuery—Side filter wiring.
resolvedListParamsDocyrusDataGridListParamsThe list params actually sent to the backend.
pagingMode'standard' | 'virtual-scroll' | undefinedResolved paging mode for the active view.
reload() => voidRefetch metadata + items.
refetch() => voidAlias of reload.
isLoadingbooleanInitial load indicator.
errorError | nullSchema or items error.
gridViewSelectPropsPick<DataGridViewSelectProps, ManagedProps>View-select props with the hook's onViewSave / onViewCreate wrappers already attached — saving a view from a custom toolbar still persists gallery state.
pivotFiltersStripReactNodeThe pivot strip element (or null when no pivotFilters configured) — render before the toolbar when composing a custom layout.

Helper Exports

detectCardConfigFromFields(fields)

Build a DataGalleryCardConfigSerializable from a DataSourceField[]. Used internally; exported so consumers can re-run the detection after schema changes or build their own card configs.

import { detectCardConfigFromFields } from '@/hooks/use-docyrus-data-gallery';

const cardConfig = detectCardConfigFromFields(dataSource.fields);

On this page