@upbound/monarch-blocks 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,10 @@
1
1
  import * as React$1 from 'react';
2
2
  import { ReactNode, Dispatch, SetStateAction, ComponentType } from 'react';
3
3
  import { RowData, ColumnFilter, Cell, Column, ColumnOrderState, Row, ColumnDef, CellContext, HeaderGroup, TableOptions, TableState, InitialTableState, SortingFn, FilterFn, ExpandedState, ColumnFiltersState, PaginationState, SortingState, VisibilityState, ColumnPinningState, GroupingState, OnChangeFn, ColumnSizingState } from '@tanstack/react-table';
4
- import { DropdownMenuItem, IconName } from '@upbound/monarch-core';
4
+ import { DropdownMenuItem } from '@upbound/monarch-core';
5
5
  import { f as CelFilterSchema, g as CelFilterTree, k as CompileResult, i as CelRestrictedOptionSelectPayload } from './types-BULiU2qC.js';
6
+ import { T as TableDataState, a as DataTableEmptyView } from './empty-view-DO36LNvk.js';
7
+ export { D as DataTableEmptyConfig, d as defaultEmptyConfig, b as defaultErrorConfig, c as defaultNoFilterFoundConfig } from './empty-view-DO36LNvk.js';
6
8
 
7
9
  /** A single entry (or nested submenu) in a cell's contextual right-click menu, alongside the built-in "Filter by this value" item. */
8
10
  type ContextualMenuAction<TData extends RowData = RowData, TValue = unknown> = {
@@ -86,180 +88,6 @@ declare function TimestampCell({ timestamp }: {
86
88
  timestamp?: Date | string | number;
87
89
  }): React$1.JSX.Element;
88
90
 
89
- /**
90
- * Non-CEL column filter definitions — the `meta.filter` vocabulary consumed by
91
- * contextual (right-click cell) filtering and by the plain filter bar.
92
- * Mirrors TableWrapper's `Filters/types.ts` shapes; `id` is omitted here since
93
- * it comes from the owning `ColumnDef.id`, not the filter definition itself.
94
- */
95
- type ColumnFilterType = 'checkbox' | 'radio' | 'dateRange' | 'text' | 'multiText';
96
- type ColumnFilterOption = {
97
- label: string;
98
- value: string;
99
- disabled?: boolean;
100
- children?: ColumnFilterOption[];
101
- };
102
- type FetchColumnFilterOptionsArgs = {
103
- query: string;
104
- page: number;
105
- signal: AbortSignal;
106
- };
107
- type FetchColumnFilterOptionsResult = {
108
- items: ColumnFilterOption[];
109
- hasMore: boolean;
110
- };
111
- type FetchColumnFilterOptions = (args: FetchColumnFilterOptionsArgs) => Promise<FetchColumnFilterOptionsResult>;
112
- type ColumnFilterBase = {
113
- title: string;
114
- type: ColumnFilterType;
115
- order?: number;
116
- };
117
- type StaticCheckboxColumnFilter = ColumnFilterBase & {
118
- type: 'checkbox';
119
- options: string[] | ColumnFilterOption[];
120
- fetchFilterOptions?: never;
121
- /** Whether the chip's value popup is a searchable Combobox. Defaults to `true`. */
122
- searchable?: boolean;
123
- };
124
- type AsyncCheckboxColumnFilter = ColumnFilterBase & {
125
- type: 'checkbox';
126
- fetchFilterOptions: FetchColumnFilterOptions;
127
- options?: never;
128
- /** Whether the chip's value popup is a searchable Combobox. Defaults to `true`. */
129
- searchable?: boolean;
130
- };
131
- type CheckboxColumnFilter = StaticCheckboxColumnFilter | AsyncCheckboxColumnFilter;
132
- type RadioColumnFilter = ColumnFilterBase & {
133
- type: 'radio';
134
- options: string[] | ColumnFilterOption[];
135
- /** Whether the chip's value popup is a searchable Combobox. Defaults to `true`. */
136
- searchable?: boolean;
137
- };
138
- type DateRangeColumnFilter = ColumnFilterBase & {
139
- type: 'dateRange';
140
- /** When true, always show time inputs. When false, never show them. When omitted, auto-detect from value. */
141
- includeTime?: boolean;
142
- };
143
- type TextColumnFilter = ColumnFilterBase & {
144
- type: 'text';
145
- placeholder?: string;
146
- };
147
- type MultiTextColumnFilter = ColumnFilterBase & {
148
- type: 'multiText';
149
- /** Defaults to comma (`,`) when omitted. */
150
- delimiter?: string;
151
- description?: string;
152
- placeholder?: string;
153
- };
154
- type ColumnFilterDefinition = CheckboxColumnFilter | DateRangeColumnFilter | RadioColumnFilter | TextColumnFilter | MultiTextColumnFilter;
155
- type CheckboxColumnFilterValue = string[] | undefined;
156
- type RadioColumnFilterValue = string | undefined;
157
- type DateRangeColumnFilterValue = {
158
- startDate: Date | null;
159
- endDate: Date | null;
160
- } | undefined;
161
- type TextColumnFilterValue = string | undefined;
162
- type MultiTextColumnFilterValue = string[] | undefined;
163
- type ColumnFilterValue = CheckboxColumnFilterValue | RadioColumnFilterValue | DateRangeColumnFilterValue | TextColumnFilterValue | MultiTextColumnFilterValue;
164
-
165
- /** Derives a contextual (right-click cell) column-filter value from a cell. */
166
- type ContextualFilterValueParser<TData extends RowData, TValue> = (cell: Cell<TData, TValue>) => ColumnFilterValue | undefined;
167
- /** Derives a contextual (right-click cell) global-search term from a cell. */
168
- type ContextualGlobalSearchValueParser<TData extends RowData, TValue> = (cell: Cell<TData, TValue>) => string | undefined;
169
- /** A fixed-width grid track: sizes to content or an explicit CSS length. */
170
- type GridColumnFixed = {
171
- fixed: string;
172
- flex?: never;
173
- };
174
- type GridColumnFlexContentMin = {
175
- min: 'max-content' | 'min-content';
176
- minCap?: string;
177
- };
178
- type GridColumnFlexFixedMin = {
179
- min?: string;
180
- minCap?: never;
181
- };
182
- /** A proportional grid track: shares space by weight, with optional min/max. */
183
- type GridColumnFlex = {
184
- flex: number;
185
- max?: string;
186
- fixed?: never;
187
- } & (GridColumnFlexContentMin | GridColumnFlexFixedMin);
188
- /**
189
- * Column sizing for grid layout mode.
190
- *
191
- * Fixed column (sizes to content or explicit value):
192
- * { fixed: 'max-content' }
193
- * { fixed: '200px' }
194
- *
195
- * Proportional column (shares space by weight, with optional min/max):
196
- * { flex: 2, min: '100px' }
197
- * { flex: 0.5, min: 'max-content' }
198
- * { flex: 0.5, min: 'max-content', minCap: '200px' }
199
- * { flex: 1, min: '80px', max: '500px' }
200
- */
201
- type GridColumnSizing = GridColumnFixed | GridColumnFlex;
202
- /**
203
- * Column-def metadata `DataTable` reads — a straight port of TableWrapper's
204
- * `ColumnMeta` module augmentation. Import this file for its side effect
205
- * (`import '@/lib/data-table/table-meta'`) wherever a `ColumnDef` is authored
206
- * with any of these fields.
207
- */
208
- declare module '@tanstack/react-table' {
209
- interface ColumnMeta<TData extends RowData, TValue> {
210
- /** Pins the column left or right. Pinned columns are excluded from column reordering. */
211
- pinning?: 'left' | 'right';
212
- /** Marks an actions column — hides its header label and excludes it from contextual filtering. */
213
- actions?: boolean;
214
- /** The column's non-CEL filter definition, read by the plain filter bar and contextual filtering. */
215
- filter?: ColumnFilterDefinition;
216
- /** Parses a cell into a filter value for contextual (right-click) cell filtering. */
217
- contextualFilterValue?: ContextualFilterValueParser<TData, TValue>;
218
- /** Parses a cell into a search term for contextual (right-click) global search. */
219
- contextualGlobalSearchValue?: ContextualGlobalSearchValueParser<TData, TValue>;
220
- /** When true, the column is used only for filtering and is never rendered in the table. */
221
- filterOnly?: boolean;
222
- /** Column sizing for `layout="grid"` mode — see `GridColumnSizing`. */
223
- grid?: GridColumnSizing;
224
- }
225
- }
226
-
227
- type TableDataState = 'empty' | 'loading' | 'error' | 'success';
228
-
229
- /**
230
- * Monarch-native equivalent of TableWrapper's `EmptyConfig` — same four
231
- * states (error / empty / filtered-empty), rebuilt on Monarch's `Empty`
232
- * compound API instead of a single props-driven component. Two deliberate
233
- * adaptations: `icon` takes a Monarch `IconName` (no FontAwesome dependency),
234
- * and `action` is a rendered node rather than a `ComponentType` reference.
235
- * `details` has no native `Empty` slot — it composes `Alert` instead.
236
- */
237
- type DataTableEmptyConfig = {
238
- icon?: IconName;
239
- header?: React$1.ReactNode;
240
- subheader?: React$1.ReactNode;
241
- details?: React$1.ReactNode;
242
- action?: React$1.ReactNode;
243
- className?: string;
244
- wrapperClassName?: string;
245
- };
246
- declare const defaultErrorConfig: DataTableEmptyConfig;
247
- declare const defaultEmptyConfig: DataTableEmptyConfig;
248
- declare const defaultNoFilterFoundConfig: DataTableEmptyConfig;
249
- type DataTableEmptyView = {
250
- empty?: DataTableEmptyConfig;
251
- error?: DataTableEmptyConfig;
252
- filter?: DataTableEmptyConfig;
253
- };
254
- declare function DataTableEmptyView({ emptyView, tableState, filterCount, resetFilters, className, }: {
255
- emptyView?: DataTableEmptyView;
256
- /** `'success'`/`'loading'` render nothing here — the table body handles those. */
257
- tableState: TableDataState;
258
- filterCount: number;
259
- resetFilters: () => void;
260
- className?: string;
261
- }): React$1.JSX.Element;
262
-
263
91
  type RowCountRelation = 'eq' | 'gt';
264
92
  /** Pass `true` for defaults, `false` to disable the footer entirely (handled by the caller, not this component), or an object to toggle individual parts. */
265
93
  type DataTablePaginationConfig = boolean | {
@@ -533,4 +361,4 @@ type ViewFilterProps<TData extends RowData> = {
533
361
  */
534
362
  declare function ViewFilter<TData extends RowData>({ columns, columnFilters, onColumnFiltersChange, className, }: ViewFilterProps<TData>): React$1.JSX.Element;
535
363
 
536
- export { CellContextualFilter, type CellContextualFilterProps, ColumnOptions, type ColumnOptionsProps, type ContextualMenuAction, DataTable, DataTableBody, type DataTableCelFilterConfig, DataTableCell, type DataTableEmptyConfig, DataTableEmptyView, DataTableExpandedRow, type DataTableFilterVisibilityConfig, DataTableGroupRow, DataTableHeader, DataTablePagination, type DataTablePaginationConfig, type DataTablePaginationProps, type DataTableProps, DataTableRow, type DataTableRowProps, DataTableSkeletonBody, DataTableValueCell, type DataTableValueCellProps, ExpandButton, RowActionItem, RowActions, type RowCountRelation, type StickyHeaderConfig, type TableHookOptions, type TableLayout, TimestampCell, ViewFilter, type ViewFilterProps, defaultEmptyConfig, defaultErrorConfig, defaultNoFilterFoundConfig, getActionsColumnDefinition, getExpandColumnDefinition, normalizePageSizeOptions };
364
+ export { CellContextualFilter, type CellContextualFilterProps, ColumnOptions, type ColumnOptionsProps, type ContextualMenuAction, DataTable, DataTableBody, type DataTableCelFilterConfig, DataTableCell, DataTableEmptyView, DataTableExpandedRow, type DataTableFilterVisibilityConfig, DataTableGroupRow, DataTableHeader, DataTablePagination, type DataTablePaginationConfig, type DataTablePaginationProps, type DataTableProps, DataTableRow, type DataTableRowProps, DataTableSkeletonBody, DataTableValueCell, type DataTableValueCellProps, ExpandButton, RowActionItem, RowActions, type RowCountRelation, type StickyHeaderConfig, type TableHookOptions, type TableLayout, TimestampCell, ViewFilter, type ViewFilterProps, getActionsColumnDefinition, getExpandColumnDefinition, normalizePageSizeOptions };
@@ -99,7 +99,15 @@ function CellContextualFilter({
99
99
  );
100
100
  }
101
101
  return /* @__PURE__ */ jsxs(ContextMenuContent, { children: [
102
- /* @__PURE__ */ jsx(ContextMenuItem, { disabled, onClick: (event) => event.stopPropagation(), onSelect: handleSelect, children: "Filter by this value" }),
102
+ /* @__PURE__ */ jsx(
103
+ ContextMenuItem,
104
+ {
105
+ disabled,
106
+ onClick: (event) => event.stopPropagation(),
107
+ onSelect: handleSelect,
108
+ children: "Filter by this value"
109
+ }
110
+ ),
103
111
  contextualMenuActions.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
104
112
  /* @__PURE__ */ jsx(ContextMenuSeparator, {}),
105
113
  contextualMenuActions.map(renderAction)
@@ -152,6 +160,9 @@ var twMerge = extendTailwindMerge({
152
160
  function cn(...inputs) {
153
161
  return twMerge(clsx(inputs));
154
162
  }
163
+ function sortBy(items, getKey) {
164
+ return [...items].sort((a, b) => getKey(a).localeCompare(getKey(b)));
165
+ }
155
166
 
156
167
  // src/lib/data-table/utils.ts
157
168
  var TO_EACH_SIDE = 2;
@@ -601,8 +612,25 @@ function RowActions({
601
612
  contentClassName
602
613
  }) {
603
614
  return /* @__PURE__ */ jsxs3(DropdownMenu2, { children: [
604
- /* @__PURE__ */ jsx4(DropdownMenuTrigger2, { asChild: true, children: /* @__PURE__ */ jsx4(Button2, { variant: "ghost", size: "icon-xs", "aria-label": "Row actions", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx4(Icon2, { name: "ellipsis-vertical", size: "sm" }) }) }),
605
- /* @__PURE__ */ jsx4(DropdownMenuContent2, { align, className: contentClassName, onClick: (event) => event.stopPropagation(), children })
615
+ /* @__PURE__ */ jsx4(DropdownMenuTrigger2, { asChild: true, children: /* @__PURE__ */ jsx4(
616
+ Button2,
617
+ {
618
+ variant: "ghost",
619
+ size: "icon-xs",
620
+ "aria-label": "Row actions",
621
+ onClick: (e) => e.stopPropagation(),
622
+ children: /* @__PURE__ */ jsx4(Icon2, { name: "ellipsis-vertical", size: "sm" })
623
+ }
624
+ ) }),
625
+ /* @__PURE__ */ jsx4(
626
+ DropdownMenuContent2,
627
+ {
628
+ align,
629
+ className: contentClassName,
630
+ onClick: (event) => event.stopPropagation(),
631
+ children
632
+ }
633
+ )
606
634
  ] });
607
635
  }
608
636
  function RowActionItem({
@@ -1787,7 +1815,10 @@ function CelAddFilterButton({
1787
1815
  list.push(column);
1788
1816
  groupMap.set(key, list);
1789
1817
  }
1790
- const groups = Array.from(groupMap.entries()).map(([value, items]) => ({ value, items }));
1818
+ const groups = sortBy(
1819
+ Array.from(groupMap.entries()).map(([value, items]) => ({ value, items: sortBy(items, (item) => item.title) })),
1820
+ (group) => group.value
1821
+ );
1791
1822
  return /* @__PURE__ */ jsxs4(
1792
1823
  Combobox,
1793
1824
  {
@@ -2015,7 +2046,7 @@ function CelCheckboxComboboxPill({
2015
2046
  container
2016
2047
  }) {
2017
2048
  var _a;
2018
- const options = (_a = columnDef.options) != null ? _a : [];
2049
+ const options = sortBy((_a = columnDef.options) != null ? _a : [], (o) => o.label);
2019
2050
  const value = isCheckboxCelValue(condition.value) ? condition.value : [];
2020
2051
  const selected = options.filter((option) => value.includes(option.value));
2021
2052
  const allowedOperators = getAllowedOperators(columnDef);
@@ -2104,7 +2135,7 @@ function CelRadioComboboxPill({
2104
2135
  container
2105
2136
  }) {
2106
2137
  var _a, _b;
2107
- const options = (_a = columnDef.options) != null ? _a : [];
2138
+ const options = sortBy((_a = columnDef.options) != null ? _a : [], (o) => o.label);
2108
2139
  const value = typeof condition.value === "string" ? condition.value : "";
2109
2140
  const selected = (_b = options.find((option) => option.value === value)) != null ? _b : null;
2110
2141
  const allowedOperators = getAllowedOperators(columnDef);
@@ -3153,6 +3184,7 @@ function DataTablePagination({
3153
3184
  PaginationLink,
3154
3185
  {
3155
3186
  href: "#",
3187
+ size: "icon",
3156
3188
  isActive: page === currentPage,
3157
3189
  onClick: (e) => {
3158
3190
  e.preventDefault();
@@ -3794,6 +3826,9 @@ function composeRefs(...refs) {
3794
3826
  }
3795
3827
  };
3796
3828
  }
3829
+ function hasOrder(filter) {
3830
+ return typeof filter.order === "number";
3831
+ }
3797
3832
  function FilterBar(_a) {
3798
3833
  var _b = _a, {
3799
3834
  filters,
@@ -3815,7 +3850,8 @@ function FilterBar(_a) {
3815
3850
  "ref"
3816
3851
  ]);
3817
3852
  const usedKeys = new Set(activeFilters.map((f) => f.key));
3818
- const availableFilters = filters.filter((f) => !usedKeys.has(f.key));
3853
+ const remainingFilters = filters.filter((f) => !usedKeys.has(f.key));
3854
+ const availableFilters = filters.some(hasOrder) ? remainingFilters : sortBy(remainingFilters, (f) => f.label);
3819
3855
  const shouldShowFilters = availableFilters.length > 0 || activeFilters.length > 0;
3820
3856
  const [pendingOpenKey, setPendingOpenKey] = React7.useState(null);
3821
3857
  function handleAddFilter(key) {
@@ -3950,7 +3986,8 @@ function CheckboxFilterChip({
3950
3986
  onRemove
3951
3987
  }) {
3952
3988
  var _a, _b;
3953
- const { label, options, searchable = false } = definition;
3989
+ const { label, searchable = false } = definition;
3990
+ const options = sortBy(definition.options, (o) => o.label);
3954
3991
  const values = asCheckboxValue(value);
3955
3992
  const displayLabel = values.length === 0 ? "Any" : values.length === 1 ? (_b = (_a = options.find((o) => o.value === values[0])) == null ? void 0 : _a.label) != null ? _b : values[0] : `${values.length} selected`;
3956
3993
  function toggle(optionValue) {
@@ -4035,7 +4072,8 @@ function RadioFilterChip({
4035
4072
  onRemove
4036
4073
  }) {
4037
4074
  var _a, _b, _c;
4038
- const { label, options, searchable = false } = definition;
4075
+ const { label, searchable = false } = definition;
4076
+ const options = sortBy(definition.options, (o) => o.label);
4039
4077
  const selectedValue = asRadioValue(value);
4040
4078
  const displayLabel = selectedValue ? (_b = (_a = options.find((o) => o.value === selectedValue)) == null ? void 0 : _a.label) != null ? _b : selectedValue : "Any";
4041
4079
  const selected = (_c = options.find((o) => o.value === selectedValue)) != null ? _c : null;
@@ -4290,21 +4328,21 @@ function toFilterOption(opt) {
4290
4328
  return typeof opt === "string" ? { value: opt, label: opt } : { value: opt.value, label: opt.label };
4291
4329
  }
4292
4330
  function toFilterBarDefinition(key, filter) {
4293
- var _a, _b, _c;
4294
- const base = { key, label: filter.title, order: (_a = filter.order) != null ? _a : 0 };
4331
+ var _a, _b;
4332
+ const base = { key, label: filter.title, order: filter.order };
4295
4333
  if (isCheckboxColumnFilter(filter)) {
4296
4334
  const options = "options" in filter && filter.options ? filter.options : [];
4297
4335
  return __spreadProps(__spreadValues({}, base), {
4298
4336
  type: "checkbox",
4299
4337
  options: options.map(toFilterOption),
4300
- searchable: (_b = filter.searchable) != null ? _b : true
4338
+ searchable: (_a = filter.searchable) != null ? _a : true
4301
4339
  });
4302
4340
  }
4303
4341
  if (isRadioColumnFilter(filter)) {
4304
4342
  return __spreadProps(__spreadValues({}, base), {
4305
4343
  type: "radio",
4306
4344
  options: filter.options.map(toFilterOption),
4307
- searchable: (_c = filter.searchable) != null ? _c : true
4345
+ searchable: (_b = filter.searchable) != null ? _b : true
4308
4346
  });
4309
4347
  }
4310
4348
  if (isDateRangeColumnFilter(filter)) {
@@ -4337,7 +4375,12 @@ function ViewFilter({
4337
4375
  [columns]
4338
4376
  );
4339
4377
  const filterDefinitions = React8.useMemo(
4340
- () => filterableColumns.map(({ key, filter }) => toFilterBarDefinition(key, filter)).sort((a, b) => a.order - b.order),
4378
+ () => filterableColumns.map(({ key, filter }) => toFilterBarDefinition(key, filter)).sort((a, b) => {
4379
+ if (a.order == null && b.order == null) return 0;
4380
+ if (a.order == null) return 1;
4381
+ if (b.order == null) return -1;
4382
+ return a.order - b.order;
4383
+ }),
4341
4384
  [filterableColumns]
4342
4385
  );
4343
4386
  const emptyValueByKey = React8.useMemo(