@rebasepro/ui 0.14.0 → 0.14.1-canary.g7e666eb

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.
@@ -13,9 +13,14 @@ type VirtualTableHeaderProps<M extends Record<string, unknown>> = {
13
13
  columnIndex: number;
14
14
  isResizingIndex: number;
15
15
  column: VirtualTableColumn<unknown>;
16
- onColumnSort: (key: Extract<keyof M, string>) => void;
16
+ onColumnSort: (key: Extract<keyof M, string>, additive?: boolean) => void;
17
17
  filter?: [VirtualTableWhereFilterOp, unknown];
18
18
  sort: VirtualTableSort;
19
+ /**
20
+ * This column's zero-based rank in a multi-key sort, or `undefined` when
21
+ * only one column is sorted and a rank would say nothing.
22
+ */
23
+ sortPosition?: number;
19
24
  onFilterUpdate: (column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp, unknown]) => void;
20
25
  onClickResizeColumn?: (columnIndex: number, column: VirtualTableColumn) => void;
21
26
  createFilterField?: (props: FilterFormFieldProps<unknown>) => React.ReactNode;
@@ -1,3 +1,3 @@
1
1
  import React from "react";
2
2
  import { VirtualTableContextProps } from "./types";
3
- export declare const VirtualTableHeaderRow: ({ columns, currentSort, onColumnSort, onFilterUpdate, sortByProperty, filter, onColumnResize, onColumnResizeEnd, createFilterField, AddColumnComponent, onColumnsOrderChange, data, cellRenderer: CellRenderer, rowHeight, draggingColumnId, headerHeight }: VirtualTableContextProps<Record<string, unknown>>) => React.JSX.Element;
3
+ export declare const VirtualTableHeaderRow: ({ columns, sortIndex, onColumnSort, onFilterUpdate, filter, onColumnResize, onColumnResizeEnd, createFilterField, AddColumnComponent, onColumnsOrderChange, data, cellRenderer: CellRenderer, rowHeight, draggingColumnId, headerHeight }: VirtualTableContextProps<Record<string, unknown>>) => React.JSX.Element;
@@ -1,5 +1,7 @@
1
1
  import React from "react";
2
2
  import type { FilterFormFieldProps } from "./VirtualTableHeader";
3
+ /** One sort key: the column, and which way it runs. */
4
+ export type VirtualTableSortKey = [string, "asc" | "desc"];
3
5
  export type OnRowClickParams<T extends Record<string, unknown>> = {
4
6
  rowData: T;
5
7
  rowIndex: number;
@@ -30,7 +32,7 @@ export interface VirtualTableProps<T extends Record<string, unknown>> {
30
32
  * @param filterValues
31
33
  * @param sortBy
32
34
  */
33
- checkFilterCombination?: (filterValues: VirtualTableFilterValues<Extract<keyof T, string>>, sortBy?: [string, "asc" | "desc"]) => boolean;
35
+ checkFilterCombination?: (filterValues: VirtualTableFilterValues<Extract<keyof T, string>>, sortBy?: VirtualTableSortKey[]) => boolean;
34
36
  /**
35
37
  * A callback function when scrolling the table to near the end
36
38
  */
@@ -77,14 +79,15 @@ export interface VirtualTableProps<T extends Record<string, unknown>> {
77
79
  scrollUpdateWasRequested: boolean;
78
80
  }) => void;
79
81
  /**
80
- * Default sort applied to this collection
82
+ * Sort applied to this collection: the keys in order of significance, the
83
+ * second breaking ties on the first.
81
84
  */
82
- sortBy?: [string, "asc" | "desc"];
85
+ sortBy?: VirtualTableSortKey[];
83
86
  /**
84
87
  * Callback used when sorting is updated
85
88
  * @param sortBy
86
89
  */
87
- onSortByUpdate?: (sortBy?: [string, "asc" | "desc"]) => void;
90
+ onSortByUpdate?: (sortBy?: VirtualTableSortKey[]) => void;
88
91
  /**
89
92
  * If there is an error loading data you can pass it here, so it gets
90
93
  * displayed instead of the content
@@ -18,14 +18,17 @@ export type VirtualTableContextProps<T> = {
18
18
  headerHeight?: number;
19
19
  columns: VirtualTableColumn[];
20
20
  cellRenderer: React.ComponentType<CellRendererParams<T>>;
21
- currentSort: "asc" | "desc" | undefined;
21
+ /** Each sorted column's direction and its rank in the sort, keyed by column. */
22
+ sortIndex: Map<string, {
23
+ direction: "asc" | "desc";
24
+ position: number;
25
+ }>;
22
26
  filter?: VirtualTableFilterValues<string>;
23
27
  onRowClick?: (props: OnRowClickParams<Record<string, unknown>>) => void;
24
- onColumnSort: (key: string) => void;
28
+ onColumnSort: (key: string, additive?: boolean) => void;
25
29
  onColumnResize: (params: OnVirtualTableColumnResizeParams) => void;
26
30
  onColumnResizeEnd: (params: OnVirtualTableColumnResizeParams) => void;
27
31
  onFilterUpdate: (column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp, unknown]) => void;
28
- sortByProperty?: string;
29
32
  customView?: React.ReactNode;
30
33
  hoverRow: boolean;
31
34
  createFilterField?: (props: FilterFormFieldProps<unknown>) => React.ReactNode;
package/dist/index.es.js CHANGED
@@ -5650,7 +5650,20 @@ function ToggleButtonGroup({ value, onValueChange, options, className }) {
5650
5650
  }
5651
5651
  //#endregion
5652
5652
  //#region src/components/VirtualTable/VirtualTableHeader.tsx
5653
- var VirtualTableHeader = React.memo(function VirtualTableHeader({ resizeHandleRef, columnIndex, isResizingIndex, sort, onColumnSort, onFilterUpdate, filter, column, onClickResizeColumn, createFilterField, AdditionalHeaderWidget, isDragging, isDraggable }) {
5653
+ /**
5654
+ * What the next click on this column's sort control will do, spelled out for
5655
+ * the tooltip and for screen readers. An unlabelled arrow that means three
5656
+ * different things depending on the state it is in is a control you have to
5657
+ * click to learn.
5658
+ */
5659
+ function sortActionTitle(sort, sortPosition) {
5660
+ const rank = sortPosition !== void 0 ? ` (sort key ${sortPosition + 1})` : "";
5661
+ const addKey = " — shift-click to add it under the current sort instead";
5662
+ if (sort === "asc") return `Sorted ascending${rank}. Click to sort descending.${addKey}`;
5663
+ if (sort === "desc") return `Sorted descending${rank}. Click to remove this sort.${addKey}`;
5664
+ return `Click to sort ascending.${addKey}`;
5665
+ }
5666
+ var VirtualTableHeader = React.memo(function VirtualTableHeader({ resizeHandleRef, columnIndex, isResizingIndex, sort, sortPosition, onColumnSort, onFilterUpdate, filter, column, onClickResizeColumn, createFilterField, AdditionalHeaderWidget, isDragging, isDraggable }) {
5654
5667
  const [onHover, setOnHover] = useState(false);
5655
5668
  const [openFilter, setOpenFilter] = React.useState(false);
5656
5669
  const [hidden, setHidden] = React.useState(false);
@@ -5694,16 +5707,18 @@ var VirtualTableHeader = React.memo(function VirtualTableHeader({ resizeHandleRe
5694
5707
  })]
5695
5708
  })
5696
5709
  }),
5697
- /* @__PURE__ */ jsxs(Fragment, { children: [AdditionalHeaderWidget && /* @__PURE__ */ jsx(AdditionalHeaderWidget, { onHover: onHover || openFilter }), column.sortable && (sort || hovered || openFilter) && /* @__PURE__ */ jsx("div", {
5698
- className: "flex-shrink-0",
5699
- children: /* @__PURE__ */ jsx(Badge, {
5710
+ /* @__PURE__ */ jsxs(Fragment, { children: [AdditionalHeaderWidget && /* @__PURE__ */ jsx(AdditionalHeaderWidget, { onHover: onHover || openFilter }), column.sortable && (sort || hovered || openFilter) && /* @__PURE__ */ jsxs("div", {
5711
+ className: "flex-shrink-0 flex items-center",
5712
+ children: [/* @__PURE__ */ jsx(Badge, {
5700
5713
  color: "secondary",
5701
5714
  invisible: !sort,
5702
5715
  children: /* @__PURE__ */ jsxs(IconButton, {
5703
5716
  size: "small",
5704
5717
  className: onHover || openFilter ? "bg-white dark:bg-surface-900" : void 0,
5705
- onClick: () => {
5706
- onColumnSort(column.key);
5718
+ title: sortActionTitle(sort, sortPosition),
5719
+ "aria-label": sortActionTitle(sort, sortPosition),
5720
+ onClick: (event) => {
5721
+ onColumnSort(column.key, event.shiftKey);
5707
5722
  },
5708
5723
  children: [
5709
5724
  !sort && /* @__PURE__ */ jsx(ArrowUpIcon$1, {
@@ -5720,7 +5735,11 @@ var VirtualTableHeader = React.memo(function VirtualTableHeader({ resizeHandleRe
5720
5735
  })
5721
5736
  ]
5722
5737
  })
5723
- })
5738
+ }), sort && sortPosition !== void 0 && /* @__PURE__ */ jsx("span", {
5739
+ className: "flex-shrink-0 -ml-0.5 mr-0.5 text-[10px] font-bold leading-none tabular-nums text-text-secondary dark:text-text-secondary-dark",
5740
+ "aria-hidden": true,
5741
+ children: sortPosition + 1
5742
+ })]
5724
5743
  })] }),
5725
5744
  column.filter && createFilterField && /* @__PURE__ */ jsx("div", {
5726
5745
  className: "flex-shrink-0",
@@ -5825,7 +5844,7 @@ function FilterForm({ column, onFilterUpdate, filter, onHover, createFilterField
5825
5844
  }
5826
5845
  //#endregion
5827
5846
  //#region src/components/VirtualTable/VirtualTableHeaderRow.tsx
5828
- var SortableColumnHeader = ({ column, columnIndex, columnRefs, isResizing, onFilterUpdate, filter, sortByProperty, currentSort, onColumnSort, onClickResizeColumn, createFilterField, isDragging, isDraggable }) => {
5847
+ var SortableColumnHeader = ({ column, columnIndex, columnRefs, isResizing, onFilterUpdate, filter, sortIndex, sortCount, onColumnSort, onClickResizeColumn, createFilterField, isDragging, isDraggable }) => {
5829
5848
  const [isPressing, setIsPressing] = useState(false);
5830
5849
  const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
5831
5850
  id: column.key,
@@ -5862,7 +5881,8 @@ var SortableColumnHeader = ({ column, columnIndex, columnRefs, isResizing, onFil
5862
5881
  isResizingIndex: isResizing,
5863
5882
  onFilterUpdate,
5864
5883
  filter,
5865
- sort: sortByProperty === column.key ? currentSort : void 0,
5884
+ sort: sortIndex.get(column.key)?.direction,
5885
+ sortPosition: sortCount > 1 ? sortIndex.get(column.key)?.position : void 0,
5866
5886
  onColumnSort,
5867
5887
  onClickResizeColumn,
5868
5888
  column,
@@ -5873,7 +5893,7 @@ var SortableColumnHeader = ({ column, columnIndex, columnRefs, isResizing, onFil
5873
5893
  })
5874
5894
  });
5875
5895
  };
5876
- var VirtualTableHeaderRow = ({ columns, currentSort, onColumnSort, onFilterUpdate, sortByProperty, filter, onColumnResize, onColumnResizeEnd, createFilterField, AddColumnComponent, onColumnsOrderChange, data, cellRenderer: CellRenderer, rowHeight = 54, draggingColumnId, headerHeight = 48 }) => {
5896
+ var VirtualTableHeaderRow = ({ columns, sortIndex, onColumnSort, onFilterUpdate, filter, onColumnResize, onColumnResizeEnd, createFilterField, AddColumnComponent, onColumnsOrderChange, data, cellRenderer: CellRenderer, rowHeight = 54, draggingColumnId, headerHeight = 48 }) => {
5877
5897
  const columnRefs = useMemo(() => columns.map(() => createRef()), [columns.length]);
5878
5898
  const [isResizing, setIsResizing] = useState(-1);
5879
5899
  const adjustWidthColumn = useCallback((index, width, end) => {
@@ -5967,8 +5987,8 @@ var VirtualTableHeaderRow = ({ columns, currentSort, onColumnSort, onFilterUpdat
5967
5987
  isResizing,
5968
5988
  onFilterUpdate,
5969
5989
  filter: filterForThisProperty,
5970
- sortByProperty,
5971
- currentSort,
5990
+ sortIndex,
5991
+ sortCount: sortIndex.size,
5972
5992
  onColumnSort,
5973
5993
  onClickResizeColumn,
5974
5994
  createFilterField,
@@ -6090,8 +6110,16 @@ var innerElementType = forwardRef(({ children, ...rest }, ref) => {
6090
6110
  * memoized component under a type that describes it.
6091
6111
  */
6092
6112
  var VirtualTable = React.memo(function VirtualTable({ data, onResetPagination, onEndReached, endOffset = 600, rowHeight = 54, headerHeight = 48, columns: columnsProp, onRowClick, onColumnResize, onColumnResizeEnd, filter: filterInput, checkFilterCombination, onFilterUpdate, sortBy, error, emptyComponent, onSortByUpdate, onScroll: onScrollProp, loading, cellRenderer, hoverRow, createFilterField, rowClassName, style, className, endAdornment, AddColumnComponent, initialScroll = 0, onColumnsOrderChange, extraData }) {
6093
- const sortByProperty = sortBy ? sortBy[0] : void 0;
6094
- const currentSort = sortBy ? sortBy[1] : void 0;
6113
+ const sortIndex = useMemo(() => {
6114
+ const index = /* @__PURE__ */ new Map();
6115
+ (sortBy ?? []).forEach(([key, direction], position) => {
6116
+ if (!index.has(key)) index.set(key, {
6117
+ direction,
6118
+ position
6119
+ });
6120
+ });
6121
+ return index;
6122
+ }, [sortBy]);
6095
6123
  const [columns, setColumns] = useState(columnsProp);
6096
6124
  const tableRef = useRef(null);
6097
6125
  const endReachCallbackThreshold = useRef(0);
@@ -6151,12 +6179,27 @@ var VirtualTable = React.memo(function VirtualTable({ data, onResetPagination, o
6151
6179
  endReachCallbackThreshold.current = 0;
6152
6180
  if (tableRef.current) tableRef.current.scrollTo(tableRef.current?.scrollLeft, 0);
6153
6181
  }, []);
6154
- const onColumnSort = useCallback((key) => {
6155
- const isDesc = sortByProperty === key && currentSort === "desc";
6156
- const newSort = sortByProperty === key && currentSort === "asc" ? "desc" : isDesc ? void 0 : "asc";
6157
- const newSortProperty = isDesc ? void 0 : key;
6182
+ /**
6183
+ * Click a header to sort by that column: ascending, descending, then
6184
+ * off. Shift-click to keep the sort you already have and add this
6185
+ * column under it the same idiom spreadsheets use, and the only way
6186
+ * to build "by role, newest first" out of two clicks.
6187
+ *
6188
+ * Shift cycles the column it lands on through the same three states, so
6189
+ * a key added by mistake is removed by shift-clicking it twice more,
6190
+ * without disturbing the keys around it.
6191
+ */
6192
+ const onColumnSort = useCallback((key, additive = false) => {
6193
+ const current = sortIndex.get(key);
6194
+ const next = current === void 0 ? "asc" : current.direction === "asc" ? "desc" : void 0;
6195
+ const existing = sortBy ?? [];
6196
+ let newSortBy;
6197
+ if (!additive) newSortBy = next ? [[key, next]] : void 0;
6198
+ else if (next === void 0) newSortBy = existing.filter(([existingKey]) => existingKey !== key);
6199
+ else if (current === void 0) newSortBy = [...existing, [key, next]];
6200
+ else newSortBy = existing.map((entry) => entry[0] === key ? [key, next] : entry);
6201
+ if (newSortBy?.length === 0) newSortBy = void 0;
6158
6202
  const filter = filterRef.current;
6159
- const newSortBy = newSort && newSortProperty ? [newSortProperty, newSort] : void 0;
6160
6203
  if (filter) {
6161
6204
  if (checkFilterCombination && !checkFilterCombination(filter, newSortBy)) {
6162
6205
  if (onFilterUpdate) onFilterUpdate(void 0);
@@ -6167,12 +6210,12 @@ var VirtualTable = React.memo(function VirtualTable({ data, onResetPagination, o
6167
6210
  scrollToTop();
6168
6211
  }, [
6169
6212
  checkFilterCombination,
6170
- currentSort,
6171
6213
  onFilterUpdate,
6172
6214
  onResetPagination,
6173
6215
  onSortByUpdate,
6174
6216
  scrollToTop,
6175
- sortByProperty
6217
+ sortBy,
6218
+ sortIndex
6176
6219
  ]);
6177
6220
  const maxScroll = Math.max((data?.length ?? 0) * rowHeight - bounds.height, 0);
6178
6221
  const onEndReachedInternal = useCallback((scrollOffset) => {
@@ -6195,13 +6238,12 @@ var VirtualTable = React.memo(function VirtualTable({ data, onResetPagination, o
6195
6238
  let newFilterValue = filter ? { ...filter } : {};
6196
6239
  if (!filterForProperty) delete newFilterValue[column.key];
6197
6240
  else newFilterValue[column.key] = filterForProperty;
6198
- if (!(!checkFilterCombination || checkFilterCombination(newFilterValue, sortByProperty && currentSort ? [sortByProperty, currentSort] : void 0))) newFilterValue = filterForProperty ? { [column.key]: filterForProperty } : {};
6241
+ if (!(!checkFilterCombination || checkFilterCombination(newFilterValue, sortBy))) newFilterValue = filterForProperty ? { [column.key]: filterForProperty } : {};
6199
6242
  if (onFilterUpdate) onFilterUpdate(newFilterValue);
6200
6243
  }, [
6201
6244
  checkFilterCombination,
6202
- currentSort,
6203
6245
  onFilterUpdate,
6204
- sortByProperty
6246
+ sortBy
6205
6247
  ]);
6206
6248
  const empty = !loading && (data?.length ?? 0) === 0;
6207
6249
  const customView = error && (data?.length ?? 0) === 0 ? /* @__PURE__ */ jsxs(CenteredView, {
@@ -6224,7 +6266,6 @@ var VirtualTable = React.memo(function VirtualTable({ data, onResetPagination, o
6224
6266
  headerHeight,
6225
6267
  cellRenderer,
6226
6268
  columns,
6227
- currentSort,
6228
6269
  onRowClick,
6229
6270
  customView,
6230
6271
  onColumnResize: onColumnResizeInternal,
@@ -6232,7 +6273,7 @@ var VirtualTable = React.memo(function VirtualTable({ data, onResetPagination, o
6232
6273
  filter: filterRef.current,
6233
6274
  onColumnSort,
6234
6275
  onFilterUpdate: onFilterUpdateInternal,
6235
- sortByProperty,
6276
+ sortIndex,
6236
6277
  hoverRow: hoverRow ?? false,
6237
6278
  createFilterField,
6238
6279
  rowClassName,
@@ -6249,7 +6290,6 @@ var VirtualTable = React.memo(function VirtualTable({ data, onResetPagination, o
6249
6290
  rowHeight,
6250
6291
  cellRenderer,
6251
6292
  columns,
6252
- currentSort,
6253
6293
  onRowClick,
6254
6294
  customView,
6255
6295
  onColumnResizeInternal,
@@ -6257,7 +6297,7 @@ var VirtualTable = React.memo(function VirtualTable({ data, onResetPagination, o
6257
6297
  filterInput,
6258
6298
  onColumnSort,
6259
6299
  onFilterUpdateInternal,
6260
- sortByProperty,
6300
+ sortIndex,
6261
6301
  hoverRow,
6262
6302
  createFilterField,
6263
6303
  rowClassName,