@olenbetong/appframe-ds 1.0.1 → 1.0.3

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @olenbetong/appframe-ds
2
2
 
3
+ ## 1.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - d503ffe: Name the neutral and brand colour sets explicitly instead of the `[data-color]` scoped aliases (`--ds-color-text-default`, `--ds-color-surface-hover`, `--ds-color-base-default`, ...) in the autocomplete, lookup combobox, filter and progress styles. The aliases came from the 1.13 theme token build; the 1.21 build the site now uses replaces them with the `--ds-color-main-*` set, and Designsystemet's own CSS no longer relies on them.
8
+ - Updated dependencies [f0403b7]
9
+ - @olenbetong/appframe-core@2.14.0
10
+ - @olenbetong/appframe-data@1.7.2
11
+ - @olenbetong/appframe-react@2.2.1
12
+
13
+ ## 1.0.2
14
+
15
+ ### Patch Changes
16
+
17
+ - e7e2be3: Migrate the lookup combobox and grid to react-window 2 (`List` with `rowComponent`/`rowProps`, dynamic row heights via `useDynamicRowHeight`), update @atlaskit/pragmatic-drag-and-drop to 3.x and its non-deprecated entry points, and drop the unused react-virtualized-auto-sizer and @types/react-window dependencies.
18
+ - Updated dependencies [c48fb2f]
19
+ - @olenbetong/appframe-react@2.2.0
20
+
3
21
  ## 1.0.1
4
22
 
5
23
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olenbetong/appframe-ds",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Components that bind Designsystemet components to Appframe data objects",
5
5
  "type": "module",
6
6
  "types": "./es/index.d.ts",
@@ -13,6 +13,10 @@
13
13
  "types": "./es/grid/index.d.ts",
14
14
  "import": "./es/grid/index.js"
15
15
  },
16
+ "./progress": {
17
+ "types": "./es/progress/index.d.ts",
18
+ "import": "./es/progress/index.js"
19
+ },
16
20
  "./mdi": {
17
21
  "types": "./es/mdi/index.d.ts",
18
22
  "import": "./es/mdi/index.js"
@@ -56,23 +60,22 @@
56
60
  }
57
61
  },
58
62
  "dependencies": {
59
- "@atlaskit/pragmatic-drag-and-drop": "2.0.2",
63
+ "@atlaskit/pragmatic-drag-and-drop": "3.1.0",
60
64
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "2.2.0",
61
65
  "@base-ui/react": "1.8.0",
62
66
  "clsx": "^2.1.1",
63
- "dockview-react": "8.3.0",
67
+ "dockview-react": "8.3.1",
64
68
  "localforage": "^1.10.0",
65
69
  "mitt": "^3.0.1",
66
70
  "react": "19.3.0",
67
71
  "react-dom": "19.3.0",
68
- "react-virtualized-auto-sizer": "^1.0.26",
69
- "react-window": "^1.8.11",
70
- "@olenbetong/appframe-data": "1.7.1",
71
- "@olenbetong/appframe-core": "2.13.1",
72
- "@olenbetong/appframe-react": "2.1.4"
72
+ "react-window": "2.3.1",
73
+ "@olenbetong/appframe-core": "2.14.0",
74
+ "@olenbetong/appframe-react": "2.2.1",
75
+ "@olenbetong/appframe-data": "1.7.2"
73
76
  },
74
77
  "devDependencies": {
75
- "@digdir/designsystemet-react": "1.21.0",
78
+ "@digdir/designsystemet-react": "1.21.1",
76
79
  "@emotion/react": "^11.14.0",
77
80
  "@emotion/styled": "^11.14.1",
78
81
  "@mui/material": "9.4.0",
@@ -85,8 +88,7 @@
85
88
  "@testing-library/user-event": "^14.6.7",
86
89
  "@types/react": "19.3.0",
87
90
  "@types/react-dom": "19.3.0",
88
- "@types/react-window": "^1.8.8",
89
- "happy-dom": "^20.14.3",
91
+ "happy-dom": "20.14.5",
90
92
  "react-error-boundary": "6.1.5",
91
93
  "react-router": "8.3.1",
92
94
  "typescript": "7.0.2"
@@ -64,7 +64,7 @@
64
64
  left: -100%;
65
65
  height: 100%;
66
66
  width: 50%;
67
- background-color: var(--ds-color-base-default);
67
+ background-color: var(--ds-color-brand-base-default);
68
68
  animation: ObCombobox-progress-anim 1.5s ease-in-out infinite;
69
69
  }
70
70
 
@@ -6,8 +6,8 @@ import type { DataObject } from "@olenbetong/appframe-data";
6
6
  import { type LookupColumn, useComboboxData } from "@olenbetong/appframe-react";
7
7
 
8
8
  import clsx from "clsx";
9
- import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
10
- import { VariableSizeList as VirtualList } from "react-window";
9
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
10
+ import { List, type RowComponentProps, useDynamicRowHeight, useListRef } from "react-window";
11
11
 
12
12
  import { LookupGrid } from "./LookupGrid.js";
13
13
 
@@ -43,6 +43,46 @@ export type LookupComboboxProps<T extends Record<string, unknown>> =
43
43
  | LookupComboboxListProps<T>
44
44
  | LookupComboboxGridProps<T>;
45
45
 
46
+ type LookupComboboxRowProps<T extends Record<string, unknown>> = {
47
+ data: T[];
48
+ currentIndex: number;
49
+ isDesktop: boolean;
50
+ getItemId: (item: T | undefined) => string | undefined;
51
+ isItemSelected?: (item: T) => boolean;
52
+ onSelectItem: (item: T) => void;
53
+ renderItem: LookupComboboxListProps<T>["renderItem"];
54
+ };
55
+
56
+ function LookupComboboxRow<T extends Record<string, unknown>>({
57
+ index,
58
+ style,
59
+ data,
60
+ currentIndex,
61
+ isDesktop,
62
+ getItemId,
63
+ isItemSelected,
64
+ onSelectItem,
65
+ renderItem: ItemComponent,
66
+ }: RowComponentProps<LookupComboboxRowProps<T>>) {
67
+ let item = data[index];
68
+ let itemId = getItemId(item);
69
+ let active = index === currentIndex;
70
+
71
+ return (
72
+ <ItemComponent
73
+ className={clsx("ObCombobox-listItem", active && "active", isItemSelected?.(item) && "selected")}
74
+ aria-selected={active}
75
+ onClick={() => onSelectItem(item)}
76
+ item={item}
77
+ style={{
78
+ ...style,
79
+ padding: isDesktop ? "0 8px" : "8px",
80
+ }}
81
+ id={itemId}
82
+ />
83
+ );
84
+ }
85
+
46
86
  export function LookupCombobox<T extends Record<string, unknown>>(props: LookupComboboxProps<T>) {
47
87
  let {
48
88
  nullable,
@@ -55,7 +95,7 @@ export function LookupCombobox<T extends Record<string, unknown>>(props: LookupC
55
95
  } = props;
56
96
  let isGrid = props.variant === "grid";
57
97
  let columns = isGrid ? (props as LookupComboboxGridProps<T>).columns : undefined;
58
- let listRef = useRef<VirtualList>(null);
98
+ let listRef = useListRef(null);
59
99
 
60
100
  let [isDesktop, setIsDesktop] = useState(
61
101
  () => typeof window !== "undefined" && window.matchMedia("(width >= 768px) and (pointer: fine)").matches,
@@ -102,47 +142,15 @@ export function LookupCombobox<T extends Record<string, unknown>>(props: LookupC
102
142
  return () => resizeObserver.disconnect();
103
143
  }, []);
104
144
 
105
- let sizeMap = useRef(new Map<number, number>());
106
- let getItemSize = (index: number) => sizeMap.current.get(index) ?? 56;
107
- let setSize = useCallback((index: number, size: number) => {
108
- if (sizeMap.current.get(index) !== size) {
109
- sizeMap.current.set(index, size);
110
- // listRef will not be set on the initial render, so delay resetting
111
- setTimeout(() => listRef.current?.resetAfterIndex(index, true));
112
- }
113
- }, []);
114
-
115
- let Row = ({ index, style: { height, ...style } }: { index: number; style: React.CSSProperties }) => {
116
- let rowRef = useRef<HTMLLIElement>(null);
117
-
118
- useLayoutEffect(() => {
119
- if (!rowRef.current) return;
120
- setSize(index, rowRef.current.offsetHeight);
121
- }, [index]);
122
-
123
- let item = data[index];
124
- let itemId = getItemId(item);
125
- let active = index === currentIndex;
126
- let ItemComponent = (props as LookupComboboxListProps<T>).renderItem;
127
-
128
- return (
129
- <ItemComponent
130
- className={clsx("ObCombobox-listItem", active && "active", isItemSelected?.(item) && "selected")}
131
- aria-selected={active}
132
- onClick={() => handleSelectItem(item)}
133
- item={item}
134
- style={{
135
- ...style,
136
- padding: isDesktop ? "0 8px" : "8px",
137
- }}
138
- ref={rowRef}
139
- key={itemId}
140
- id={itemId}
141
- />
142
- );
143
- };
145
+ // Rows are rendered by the consumer and have no fixed height, so let the
146
+ // list measure them as they mount.
147
+ let rowHeight = useDynamicRowHeight({ defaultRowHeight: 56 });
144
148
 
145
- useEffect(() => listRef.current?.scrollToItem(currentIndex, "smart"), [currentIndex]);
149
+ useEffect(() => {
150
+ if (currentIndex >= 0 && currentIndex < data.length) {
151
+ listRef.current?.scrollToRow({ index: currentIndex, align: "smart" });
152
+ }
153
+ }, [currentIndex, data.length, listRef]);
146
154
 
147
155
  useEffect(() => {
148
156
  if (initialSearchValue !== undefined) {
@@ -203,16 +211,24 @@ export function LookupCombobox<T extends Record<string, unknown>>(props: LookupC
203
211
  />
204
212
  ) : (
205
213
  <div className="ObCombobox-list" role="listbox">
206
- <VirtualList
207
- ref={listRef}
208
- height={height}
209
- width="100%"
210
- itemCount={data.length}
211
- itemSize={getItemSize}
214
+ <List<LookupComboboxRowProps<T>>
215
+ role="presentation"
216
+ listRef={listRef}
217
+ style={{ height }}
218
+ rowCount={data.length}
219
+ rowHeight={rowHeight}
212
220
  overscanCount={5}
213
- >
214
- {Row}
215
- </VirtualList>
221
+ rowComponent={LookupComboboxRow}
222
+ rowProps={{
223
+ data,
224
+ currentIndex,
225
+ isDesktop,
226
+ getItemId,
227
+ isItemSelected,
228
+ onSelectItem: handleSelectItem,
229
+ renderItem: (props as LookupComboboxListProps<T>).renderItem,
230
+ }}
231
+ />
216
232
  </div>
217
233
  ))}
218
234
  </div>
@@ -4,8 +4,8 @@ import { getLocalizedString } from "@olenbetong/appframe-core";
4
4
  import type { LookupColumn } from "@olenbetong/appframe-react";
5
5
 
6
6
  import clsx from "clsx";
7
- import { useEffect, useMemo, useRef } from "react";
8
- import { FixedSizeList as VirtualList } from "react-window";
7
+ import { useEffect, useMemo } from "react";
8
+ import { List, type RowComponentProps, useListRef } from "react-window";
9
9
 
10
10
  const ROW_HEIGHT = 40;
11
11
  const HEADER_HEIGHT = 40;
@@ -29,6 +29,49 @@ function getColumnWidth<T extends Record<string, unknown>>(column: LookupColumn<
29
29
  return column.width ?? "minmax(80px, 1fr)";
30
30
  }
31
31
 
32
+ type LookupGridRowProps<T extends Record<string, unknown>> = Omit<LookupGridProps<T>, "height"> & {
33
+ gridTemplateColumns: string;
34
+ };
35
+
36
+ function LookupGridRow<T extends Record<string, unknown>>({
37
+ index,
38
+ style,
39
+ columns,
40
+ data,
41
+ currentIndex,
42
+ currentColumn,
43
+ gridTemplateColumns,
44
+ getItemId,
45
+ isItemSelected,
46
+ onSelectItem,
47
+ }: RowComponentProps<LookupGridRowProps<T>>) {
48
+ let item = data[index];
49
+ let itemId = getItemId(item);
50
+ let active = index === currentIndex;
51
+
52
+ return (
53
+ <div
54
+ role="row"
55
+ aria-selected={active}
56
+ className={clsx("ObLookupGrid-row", active && "active", isItemSelected?.(item) && "selected")}
57
+ style={{ ...style, gridTemplateColumns }}
58
+ onClick={() => onSelectItem(item)}
59
+ id={itemId}
60
+ >
61
+ {columns.map((column, columnIndex) => (
62
+ <div
63
+ role="gridcell"
64
+ className={clsx("ObLookupGrid-cell", active && columnIndex === currentColumn && "active")}
65
+ key={column.field}
66
+ title={String(item[column.field] ?? "")}
67
+ >
68
+ {column.renderCell ? column.renderCell(item[column.field], item) : String(item[column.field] ?? "")}
69
+ </div>
70
+ ))}
71
+ </div>
72
+ );
73
+ }
74
+
32
75
  export function LookupGrid<T extends Record<string, unknown>>({
33
76
  columns,
34
77
  data,
@@ -39,40 +82,14 @@ export function LookupGrid<T extends Record<string, unknown>>({
39
82
  isItemSelected,
40
83
  onSelectItem,
41
84
  }: LookupGridProps<T>) {
42
- let listRef = useRef<VirtualList>(null);
85
+ let listRef = useListRef(null);
43
86
  let gridTemplateColumns = useMemo(() => columns.map(getColumnWidth).join(" "), [columns]);
44
87
 
45
- useEffect(() => listRef.current?.scrollToItem(currentIndex, "smart"), [currentIndex]);
46
-
47
- // oxlint-disable-next-line react/no-nested-component-definitions -- react-window row renderer
48
- let Row = ({ index, style }: { index: number; style: React.CSSProperties }) => {
49
- let item = data[index];
50
- let itemId = getItemId(item);
51
- let active = index === currentIndex;
52
-
53
- return (
54
- <div
55
- role="row"
56
- aria-selected={active}
57
- className={clsx("ObLookupGrid-row", active && "active", isItemSelected?.(item) && "selected")}
58
- style={{ ...style, gridTemplateColumns }}
59
- onClick={() => onSelectItem(item)}
60
- key={itemId}
61
- id={itemId}
62
- >
63
- {columns.map((column, columnIndex) => (
64
- <div
65
- role="gridcell"
66
- className={clsx("ObLookupGrid-cell", active && columnIndex === currentColumn && "active")}
67
- key={column.field}
68
- title={String(item[column.field] ?? "")}
69
- >
70
- {column.renderCell ? column.renderCell(item[column.field], item) : String(item[column.field] ?? "")}
71
- </div>
72
- ))}
73
- </div>
74
- );
75
- };
88
+ useEffect(() => {
89
+ if (currentIndex >= 0 && currentIndex < data.length) {
90
+ listRef.current?.scrollToRow({ index: currentIndex, align: "smart" });
91
+ }
92
+ }, [currentIndex, data.length, listRef]);
76
93
 
77
94
  return (
78
95
  <div role="grid" aria-label={getLocalizedString("Search results")} className="ObLookupGrid-root">
@@ -83,16 +100,25 @@ export function LookupGrid<T extends Record<string, unknown>>({
83
100
  </div>
84
101
  ))}
85
102
  </div>
86
- <VirtualList
87
- ref={listRef}
88
- height={Math.max(0, height - HEADER_HEIGHT)}
89
- width="100%"
90
- itemCount={data.length}
91
- itemSize={ROW_HEIGHT}
103
+ <List<LookupGridRowProps<T>>
104
+ role="rowgroup"
105
+ listRef={listRef}
106
+ style={{ height: Math.max(0, height - HEADER_HEIGHT) }}
107
+ rowCount={data.length}
108
+ rowHeight={ROW_HEIGHT}
92
109
  overscanCount={5}
93
- >
94
- {Row}
95
- </VirtualList>
110
+ rowComponent={LookupGridRow}
111
+ rowProps={{
112
+ columns,
113
+ data,
114
+ currentIndex,
115
+ currentColumn,
116
+ gridTemplateColumns,
117
+ getItemId,
118
+ isItemSelected,
119
+ onSelectItem,
120
+ }}
121
+ />
96
122
  </div>
97
123
  );
98
124
  }
@@ -199,23 +199,23 @@
199
199
  }
200
200
 
201
201
  /*
202
- * The `accent` colour set does not exist in every Designsystemet theme, so the
203
- * option states use the contextual `--ds-color-*` aliases the same tokens
204
- * Designsystemet's own Suggestion list uses.
202
+ * The `accent` colour set does not exist in every Designsystemet theme, and the
203
+ * contextual `--ds-color-*` aliases of the 1.13 token build are gone since 1.21,
204
+ * so the option states use the neutral set explicitly.
205
205
  */
206
206
  .ObAutocomplete-item[data-highlighted] {
207
- color: var(--ds-color-text-default);
208
- background-color: var(--ds-color-surface-hover);
207
+ color: var(--ds-color-neutral-text-default);
208
+ background-color: var(--ds-color-neutral-surface-hover);
209
209
  }
210
210
 
211
211
  .ObAutocomplete-item[data-selected] {
212
212
  font-weight: var(--ds-font-weight-medium);
213
- color: var(--ds-color-text-default);
214
- background-color: var(--ds-color-surface-tinted);
213
+ color: var(--ds-color-neutral-text-default);
214
+ background-color: var(--ds-color-neutral-surface-tinted);
215
215
  }
216
216
 
217
217
  .ObAutocomplete-item[data-selected][data-highlighted] {
218
- background-color: var(--ds-color-surface-active);
218
+ background-color: var(--ds-color-neutral-surface-active);
219
219
  }
220
220
 
221
221
  .ObAutocomplete-item[data-disabled] {
@@ -25,7 +25,7 @@
25
25
  grid-area: sidebar;
26
26
  overflow: auto;
27
27
  padding: var(--ds-size-2);
28
- border: var(--ds-border-width-default) solid var(--ds-color-border-subtle);
28
+ border: var(--ds-border-width-default) solid var(--ds-color-neutral-border-subtle);
29
29
  border-radius: var(--ds-border-radius-md);
30
30
  }
31
31
 
@@ -199,8 +199,8 @@
199
199
  }
200
200
 
201
201
  &[aria-pressed="true"] {
202
- background-color: var(--ds-color-surface-active);
203
- color: var(--ds-color-text-default);
202
+ background-color: var(--ds-color-neutral-surface-active);
203
+ color: var(--ds-color-neutral-text-default);
204
204
  }
205
205
 
206
206
  svg {
@@ -15,7 +15,7 @@
15
15
 
16
16
  .ObFilterShareDialog-candidateLogin {
17
17
  margin-inline-start: auto;
18
- color: var(--ds-color-text-subtle);
18
+ color: var(--ds-color-neutral-text-subtle);
19
19
  }
20
20
 
21
21
  .ObFilterShareDialog-table {
@@ -37,14 +37,14 @@
37
37
  border: none;
38
38
  border-radius: var(--ds-border-radius-sm);
39
39
  background: none;
40
- color: var(--ds-color-text-default);
40
+ color: var(--ds-color-neutral-text-default);
41
41
  font: inherit;
42
42
  text-align: start;
43
43
  cursor: pointer;
44
44
  }
45
45
 
46
46
  .ObSavedFilterTree-node:hover {
47
- background: var(--ds-color-surface-hover);
47
+ background: var(--ds-color-neutral-surface-hover);
48
48
  }
49
49
 
50
50
  .ObSavedFilterTree-node:focus-visible {
@@ -53,7 +53,7 @@
53
53
  }
54
54
 
55
55
  .ObSavedFilterTree-selected {
56
- background: var(--ds-color-surface-active);
56
+ background: var(--ds-color-neutral-surface-active);
57
57
  }
58
58
 
59
59
  .ObSavedFilterTree-dirty .ObSavedFilterTree-label {
@@ -68,7 +68,7 @@
68
68
  justify-content: center;
69
69
  inline-size: var(--ds-size-6);
70
70
  block-size: var(--ds-size-6);
71
- color: var(--ds-color-text-subtle);
71
+ color: var(--ds-color-neutral-text-subtle);
72
72
  }
73
73
 
74
74
  .ObSavedFilterTree-chevron > *,
@@ -87,7 +87,7 @@
87
87
  /* Lines up with the child labels, past their icon. */
88
88
  padding: var(--ds-size-1) var(--ds-size-2) var(--ds-size-1)
89
89
  calc(var(--ds-size-2) + var(--ds-size-6) + var(--ds-size-1));
90
- color: var(--ds-color-text-subtle);
90
+ color: var(--ds-color-neutral-text-subtle);
91
91
  font-style: italic;
92
92
  }
93
93
 
@@ -8,7 +8,7 @@
8
8
  inline-size: 100%;
9
9
  block-size: var(--obLinearProgress-thickness);
10
10
  overflow: hidden;
11
- background-color: var(--ds-color-surface-active, #cddffb);
11
+ background-color: var(--ds-color-brand-surface-active, #cddffb);
12
12
  border-radius: var(--ds-border-radius-full, 624.9375rem);
13
13
  }
14
14
 
@@ -33,7 +33,7 @@
33
33
  inset-block: 0;
34
34
  inset-inline-start: 0;
35
35
  inline-size: 100%;
36
- background-color: var(--ds-color-base-default, #0062ba);
36
+ background-color: var(--ds-color-brand-base-default, #0062ba);
37
37
  border-radius: inherit;
38
38
  }
39
39
 
@@ -1,4 +1,4 @@
1
- import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
1
+ import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter";
2
2
  import { type Edge, extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
3
3
  import { useEffect, useRef } from "react";
4
4
 
@@ -1,5 +1,5 @@
1
- import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
2
- import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
1
+ import { combine } from "@atlaskit/pragmatic-drag-and-drop/utils/combine";
2
+ import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter";
3
3
  import {
4
4
  attachClosestEdge,
5
5
  type Edge,