@olenbetong/appframe-ds 0.4.1 → 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.
Files changed (128) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/package.json +4 -4
  3. package/scripts/copyAssets.mjs +12 -0
  4. package/src/AfLookup/Lookup.tsx +3 -4
  5. package/src/AfLookup/LookupEdit.tsx +3 -4
  6. package/src/binding/CancelButton.tsx +4 -1
  7. package/src/binding/CancelIconButton.tsx +2 -2
  8. package/src/binding/DataEditToolbar.tsx +1 -2
  9. package/src/binding/DeleteButton.tsx +6 -2
  10. package/src/binding/DeleteIconButton.tsx +5 -4
  11. package/src/binding/RefreshButton.tsx +7 -3
  12. package/src/binding/RefreshIconButton.tsx +6 -5
  13. package/src/binding/RefreshRowIconButton.tsx +6 -5
  14. package/src/binding/SaveButton.tsx +14 -4
  15. package/src/binding/SaveIconButton.tsx +13 -6
  16. package/src/index.ts +5 -0
  17. package/src/input/InputAdornments.css +75 -0
  18. package/src/input/InputAdornments.tsx +97 -0
  19. package/src/input/index.ts +1 -0
  20. package/src/layout/AppLayoutDesktop.tsx +3 -0
  21. package/src/layout/AppLayoutMobile.tsx +3 -0
  22. package/src/layout/AppMenu.css +1 -0
  23. package/src/layout/PageContainer.tsx +6 -0
  24. package/src/layout/index.tsx +5 -0
  25. package/src/list/ItemList.css +30 -0
  26. package/src/list/ItemList.tsx +52 -0
  27. package/src/list/ItemListItem.tsx +41 -0
  28. package/src/list/ItemListItemText.tsx +23 -0
  29. package/src/list/index.ts +3 -0
  30. package/src/page/Page.css +45 -0
  31. package/src/page/Page.tsx +70 -0
  32. package/src/page/PageBlock.css +59 -0
  33. package/src/page/PageBlock.tsx +78 -0
  34. package/src/page/PageHeader.css +148 -0
  35. package/src/page/PageHeader.tsx +184 -0
  36. package/src/page/index.ts +3 -0
  37. package/src/paper/Paper.css +35 -0
  38. package/src/paper/Paper.tsx +50 -0
  39. package/src/paper/index.ts +1 -0
  40. package/src/progress/LinearProgress.css +93 -0
  41. package/src/progress/LinearProgress.tsx +104 -0
  42. package/src/progress/index.ts +1 -0
  43. package/es/AfLookup/BoundLookup.d.ts +0 -10
  44. package/es/AfLookup/BoundLookup.js +0 -16
  45. package/es/AfLookup/Combobox.css +0 -78
  46. package/es/AfLookup/Combobox.d.ts +0 -32
  47. package/es/AfLookup/Combobox.js +0 -76
  48. package/es/AfLookup/Lookup.css +0 -125
  49. package/es/AfLookup/Lookup.d.ts +0 -35
  50. package/es/AfLookup/Lookup.js +0 -59
  51. package/es/AfLookup/LookupEdit.d.ts +0 -21
  52. package/es/AfLookup/LookupEdit.js +0 -49
  53. package/es/AfLookup/LookupGrid.css +0 -57
  54. package/es/AfLookup/LookupGrid.d.ts +0 -13
  55. package/es/AfLookup/LookupGrid.js +0 -27
  56. package/es/AfLookup/index.d.ts +0 -5
  57. package/es/AfLookup/index.js +0 -5
  58. package/es/binding/BoundCheckbox.d.ts +0 -9
  59. package/es/binding/BoundCheckbox.js +0 -13
  60. package/es/binding/BoundDatePicker.d.ts +0 -7
  61. package/es/binding/BoundDatePicker.js +0 -12
  62. package/es/binding/BoundDateTimePicker.d.ts +0 -7
  63. package/es/binding/BoundDateTimePicker.js +0 -14
  64. package/es/binding/BoundInput.d.ts +0 -6
  65. package/es/binding/BoundInput.js +0 -12
  66. package/es/binding/BoundNumericTextField.d.ts +0 -20
  67. package/es/binding/BoundNumericTextField.js +0 -58
  68. package/es/binding/BoundSelect.d.ts +0 -9
  69. package/es/binding/BoundSelect.js +0 -13
  70. package/es/binding/BoundSwitch.d.ts +0 -9
  71. package/es/binding/BoundSwitch.js +0 -13
  72. package/es/binding/BoundTextField.d.ts +0 -7
  73. package/es/binding/BoundTextField.js +0 -22
  74. package/es/binding/CancelButton.d.ts +0 -3
  75. package/es/binding/CancelButton.js +0 -14
  76. package/es/binding/CancelIconButton.d.ts +0 -2
  77. package/es/binding/CancelIconButton.js +0 -15
  78. package/es/binding/DataEditToolbar.d.ts +0 -13
  79. package/es/binding/DataEditToolbar.js +0 -28
  80. package/es/binding/DataObjectSelect.d.ts +0 -50
  81. package/es/binding/DataObjectSelect.js +0 -26
  82. package/es/binding/DeleteButton.d.ts +0 -8
  83. package/es/binding/DeleteButton.js +0 -14
  84. package/es/binding/DeleteIconButton.d.ts +0 -10
  85. package/es/binding/DeleteIconButton.js +0 -15
  86. package/es/binding/RefreshButton.d.ts +0 -3
  87. package/es/binding/RefreshButton.js +0 -14
  88. package/es/binding/RefreshIconButton.d.ts +0 -2
  89. package/es/binding/RefreshIconButton.js +0 -16
  90. package/es/binding/RefreshRowIconButton.d.ts +0 -7
  91. package/es/binding/RefreshRowIconButton.js +0 -15
  92. package/es/binding/SaveButton.d.ts +0 -3
  93. package/es/binding/SaveButton.js +0 -14
  94. package/es/binding/SaveIconButton.d.ts +0 -2
  95. package/es/binding/SaveIconButton.js +0 -15
  96. package/es/binding/index.d.ts +0 -19
  97. package/es/binding/index.js +0 -19
  98. package/es/index.d.ts +0 -3
  99. package/es/index.js +0 -3
  100. package/es/layout/AppLayoutDesktop.css +0 -65
  101. package/es/layout/AppLayoutDesktop.d.ts +0 -3
  102. package/es/layout/AppLayoutDesktop.js +0 -19
  103. package/es/layout/AppLayoutMobile.css +0 -75
  104. package/es/layout/AppLayoutMobile.d.ts +0 -3
  105. package/es/layout/AppLayoutMobile.js +0 -18
  106. package/es/layout/AppMenu.css +0 -67
  107. package/es/layout/AppMenu.d.ts +0 -8
  108. package/es/layout/AppMenu.js +0 -36
  109. package/es/layout/AppMenuItem.css +0 -97
  110. package/es/layout/AppMenuItem.d.ts +0 -50
  111. package/es/layout/AppMenuItem.js +0 -14
  112. package/es/layout/AppMenuSubheader.css +0 -18
  113. package/es/layout/AppMenuSubheader.d.ts +0 -4
  114. package/es/layout/AppMenuSubheader.js +0 -6
  115. package/es/layout/ErrorBoundary.css +0 -101
  116. package/es/layout/ErrorBoundary.d.ts +0 -30
  117. package/es/layout/ErrorBoundary.js +0 -81
  118. package/es/layout/PageContainer.css +0 -29
  119. package/es/layout/PageContainer.d.ts +0 -7
  120. package/es/layout/PageContainer.js +0 -6
  121. package/es/layout/PartnerToggle.d.ts +0 -5
  122. package/es/layout/PartnerToggle.js +0 -9
  123. package/es/layout/constants.d.ts +0 -2
  124. package/es/layout/constants.js +0 -2
  125. package/es/layout/index.css +0 -65
  126. package/es/layout/index.d.ts +0 -33
  127. package/es/layout/index.js +0 -31
  128. package/tsconfig.build.tsbuildinfo +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @olenbetong/appframe-ds
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0147e80: Add `LinearProgress` component: a designsystemet-styled linear progress bar equivalent to MUI `LinearProgress`, with indeterminate and determinate modes and an `overlay` prop for an absolutely positioned bar that does not affect the layout.
8
+ - 56080ed: Add `InputAdornments` and `InputSlot`: an input with adornments (MUI `TextField` start/end adornment equivalent) where the border, background, hover and focus styling lives on a wrapper around the input and its adornments. Adornments are passed as children, and `InputSlot` controls where the input is placed (defaults to the start).
9
+ - bc89c1e: Migrated all icons in appframe-ds from `@mui/icons-material` to `@navikt/aksel-icons`. Bound action buttons (`SaveButton`, `DeleteButton`, `CancelButton`, `RefreshButton` and their icon-only variants) now show a leading icon that is hidden while loading, so button width no longer changes when toggling the loading state. `SaveButton` and `SaveIconButton` also display a small warning badge on the save icon when the current record has unsaved changes, matching the existing appframe-mui behavior.
10
+
11
+ ### Patch Changes
12
+
13
+ - 0147e80: Make the indeterminate `LinearProgress` animation more visible by using a single travelling bar that stretches and shrinks, instead of the two subtle MUI-style bars.
14
+
15
+ ## 0.5.0
16
+
17
+ ### Minor Changes
18
+
19
+ - a118bef: Add Aksel-inspired `Page` primitives (`Page`, `Page.Block`, `Page.Header`) replacing the now-deprecated `AppLayout`, `AppLayoutDesktop`, `AppLayoutMobile` and `PageContainer`. `Page` supports both normal document scrolling and full-viewport app layouts (`layout="app"`, replacing `fullHeight`), `Page.Block` handles max-width/gutters/centering, and `Page.Header` comes in `appbar` (with responsive app menu drawer) and `plain` variants — both prepared for the upcoming sidebar-free site layout.
20
+
21
+ Add a `Paper` surface component (MUI `Paper` equivalent): neutral surface background, rounded corners and `--ds-shadow-*` elevation, with an optional `border` for an outlined variant. Pages using the `Page` primitive get a tinted body background (`--ds-color-neutral-background-tinted`) so surfaces stand out, matching the old site design.
22
+
23
+ Add `ItemList` components (`ItemList`, `ItemList.Item`, `ItemList.ItemText`) equivalent to MUI `List`/`ListItem`/`ListItemText`, for flex-row lists with dividers and primary/secondary text — typically rendered inside a `Paper` (`<Paper as={ItemList}>`).
24
+
3
25
  ## 0.4.1
4
26
 
5
27
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olenbetong/appframe-ds",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "Components that bind Designsystemet components to Appframe data objects",
5
5
  "type": "module",
6
6
  "types": "./es/index.d.ts",
@@ -16,7 +16,7 @@
16
16
  "license": "MIT",
17
17
  "peerDependencies": {
18
18
  "@digdir/designsystemet-react": ">=1.18.0",
19
- "@mui/icons-material": ">=7.0.0",
19
+ "@navikt/aksel-icons": ">=8.16.1",
20
20
  "react": ">=19.2.8",
21
21
  "react-dom": ">=19.2.8",
22
22
  "react-error-boundary": ">=6.1.2",
@@ -25,14 +25,14 @@
25
25
  "dependencies": {
26
26
  "@olenbetong/appframe-core": "2.11.10",
27
27
  "@olenbetong/appframe-data": "1.5.0",
28
- "@olenbetong/appframe-react": "1.23.0",
28
+ "@olenbetong/appframe-react": "2.0.0",
29
29
  "react-window": "^1.8.11",
30
30
  "react-virtualized-auto-sizer": "^1.0.26",
31
31
  "clsx": "^2.1.1"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@digdir/designsystemet-react": "1.18.0",
35
- "@mui/icons-material": "9.2.0",
35
+ "@navikt/aksel-icons": "8.16.1",
36
36
  "@types/react": "19.2.17",
37
37
  "@types/react-dom": "19.2.3",
38
38
  "@types/react-window": "^1.8.8",
@@ -12,9 +12,21 @@ const files = [
12
12
  ["src/layout/index.css", "es/layout/index.css"],
13
13
  ["src/layout/ErrorBoundary.css", "es/layout/ErrorBoundary.css"],
14
14
  ["src/layout/PageContainer.css", "es/layout/PageContainer.css"],
15
+ ["src/page/Page.css", "es/page/Page.css"],
16
+ ["src/page/PageBlock.css", "es/page/PageBlock.css"],
17
+ ["src/page/PageHeader.css", "es/page/PageHeader.css"],
18
+ ["src/list/ItemList.css", "es/list/ItemList.css"],
19
+ ["src/input/InputAdornments.css", "es/input/InputAdornments.css"],
20
+ ["src/paper/Paper.css", "es/paper/Paper.css"],
21
+ ["src/progress/LinearProgress.css", "es/progress/LinearProgress.css"],
15
22
  ];
16
23
 
17
24
  mkdirSync("es/layout", { recursive: true });
25
+ mkdirSync("es/input", { recursive: true });
26
+ mkdirSync("es/list", { recursive: true });
27
+ mkdirSync("es/page", { recursive: true });
28
+ mkdirSync("es/paper", { recursive: true });
29
+ mkdirSync("es/progress", { recursive: true });
18
30
 
19
31
  for (let [src, dest] of files) {
20
32
  cpSync(src, dest);
@@ -1,8 +1,7 @@
1
1
  import "./Lookup.css";
2
2
 
3
3
  import { Button, Textfield, type TextfieldProps } from "@digdir/designsystemet-react";
4
- import ClearIcon from "@mui/icons-material/Close";
5
- import SearchIcon from "@mui/icons-material/Search";
4
+ import { MagnifyingGlassIcon, XMarkIcon } from "@navikt/aksel-icons";
6
5
  import { getLocalizedString } from "@olenbetong/appframe-core";
7
6
  import { type DistributiveOmit, useLookupState } from "@olenbetong/appframe-react";
8
7
 
@@ -143,7 +142,7 @@ export function AfLookup<T extends Record<string, unknown>>({
143
142
  document.getElementById(inputId)?.focus();
144
143
  }}
145
144
  >
146
- <ClearIcon />
145
+ <XMarkIcon />
147
146
  </Button>
148
147
  )}
149
148
  <Button
@@ -153,7 +152,7 @@ export function AfLookup<T extends Record<string, unknown>>({
153
152
  aria-label={getLocalizedString("Search")}
154
153
  onClick={() => openDialog()}
155
154
  >
156
- <SearchIcon />
155
+ <MagnifyingGlassIcon />
157
156
  </Button>
158
157
  </div>
159
158
  </div>
@@ -1,8 +1,7 @@
1
1
  import "./Lookup.css";
2
2
 
3
3
  import { Button, Textfield, type TextfieldProps } from "@digdir/designsystemet-react";
4
- import ClearIcon from "@mui/icons-material/Close";
5
- import SearchIcon from "@mui/icons-material/Search";
4
+ import { MagnifyingGlassIcon, XMarkIcon } from "@navikt/aksel-icons";
6
5
  import { getLocalizedString } from "@olenbetong/appframe-core";
7
6
  import type { DistributiveOmit } from "@olenbetong/appframe-react";
8
7
 
@@ -108,7 +107,7 @@ export function AfLookupEdit<T extends Record<string, unknown>>({
108
107
  document.getElementById(inputId)?.focus();
109
108
  }}
110
109
  >
111
- <ClearIcon />
110
+ <XMarkIcon />
112
111
  </Button>
113
112
  )}
114
113
  <Button
@@ -121,7 +120,7 @@ export function AfLookupEdit<T extends Record<string, unknown>>({
121
120
  openDialog();
122
121
  }}
123
122
  >
124
- <SearchIcon />
123
+ <MagnifyingGlassIcon />
125
124
  </Button>
126
125
  </div>
127
126
  </div>
@@ -1,4 +1,5 @@
1
1
  import { Button, type ButtonProps } from "@digdir/designsystemet-react";
2
+ import { ArrowUndoIcon } from "@navikt/aksel-icons";
2
3
  import { getLocalizedString } from "@olenbetong/appframe-core";
3
4
  import { useCancelButton } from "@olenbetong/appframe-react";
4
5
  import { type ForwardRefExoticComponent, forwardRef, type RefAttributes } from "react";
@@ -6,11 +7,12 @@ import { type ForwardRefExoticComponent, forwardRef, type RefAttributes } from "
6
7
  export const CancelButton: ForwardRefExoticComponent<ButtonProps & RefAttributes<HTMLButtonElement>> = forwardRef<
7
8
  HTMLButtonElement,
8
9
  ButtonProps
9
- >(function CancelButton({ onClick, children, ...props }, ref) {
10
+ >(function CancelButton({ onClick, children, loading, ...props }, ref) {
10
11
  let { cancelEdit } = useCancelButton();
11
12
 
12
13
  return (
13
14
  <Button
15
+ loading={loading}
14
16
  onClick={(evt) => {
15
17
  onClick?.(evt);
16
18
  if (!evt.defaultPrevented) {
@@ -21,6 +23,7 @@ export const CancelButton: ForwardRefExoticComponent<ButtonProps & RefAttributes
21
23
  {...props}
22
24
  ref={ref}
23
25
  >
26
+ {!loading && <ArrowUndoIcon aria-hidden="true" />}
24
27
  {children ?? getLocalizedString("Cancel")}
25
28
  </Button>
26
29
  );
@@ -1,5 +1,5 @@
1
1
  import { Button, type ButtonProps } from "@digdir/designsystemet-react";
2
- import UndoIcon from "@mui/icons-material/Undo";
2
+ import { ArrowUndoIcon } from "@navikt/aksel-icons";
3
3
  import { getLocalizedString } from "@olenbetong/appframe-core";
4
4
  import { useCancelButton } from "@olenbetong/appframe-react";
5
5
  import { forwardRef } from "react";
@@ -24,7 +24,7 @@ export const CancelIconButton = forwardRef<HTMLButtonElement, Omit<ButtonProps,
24
24
  {...props}
25
25
  ref={ref}
26
26
  >
27
- {children ?? <UndoIcon />}
27
+ {children ?? <ArrowUndoIcon />}
28
28
  </Button>
29
29
  );
30
30
  });
@@ -1,6 +1,5 @@
1
1
  import { Button, Tooltip } from "@digdir/designsystemet-react";
2
- import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
3
- import ChevronRightIcon from "@mui/icons-material/ChevronRight";
2
+ import { ChevronLeftIcon, ChevronRightIcon } from "@navikt/aksel-icons";
4
3
  import { getLocalizedString } from "@olenbetong/appframe-core";
5
4
  import type { DataObject } from "@olenbetong/appframe-data";
6
5
  import { useCurrentIndex, useDataLength, useDataObject } from "@olenbetong/appframe-react";
@@ -1,4 +1,5 @@
1
1
  import { Button, type ButtonProps } from "@digdir/designsystemet-react";
2
+ import { TrashIcon } from "@navikt/aksel-icons";
2
3
  import { getLocalizedString } from "@olenbetong/appframe-core";
3
4
  import { type DeletePrompt, useDeleteButton } from "@olenbetong/appframe-react";
4
5
  import { type ForwardRefExoticComponent, forwardRef, type RefAttributes } from "react";
@@ -8,12 +9,13 @@ export type DeleteButtonProps = ButtonProps & { index?: number; prompt?: DeleteP
8
9
  export const DeleteButton: ForwardRefExoticComponent<DeleteButtonProps & RefAttributes<HTMLButtonElement>> = forwardRef<
9
10
  HTMLButtonElement,
10
11
  DeleteButtonProps
11
- >(function DeleteButton({ prompt, onClick, index, children, ...props }, ref) {
12
+ >(function DeleteButton({ prompt, onClick, index, children, loading, ...props }, ref) {
12
13
  let { deleting, deleteRow } = useDeleteButton(prompt);
14
+ let isLoading = deleting || loading;
13
15
 
14
16
  return (
15
17
  <Button
16
- loading={deleting}
18
+ loading={isLoading}
17
19
  data-color="danger"
18
20
  variant="secondary"
19
21
  onClick={(evt) => {
@@ -25,6 +27,8 @@ export const DeleteButton: ForwardRefExoticComponent<DeleteButtonProps & RefAttr
25
27
  {...props}
26
28
  ref={ref}
27
29
  >
30
+ {/* Icon is hidden while loading so the spinner takes its place instead of adding width */}
31
+ {!isLoading && <TrashIcon aria-hidden="true" />}
28
32
  {children ?? getLocalizedString("Delete")}
29
33
  </Button>
30
34
  );
@@ -1,5 +1,5 @@
1
1
  import { Button, type ButtonProps } from "@digdir/designsystemet-react";
2
- import DeleteIcon from "@mui/icons-material/Delete";
2
+ import { TrashIcon } from "@navikt/aksel-icons";
3
3
  import { getLocalizedString } from "@olenbetong/appframe-core";
4
4
  import { type DeletePrompt, useDeleteButton } from "@olenbetong/appframe-react";
5
5
  import { forwardRef } from "react";
@@ -7,15 +7,16 @@ import { forwardRef } from "react";
7
7
  export type DeleteIconButtonProps = Omit<ButtonProps, "icon"> & { index?: number; prompt?: DeletePrompt };
8
8
 
9
9
  export const DeleteIconButton = forwardRef<HTMLButtonElement, DeleteIconButtonProps>(function DeleteIconButton(
10
- { prompt, onClick, index, children, ...props },
10
+ { prompt, onClick, index, children, loading, ...props },
11
11
  ref,
12
12
  ) {
13
13
  let { isDeleting, deleteRow } = useDeleteButton(prompt);
14
+ let isLoading = isDeleting || loading;
14
15
 
15
16
  return (
16
17
  <Button
17
18
  icon
18
- loading={isDeleting}
19
+ loading={isLoading}
19
20
  data-color="danger"
20
21
  aria-label={getLocalizedString("Delete")}
21
22
  variant="tertiary"
@@ -28,7 +29,7 @@ export const DeleteIconButton = forwardRef<HTMLButtonElement, DeleteIconButtonPr
28
29
  {...props}
29
30
  ref={ref}
30
31
  >
31
- {children ?? <DeleteIcon />}
32
+ {children ?? <TrashIcon />}
32
33
  </Button>
33
34
  );
34
35
  });
@@ -1,4 +1,5 @@
1
1
  import { Button, type ButtonProps } from "@digdir/designsystemet-react";
2
+ import { ArrowsCirclepathIcon } from "@navikt/aksel-icons";
2
3
  import { getLocalizedString } from "@olenbetong/appframe-core";
3
4
  import { useRefreshButton } from "@olenbetong/appframe-react";
4
5
  import { type ForwardRefExoticComponent, forwardRef, type RefAttributes } from "react";
@@ -6,12 +7,13 @@ import { type ForwardRefExoticComponent, forwardRef, type RefAttributes } from "
6
7
  export const RefreshButton: ForwardRefExoticComponent<ButtonProps & RefAttributes<HTMLButtonElement>> = forwardRef<
7
8
  HTMLButtonElement,
8
9
  ButtonProps
9
- >(function RefreshButton({ onClick, children, ...props }, ref) {
10
- let { loading, refresh } = useRefreshButton();
10
+ >(function RefreshButton({ onClick, children, loading, ...props }, ref) {
11
+ let { loading: refreshing, refresh } = useRefreshButton();
12
+ let isLoading = refreshing || loading;
11
13
 
12
14
  return (
13
15
  <Button
14
- loading={loading}
16
+ loading={isLoading}
15
17
  onClick={(evt) => {
16
18
  onClick?.(evt);
17
19
  if (!evt.defaultPrevented) {
@@ -21,6 +23,8 @@ export const RefreshButton: ForwardRefExoticComponent<ButtonProps & RefAttribute
21
23
  {...props}
22
24
  ref={ref}
23
25
  >
26
+ {/* Icon is hidden while loading so the spinner takes its place instead of adding width */}
27
+ {!isLoading && <ArrowsCirclepathIcon aria-hidden="true" />}
24
28
  {children ?? getLocalizedString("Refresh")}
25
29
  </Button>
26
30
  );
@@ -1,20 +1,21 @@
1
1
  import { Button, type ButtonProps } from "@digdir/designsystemet-react";
2
- import RefreshIcon from "@mui/icons-material/Refresh";
2
+ import { ArrowsCirclepathIcon } from "@navikt/aksel-icons";
3
3
  import { getLocalizedString } from "@olenbetong/appframe-core";
4
4
  import { useDataObject, useLoading } from "@olenbetong/appframe-react";
5
5
  import { forwardRef } from "react";
6
6
 
7
7
  export const RefreshIconButton = forwardRef<HTMLButtonElement, Omit<ButtonProps, "icon">>(function RefreshIconButton(
8
- { onClick, children, ...props },
8
+ { onClick, children, loading, ...props },
9
9
  ref,
10
10
  ) {
11
11
  let dataObject = useDataObject();
12
- let loading = useLoading(dataObject);
12
+ let refreshing = useLoading(dataObject);
13
+ let isLoading = refreshing || loading;
13
14
 
14
15
  return (
15
16
  <Button
16
17
  icon
17
- loading={loading}
18
+ loading={isLoading}
18
19
  aria-label={getLocalizedString("Refresh data")}
19
20
  variant="tertiary"
20
21
  onClick={(evt) => {
@@ -26,7 +27,7 @@ export const RefreshIconButton = forwardRef<HTMLButtonElement, Omit<ButtonProps,
26
27
  {...props}
27
28
  ref={ref}
28
29
  >
29
- {children ?? <RefreshIcon />}
30
+ {children ?? <ArrowsCirclepathIcon />}
30
31
  </Button>
31
32
  );
32
33
  });
@@ -1,5 +1,5 @@
1
1
  import { Button, type ButtonProps } from "@digdir/designsystemet-react";
2
- import RefreshIcon from "@mui/icons-material/Refresh";
2
+ import { ArrowsCirclepathIcon } from "@navikt/aksel-icons";
3
3
  import { getLocalizedString } from "@olenbetong/appframe-core";
4
4
  import { useRefreshRowButton } from "@olenbetong/appframe-react";
5
5
  import { forwardRef } from "react";
@@ -7,13 +7,14 @@ import { forwardRef } from "react";
7
7
  export type RefreshRowIconButtonProps = Omit<ButtonProps, "icon"> & { index?: number };
8
8
 
9
9
  export const RefreshRowIconButton = forwardRef<HTMLButtonElement, RefreshRowIconButtonProps>(
10
- function RefreshRowIconButton({ onClick, index, children, ...props }, ref) {
11
- let { loading, refreshRow } = useRefreshRowButton();
10
+ function RefreshRowIconButton({ onClick, index, children, loading, ...props }, ref) {
11
+ let { loading: refreshing, refreshRow } = useRefreshRowButton();
12
+ let isLoading = refreshing || loading;
12
13
 
13
14
  return (
14
15
  <Button
15
16
  icon
16
- loading={loading}
17
+ loading={isLoading}
17
18
  aria-label={getLocalizedString("Refresh current row")}
18
19
  variant="tertiary"
19
20
  onClick={(evt) => {
@@ -25,7 +26,7 @@ export const RefreshRowIconButton = forwardRef<HTMLButtonElement, RefreshRowIcon
25
26
  {...props}
26
27
  ref={ref}
27
28
  >
28
- {children ?? <RefreshIcon />}
29
+ {children ?? <ArrowsCirclepathIcon />}
29
30
  </Button>
30
31
  );
31
32
  },
@@ -1,4 +1,5 @@
1
- import { Button, type ButtonProps } from "@digdir/designsystemet-react";
1
+ import { Badge, BadgePosition, Button, type ButtonProps } from "@digdir/designsystemet-react";
2
+ import { FloppydiskIcon } from "@navikt/aksel-icons";
2
3
  import { getLocalizedString } from "@olenbetong/appframe-core";
3
4
  import { useSaveButton } from "@olenbetong/appframe-react";
4
5
  import { type ForwardRefExoticComponent, forwardRef, type RefAttributes } from "react";
@@ -6,12 +7,13 @@ import { type ForwardRefExoticComponent, forwardRef, type RefAttributes } from "
6
7
  export const SaveButton: ForwardRefExoticComponent<ButtonProps & RefAttributes<HTMLButtonElement>> = forwardRef<
7
8
  HTMLButtonElement,
8
9
  ButtonProps
9
- >(function SaveButton({ onClick, children, ...props }, ref) {
10
- let { saving, save } = useSaveButton();
10
+ >(function SaveButton({ onClick, children, loading, ...props }, ref) {
11
+ let { dirty, saving, save } = useSaveButton();
12
+ let isLoading = saving || loading;
11
13
 
12
14
  return (
13
15
  <Button
14
- loading={saving}
16
+ loading={isLoading}
15
17
  onClick={(evt) => {
16
18
  onClick?.(evt);
17
19
  if (!evt.defaultPrevented) {
@@ -21,6 +23,14 @@ export const SaveButton: ForwardRefExoticComponent<ButtonProps & RefAttributes<H
21
23
  {...props}
22
24
  ref={ref}
23
25
  >
26
+ {/* Icon is hidden while loading so the spinner takes its place instead of adding width */}
27
+ {!isLoading && (
28
+ <BadgePosition placement="top-right" overlap="circle">
29
+ <FloppydiskIcon aria-hidden="true" />
30
+ {/* Indicates unsaved changes on the current record */}
31
+ {dirty && <Badge data-color="warning" aria-hidden="true" />}
32
+ </BadgePosition>
33
+ )}
24
34
  {children ?? getLocalizedString("Save")}
25
35
  </Button>
26
36
  );
@@ -1,19 +1,20 @@
1
- import { Button, type ButtonProps } from "@digdir/designsystemet-react";
2
- import SaveIcon from "@mui/icons-material/Save";
1
+ import { Badge, BadgePosition, Button, type ButtonProps } from "@digdir/designsystemet-react";
2
+ import { FloppydiskIcon } from "@navikt/aksel-icons";
3
3
  import { getLocalizedString } from "@olenbetong/appframe-core";
4
4
  import { useSaveButton } from "@olenbetong/appframe-react";
5
5
  import { forwardRef } from "react";
6
6
 
7
7
  export const SaveIconButton = forwardRef<HTMLButtonElement, Omit<ButtonProps, "icon">>(function SaveIconButton(
8
- { onClick, children, ...props },
8
+ { onClick, children, loading, ...props },
9
9
  ref,
10
10
  ) {
11
- let { saving, save } = useSaveButton();
11
+ let { dirty, saving, save } = useSaveButton();
12
+ let isLoading = saving || loading;
12
13
 
13
14
  return (
14
15
  <Button
15
16
  icon
16
- loading={saving}
17
+ loading={isLoading}
17
18
  aria-label={getLocalizedString("Save")}
18
19
  variant="tertiary"
19
20
  onClick={(evt) => {
@@ -25,7 +26,13 @@ export const SaveIconButton = forwardRef<HTMLButtonElement, Omit<ButtonProps, "i
25
26
  {...props}
26
27
  ref={ref}
27
28
  >
28
- {children ?? <SaveIcon />}
29
+ {children ?? (
30
+ <BadgePosition placement="top-right" overlap="circle">
31
+ <FloppydiskIcon />
32
+ {/* Indicates unsaved changes on the current record */}
33
+ {dirty && <Badge data-color="warning" aria-hidden="true" />}
34
+ </BadgePosition>
35
+ )}
29
36
  </Button>
30
37
  );
31
38
  });
package/src/index.ts CHANGED
@@ -1,3 +1,8 @@
1
1
  export * from "./AfLookup/index.js";
2
2
  export * from "./binding/index.js";
3
+ export * from "./input/index.js";
3
4
  export * from "./layout/index.js";
5
+ export * from "./list/index.js";
6
+ export * from "./page/index.js";
7
+ export * from "./paper/index.js";
8
+ export * from "./progress/index.js";
@@ -0,0 +1,75 @@
1
+ /*
2
+ * The wrapper is the visual input: it owns the border, background, hover and
3
+ * focus styling, while the inner Designsystemet input is rendered bare so the
4
+ * input and the adornments read as a single control.
5
+ */
6
+ .ObInputAdornments-root {
7
+ --obInputAdornments-padding: 0.25rem;
8
+ --obInputAdornments-gap: 0.25rem;
9
+ --obInputAdornments-fontSize: var(--ds-font-size-3, 1rem);
10
+
11
+ display: flex;
12
+ align-items: center;
13
+ box-sizing: border-box;
14
+ gap: var(--obInputAdornments-gap);
15
+ padding: var(--obInputAdornments-padding);
16
+ inline-size: 100%;
17
+ color: var(--ds-color-neutral-text-default, #1e2b3c);
18
+ background-color: var(--ds-color-neutral-surface-default, #fff);
19
+ border: var(--ds-border-width-default, 1px) solid var(--ds-color-neutral-border-default, #b3b3b3);
20
+ border-radius: var(--ds-border-radius-md, 0.375rem);
21
+ }
22
+
23
+ .ObInputAdornments-root[data-size="xs"] {
24
+ --obInputAdornments-padding: 0.125rem;
25
+ --obInputAdornments-fontSize: var(--ds-font-size-1, 0.75rem);
26
+ }
27
+
28
+ .ObInputAdornments-root[data-size="sm"] {
29
+ --obInputAdornments-padding: 0.25rem;
30
+ --obInputAdornments-fontSize: var(--ds-font-size-2, 0.875rem);
31
+ }
32
+
33
+ .ObInputAdornments-root[data-size="md"] {
34
+ --obInputAdornments-padding: 0.25rem;
35
+ --obInputAdornments-fontSize: var(--ds-font-size-3, 1rem);
36
+ }
37
+
38
+ .ObInputAdornments-root[data-size="lg"] {
39
+ --obInputAdornments-padding: 0.375rem;
40
+ --obInputAdornments-fontSize: var(--ds-font-size-4, 1.125rem);
41
+ }
42
+
43
+ .ObInputAdornments-root:hover:not(:focus-within) {
44
+ border-color: var(--ds-color-neutral-border-strong, #767676);
45
+ }
46
+
47
+ .ObInputAdornments-root:has(.ObInputAdornments-field:focus-visible) {
48
+ box-shadow: var(--dsc-focus-boxShadow);
49
+ outline: var(--dsc-focus-outline);
50
+ outline-offset: var(--ds-border-width-focus, 3px);
51
+ }
52
+
53
+ .ObInputAdornments-root:has(.ObInputAdornments-field:disabled) {
54
+ cursor: not-allowed;
55
+ opacity: var(--ds-disabled-opacity, 0.3);
56
+ }
57
+
58
+ /* The bare input: all the visual styling lives on the wrapper instead */
59
+ .ObInputAdornments-root .ObInputAdornments-field {
60
+ flex: 1;
61
+ min-inline-size: 0;
62
+ font-size: var(--obInputAdornments-fontSize);
63
+ background: transparent;
64
+ border: none;
65
+ }
66
+
67
+ .ObInputAdornments-root .ObInputAdornments-field:disabled {
68
+ opacity: 1;
69
+ }
70
+
71
+ .ObInputAdornments-root .ObInputAdornments-field:focus-visible {
72
+ box-shadow: none;
73
+ outline: none;
74
+ outline-offset: 0;
75
+ }
@@ -0,0 +1,97 @@
1
+ import "./InputAdornments.css";
2
+
3
+ import { Input, type InputProps } from "@digdir/designsystemet-react";
4
+ import clsx from "clsx";
5
+ import { Children, isValidElement, type ReactElement, type ReactNode } from "react";
6
+
7
+ export type InputSlotProps = {
8
+ /**
9
+ * Extra classes for the input rendered in this slot
10
+ */
11
+ className?: string;
12
+ };
13
+
14
+ /**
15
+ * Marks where the input should be rendered among the children of
16
+ * `InputAdornments`. Renders nothing on its own; `InputAdornments` swaps it
17
+ * for the actual input. When it is left out, the input is rendered before the
18
+ * adornments.
19
+ *
20
+ * @example
21
+ * ```jsx
22
+ * <InputAdornments value={value} onChange={handleChange}>
23
+ * <SearchIcon aria-hidden />
24
+ * <InputSlot />
25
+ * <Button icon variant="tertiary"><XMarkIcon aria-hidden /></Button>
26
+ * </InputAdornments>
27
+ * ```
28
+ */
29
+ export function InputSlot(_props: InputSlotProps) {
30
+ return null;
31
+ }
32
+
33
+ export type InputAdornmentsProps = Omit<InputProps, "children"> & {
34
+ /**
35
+ * Adornments rendered next to the input, e.g. icons or buttons. Include an
36
+ * `InputSlot` to control where the input itself ends up.
37
+ */
38
+ children?: ReactNode;
39
+ /**
40
+ * Classes for the wrapper that carries the input styling
41
+ */
42
+ className?: string;
43
+ /**
44
+ * Classes for the bare input element itself
45
+ */
46
+ inputClassName?: string;
47
+ };
48
+
49
+ /**
50
+ * Input with adornments, equivalent to a MUI `TextField` with
51
+ * `InputProps.startAdornment` / `endAdornment`. Designsystemet has no
52
+ * equivalent, so the border, background, hover and focus styling is moved from
53
+ * the input onto a wrapper element, making the input and its adornments read
54
+ * as a single control.
55
+ *
56
+ * All other props are forwarded to the Designsystemet `Input`, so `ref`,
57
+ * `value`, `onChange`, `data-size` and `data-color` work as usual.
58
+ *
59
+ * @example
60
+ * ```jsx
61
+ * // Input first (default), adornments after
62
+ * <InputAdornments aria-label="Search" value={value} onChange={handleChange}>
63
+ * <Button icon variant="tertiary"><MagnifyingGlassIcon aria-hidden /></Button>
64
+ * </InputAdornments>
65
+ *
66
+ * // Explicit placement with InputSlot
67
+ * <InputAdornments aria-label="Amount" data-size="lg">
68
+ * <span aria-hidden>kr</span>
69
+ * <InputSlot />
70
+ * <span aria-hidden>,-</span>
71
+ * </InputAdornments>
72
+ * ```
73
+ */
74
+ export function InputAdornments({ children, className, inputClassName, ...props }: InputAdornmentsProps) {
75
+ let size = props["data-size"];
76
+ let color = props["data-color"];
77
+ let childArray = Children.toArray(children);
78
+ let slot = childArray.find((child) => isValidElement(child) && child.type === InputSlot) as
79
+ | ReactElement<InputSlotProps>
80
+ | undefined;
81
+
82
+ let input = (
83
+ <Input
84
+ key="ObInputAdornments-input"
85
+ {...props}
86
+ className={clsx("ObInputAdornments-field", inputClassName, slot?.props.className)}
87
+ />
88
+ );
89
+
90
+ let content = slot ? childArray.map((child) => (child === slot ? input : child)) : [input, ...childArray];
91
+
92
+ return (
93
+ <div className={clsx("ObInputAdornments-root", className)} data-size={size} data-color={color}>
94
+ {content}
95
+ </div>
96
+ );
97
+ }
@@ -0,0 +1 @@
1
+ export * from "./InputAdornments.js";
@@ -21,6 +21,9 @@ function LoadingFallback() {
21
21
  );
22
22
  }
23
23
 
24
+ /**
25
+ * @deprecated Use the `Page` primitives instead (`Page`, `Page.Header`, `Page.Block`).
26
+ */
24
27
  export function AppLayoutDesktop({ AppMenuProps, children, menu, partner }: AppLayoutProps) {
25
28
  let [open, setOpen] = useState(true);
26
29
 
@@ -15,6 +15,9 @@ function Toggle({ onToggle }: { onToggle: () => void }) {
15
15
  );
16
16
  }
17
17
 
18
+ /**
19
+ * @deprecated Use the `Page` primitives instead (`Page`, `Page.Header`, `Page.Block`).
20
+ */
18
21
  export function AppLayoutMobile({ AppMenuProps, children, menu, partner }: AppLayoutProps) {
19
22
  let [drawerOpen, setDrawerOpen] = useState(false);
20
23
  let location = useLocation();
@@ -7,6 +7,7 @@
7
7
  letter-spacing: -0.02rem;
8
8
  font-size: var(--ds-font-size-xl, 1.5rem);
9
9
  margin: 0;
10
+ color: var(--ds-color-neutral-text-default, #1e2b3c);
10
11
  }
11
12
 
12
13
  .AppMenu-header .AppMenu-subTitle {