@olenbetong/appframe-ds 1.7.2 → 1.9.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 (147) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/package.json +2 -2
  3. package/src/AfLookup/Lookup.css +9 -1
  4. package/src/AfLookup/Lookup.tsx +8 -10
  5. package/src/AfLookup/LookupCombobox.tsx +1 -9
  6. package/src/AfLookup/LookupEdit.tsx +3 -3
  7. package/src/AfLookup/LookupGrid.css +3 -2
  8. package/src/AfLookup/LookupGrid.tsx +8 -4
  9. package/src/autocomplete/Autocomplete.css +6 -17
  10. package/src/binding/BoundNumberInput.tsx +42 -42
  11. package/src/binding/BoundNumericTextField.tsx +102 -102
  12. package/src/binding/BoundTextField.tsx +49 -49
  13. package/src/binding/CancelButton.tsx +30 -30
  14. package/src/binding/CancelIconButton.tsx +30 -30
  15. package/src/binding/DataEditToolbar.css +23 -23
  16. package/src/binding/DataEditToolbar.tsx +84 -84
  17. package/src/binding/DeleteButton.tsx +35 -35
  18. package/src/binding/DeleteIconButton.tsx +35 -35
  19. package/src/binding/RefreshButton.tsx +31 -31
  20. package/src/binding/RefreshIconButton.tsx +33 -33
  21. package/src/binding/RefreshRowIconButton.tsx +33 -33
  22. package/src/binding/SaveButton.tsx +37 -37
  23. package/src/binding/SaveIconButton.tsx +38 -38
  24. package/src/binding/index.ts +20 -20
  25. package/src/container/Container.tsx +50 -50
  26. package/src/container/index.ts +1 -1
  27. package/src/cutoff/CutOffPicker.css +12 -12
  28. package/src/cutoff/CutOffPicker.tsx +165 -165
  29. package/src/cutoff/context.tsx +194 -194
  30. package/src/cutoff/dsCutOffDates.ts +21 -21
  31. package/src/cutoff/index.ts +4 -4
  32. package/src/cutoff/utils.test.ts +59 -59
  33. package/src/cutoff/utils.ts +276 -276
  34. package/src/filter/ChipListInput.tsx +79 -79
  35. package/src/filter/FieldFilterPanel.css +150 -150
  36. package/src/filter/FieldFilterPanel.tsx +303 -309
  37. package/src/filter/FilterBuilder.css +152 -152
  38. package/src/filter/FilterBuilder.test.tsx +153 -153
  39. package/src/filter/FilterBuilder.tsx +435 -435
  40. package/src/filter/FilterEditor.css +231 -231
  41. package/src/filter/FilterEditor.test.tsx +285 -285
  42. package/src/filter/FilterEditor.tsx +75 -75
  43. package/src/filter/FilterGroupEditor.tsx +160 -160
  44. package/src/filter/FilterNameDialog.css +22 -22
  45. package/src/filter/FilterNameDialog.tsx +81 -81
  46. package/src/filter/FilterRow.tsx +117 -117
  47. package/src/filter/FilterShareDialog.css +28 -28
  48. package/src/filter/FilterShareDialog.tsx +195 -201
  49. package/src/filter/FilterStringField.tsx +72 -78
  50. package/src/filter/FilterValueEditor.tsx +239 -239
  51. package/src/filter/SavedFilterTree.css +107 -107
  52. package/src/filter/SavedFilterTree.test.tsx +94 -94
  53. package/src/filter/SavedFilterTree.tsx +218 -218
  54. package/src/filter/fieldFilter.test.ts +158 -158
  55. package/src/filter/fieldFilter.ts +139 -139
  56. package/src/filter/index.ts +27 -27
  57. package/src/filter/types.ts +38 -38
  58. package/src/filter/useDistinctValues.test.ts +102 -102
  59. package/src/filter/useDistinctValues.ts +230 -230
  60. package/src/global.d.ts +11 -11
  61. package/src/grid/AfGridColumnsPanel.tsx +4 -7
  62. package/src/grid/AfGridError.tsx +2 -2
  63. package/src/grid/AfHeaderFilterCell.tsx +0 -3
  64. package/src/grid/AfHeaderFilterPanel.tsx +1 -3
  65. package/src/grid/GridEditLookup.tsx +0 -2
  66. package/src/grid/Toolbar.tsx +72 -36
  67. package/src/grid/index.css +44 -0
  68. package/src/grid/index.tsx +16 -3
  69. package/src/grid/slots/index.tsx +15 -40
  70. package/src/grid/useAfGridApi.ts +0 -1
  71. package/src/grid/useAfPersistedState.ts +4 -8
  72. package/src/grid/useAfSizeMode.ts +19 -0
  73. package/src/i18n.ts +13 -13
  74. package/src/index.ts +14 -14
  75. package/src/input/NumberInput.tsx +0 -2
  76. package/src/layout/AppMenu.tsx +74 -74
  77. package/src/layout/AppMenuDrawer.css +71 -71
  78. package/src/layout/AppMenuDrawer.tsx +122 -122
  79. package/src/layout/DialogMotion.css +79 -79
  80. package/src/layout/ErrorBoundary.tsx +162 -162
  81. package/src/layout/PageContainer.css +48 -48
  82. package/src/layout/PageContainer.tsx +41 -41
  83. package/src/layout/ResponsiveDialog.css +86 -86
  84. package/src/layout/ResponsiveDialog.tsx +55 -55
  85. package/src/layout/index.tsx +70 -70
  86. package/src/link/Link.tsx +23 -23
  87. package/src/link/index.ts +1 -1
  88. package/src/list/ItemList.css +2 -1
  89. package/src/mdi/Mdi.test.tsx +91 -91
  90. package/src/mdi/Mdi.tsx +141 -141
  91. package/src/mdi/MdiConfirmCloseDialog.tsx +41 -47
  92. package/src/mdi/MdiContext.tsx +59 -59
  93. package/src/mdi/MdiDocumentContext.tsx +60 -60
  94. package/src/mdi/MdiPanel.tsx +61 -61
  95. package/src/mdi/MdiRestore.test.tsx +86 -86
  96. package/src/mdi/MdiTab.tsx +30 -30
  97. package/src/mdi/controller.test.ts +382 -382
  98. package/src/mdi/controller.ts +656 -656
  99. package/src/mdi/dockviewHost.ts +132 -132
  100. package/src/mdi/fakeHost.ts +200 -200
  101. package/src/mdi/host.ts +44 -44
  102. package/src/mdi/ids.test.ts +20 -20
  103. package/src/mdi/ids.ts +18 -18
  104. package/src/mdi/index.ts +31 -31
  105. package/src/mdi/layoutRestore.test.ts +156 -156
  106. package/src/mdi/layoutRestore.ts +311 -311
  107. package/src/mdi/layoutSpec.test.ts +143 -143
  108. package/src/mdi/layoutSpec.ts +214 -214
  109. package/src/mdi/layoutStore.test.ts +93 -93
  110. package/src/mdi/layoutStore.ts +99 -99
  111. package/src/mdi/memorySettingsStore.ts +21 -21
  112. package/src/mdi/registry.ts +34 -34
  113. package/src/mdi/testTypes.ts +37 -37
  114. package/src/mdi/theme.ts +22 -22
  115. package/src/mdi/types.ts +214 -214
  116. package/src/page/Page.css +57 -57
  117. package/src/page/PageBlock.css +64 -64
  118. package/src/page/PageBlock.tsx +93 -93
  119. package/src/page/PageHeader.css +88 -88
  120. package/src/page/PageHeader.tsx +115 -115
  121. package/src/page/PageTitle.css +5 -5
  122. package/src/page/PageTitle.tsx +21 -21
  123. package/src/page/index.ts +4 -4
  124. package/src/paper/Paper.tsx +52 -52
  125. package/src/progress/LinearProgress.css +121 -121
  126. package/src/progress/LinearProgress.tsx +106 -106
  127. package/src/progress/index.ts +1 -1
  128. package/src/report-downloader/ReportDownloader.css +67 -67
  129. package/src/report-downloader/components/StatusItem.tsx +87 -87
  130. package/src/report-downloader/components/StatusList.tsx +28 -28
  131. package/src/report-downloader/index.ts +41 -41
  132. package/src/report-downloader/status.tsx +92 -92
  133. package/src/sortable/DropIndicator.css +51 -51
  134. package/src/sortable/DropIndicator.tsx +27 -27
  135. package/src/sortable/SortableItemList.css +21 -21
  136. package/src/sortable/SortableItemList.tsx +264 -264
  137. package/src/sortable/index.ts +8 -8
  138. package/src/sortable/reorderSortOrder.ts +126 -126
  139. package/src/sortable/sortOrder.ts +101 -101
  140. package/src/sortable/types.ts +9 -9
  141. package/src/sortable/useSortableContainer.ts +53 -53
  142. package/src/sortable/useSortableItem.ts +95 -95
  143. package/src/sortable/useSortableList.ts +117 -117
  144. package/src/test/happy-dom-document-class.ts +24 -24
  145. package/src/test/setup.ts +27 -27
  146. package/src/theme/sizeMode.ts +62 -0
  147. package/src/theme/useDsColorScheme.ts +53 -53
@@ -1,79 +1,79 @@
1
- /*
2
- * One open and close motion for every centred modal: Designsystemet's Dialog (the filter builder,
3
- * the site search, the report launcher and any app's own dialogs) and anything else that opts in
4
- * with the `ob-dialog-motion` class (the chrome's app menu, which is a plain <dialog>).
5
- *
6
- * Designsystemet animates the opening with keyframes only, 300 ms, and nothing on close. This
7
- * replaces that with transitions, so the close plays the same motion in reverse:
8
- * - @starting-style gives the first frame after showModal() something to transition from;
9
- * - `display` and `overlay` transition with `allow-discrete`, which keeps the dialog in the top
10
- * layer until the fade out has finished after close().
11
- * Browsers without `overlay` (Firefox, Safari at the time of writing) still animate the opening
12
- * and close without the fade; nothing breaks.
13
- *
14
- * A dialog only fades out when it is closed with close() and stays mounted. One that React
15
- * removes on close disappears at once.
16
- *
17
- * Reduced motion keeps a short fade and drops the movement.
18
- */
19
-
20
- :root {
21
- --ob-dialog-enter-duration: 180ms;
22
- --ob-dialog-exit-duration: 140ms;
23
- --ob-dialog-offset: 1rem;
24
- }
25
-
26
- @media (prefers-reduced-motion: reduce) {
27
- :root {
28
- --ob-dialog-offset: 0;
29
- }
30
- }
31
-
32
- .ds-dialog:is(:not([data-placement]), [data-placement="center"]),
33
- .ob-dialog-motion {
34
- opacity: 0;
35
- translate: 0 var(--ob-dialog-offset);
36
- transition-property: opacity, translate, overlay, display;
37
- transition-duration: var(--ob-dialog-exit-duration);
38
- transition-timing-function: ease-in;
39
- transition-behavior: allow-discrete;
40
-
41
- &[open] {
42
- opacity: 1;
43
- translate: 0 0;
44
- /* Designsystemet's keyframes would play on top of the transition. */
45
- animation: none;
46
- transition-duration: var(--ob-dialog-enter-duration);
47
- transition-timing-function: ease-out;
48
-
49
- /*
50
- * Nested so it has exactly this rule's selector. A separate @starting-style rule for
51
- * `.ob-dialog-motion[open]` is less specific than this one, whose `:is()` takes the
52
- * specificity of `.ds-dialog:is(...)`, and loses to it: the dialog then has no
53
- * starting frame and appears without the transition.
54
- */
55
- @starting-style {
56
- opacity: 0;
57
- translate: 0 var(--ob-dialog-offset);
58
- }
59
- }
60
-
61
- &::backdrop {
62
- opacity: 0;
63
- animation: none;
64
- transition-property: opacity, overlay, display;
65
- transition-duration: var(--ob-dialog-exit-duration);
66
- transition-timing-function: ease-in;
67
- transition-behavior: allow-discrete;
68
- }
69
-
70
- &[open]::backdrop {
71
- opacity: 1;
72
- transition-duration: var(--ob-dialog-enter-duration);
73
- transition-timing-function: ease-out;
74
-
75
- @starting-style {
76
- opacity: 0;
77
- }
78
- }
79
- }
1
+ /*
2
+ * One open and close motion for every centred modal: Designsystemet's Dialog (the filter builder,
3
+ * the site search, the report launcher and any app's own dialogs) and anything else that opts in
4
+ * with the `ob-dialog-motion` class (the chrome's app menu, which is a plain <dialog>).
5
+ *
6
+ * Designsystemet animates the opening with keyframes only, 300 ms, and nothing on close. This
7
+ * replaces that with transitions, so the close plays the same motion in reverse:
8
+ * - @starting-style gives the first frame after showModal() something to transition from;
9
+ * - `display` and `overlay` transition with `allow-discrete`, which keeps the dialog in the top
10
+ * layer until the fade out has finished after close().
11
+ * Browsers without `overlay` (Firefox, Safari at the time of writing) still animate the opening
12
+ * and close without the fade; nothing breaks.
13
+ *
14
+ * A dialog only fades out when it is closed with close() and stays mounted. One that React
15
+ * removes on close disappears at once.
16
+ *
17
+ * Reduced motion keeps a short fade and drops the movement.
18
+ */
19
+
20
+ :root {
21
+ --ob-dialog-enter-duration: 180ms;
22
+ --ob-dialog-exit-duration: 140ms;
23
+ --ob-dialog-offset: 1rem;
24
+ }
25
+
26
+ @media (prefers-reduced-motion: reduce) {
27
+ :root {
28
+ --ob-dialog-offset: 0;
29
+ }
30
+ }
31
+
32
+ .ds-dialog:is(:not([data-placement]), [data-placement="center"]),
33
+ .ob-dialog-motion {
34
+ opacity: 0;
35
+ translate: 0 var(--ob-dialog-offset);
36
+ transition-property: opacity, translate, overlay, display;
37
+ transition-duration: var(--ob-dialog-exit-duration);
38
+ transition-timing-function: ease-in;
39
+ transition-behavior: allow-discrete;
40
+
41
+ &[open] {
42
+ opacity: 1;
43
+ translate: 0 0;
44
+ /* Designsystemet's keyframes would play on top of the transition. */
45
+ animation: none;
46
+ transition-duration: var(--ob-dialog-enter-duration);
47
+ transition-timing-function: ease-out;
48
+
49
+ /*
50
+ * Nested so it has exactly this rule's selector. A separate @starting-style rule for
51
+ * `.ob-dialog-motion[open]` is less specific than this one, whose `:is()` takes the
52
+ * specificity of `.ds-dialog:is(...)`, and loses to it: the dialog then has no
53
+ * starting frame and appears without the transition.
54
+ */
55
+ @starting-style {
56
+ opacity: 0;
57
+ translate: 0 var(--ob-dialog-offset);
58
+ }
59
+ }
60
+
61
+ &::backdrop {
62
+ opacity: 0;
63
+ animation: none;
64
+ transition-property: opacity, overlay, display;
65
+ transition-duration: var(--ob-dialog-exit-duration);
66
+ transition-timing-function: ease-in;
67
+ transition-behavior: allow-discrete;
68
+ }
69
+
70
+ &[open]::backdrop {
71
+ opacity: 1;
72
+ transition-duration: var(--ob-dialog-enter-duration);
73
+ transition-timing-function: ease-out;
74
+
75
+ @starting-style {
76
+ opacity: 0;
77
+ }
78
+ }
79
+ }
@@ -1,162 +1,162 @@
1
- import "./ErrorBoundary.css";
2
-
3
- import type { DataObject } from "@olenbetong/appframe-data";
4
- import { useError } from "@olenbetong/appframe-react";
5
-
6
- import { Component, useEffect } from "react";
7
- import { useAsyncError, useRouteError } from "react-router";
8
-
9
- import { PageContainer } from "./PageContainer.js";
10
- import { getLocalizedString } from "../i18n.js";
11
-
12
- function parseError(error: unknown) {
13
- let message = "Unspecified error";
14
- let stackTrace: string | null = null;
15
- let title: string = getLocalizedString("Unexpected application error");
16
-
17
- if (typeof error === "string") {
18
- message = error;
19
- } else if (error && typeof error === "object") {
20
- const err = error as any;
21
- if (err.status) {
22
- title = `${err.status} ${err.statusText}`;
23
- message = err.message ?? "";
24
- } else if (err.message) {
25
- message = err.message;
26
- if (err.type) {
27
- message = `${err.type}: ${message}`;
28
- }
29
- } else {
30
- message = String(error);
31
- }
32
-
33
- if (err.stack || err.stackTrace) {
34
- stackTrace = err.stack ?? err.stackTrace;
35
- }
36
- }
37
-
38
- return { message, stackTrace, title };
39
- }
40
-
41
- // ── AppErrorBoundary ──────────────────────────────────────────────────────────
42
-
43
- export type AppErrorBoundaryProps = {
44
- children?: React.ReactNode;
45
- message?: string | ((error: unknown) => string);
46
- imgSrc?: string;
47
- image?: React.ReactNode;
48
- };
49
-
50
- type AppErrorBoundaryState = {
51
- hasError: boolean;
52
- error: unknown;
53
- };
54
-
55
- export class AppErrorBoundary extends Component<AppErrorBoundaryProps, AppErrorBoundaryState> {
56
- constructor(props: AppErrorBoundaryProps) {
57
- super(props);
58
- this.state = { hasError: false, error: null };
59
- }
60
-
61
- static getDerivedStateFromError(error: unknown): AppErrorBoundaryState {
62
- return { hasError: true, error };
63
- }
64
-
65
- componentDidCatch(error: unknown, errorInfo: unknown) {
66
- console.error(error, errorInfo);
67
- }
68
-
69
- render(): React.ReactNode {
70
- if (this.state.hasError) {
71
- const { imgSrc, message, image } = this.props;
72
- const { message: errorMessage } = parseError(this.state.error);
73
- const customMessage = typeof message === "function" ? message(this.state.error) : message;
74
-
75
- return (
76
- <div className="AppErrorBoundary-root">
77
- <div className="AppErrorBoundary-inner">
78
- {image ?? (
79
- <img
80
- alt=""
81
- src={imgSrc ?? "/file/site/graphic/error-broken-robot.svg"}
82
- className="AppErrorBoundary-image"
83
- />
84
- )}
85
- <h1 className="AppErrorBoundary-title">{getLocalizedString("Something went terribly wrong!")}</h1>
86
- <div className="AppErrorBoundary-alert">{errorMessage}</div>
87
- <p className="AppErrorBoundary-message">
88
- {customMessage ??
89
- getLocalizedString(
90
- "Sometimes it is enough to simply refresh the page. If the error persists, contact the IT department.",
91
- )}
92
- </p>
93
- <button type="button" className="AppErrorBoundary-reload" onClick={() => window.location.reload()}>
94
- {getLocalizedString("Refresh page")}
95
- </button>
96
- </div>
97
- </div>
98
- );
99
- }
100
-
101
- return this.props.children;
102
- }
103
- }
104
-
105
- // ── ErrorAlertFallback ────────────────────────────────────────────────────────
106
-
107
- export type ErrorAlertFallbackProps = {
108
- error: unknown;
109
- className?: string;
110
- };
111
-
112
- export function ErrorAlertFallback({ error, className }: ErrorAlertFallbackProps) {
113
- const message = error instanceof Error ? error.message : String(error);
114
-
115
- return (
116
- <div className={`ErrorAlertFallback-root${className ? ` ${className}` : ""}`}>
117
- <div className="ErrorAlertFallback-content">
118
- <p className="ErrorAlertFallback-title">{getLocalizedString("An error occured. Try refreshing the page")}</p>
119
- <p>{message}</p>
120
- </div>
121
- <button type="button" className="ErrorAlertFallback-reload" onClick={() => window.location.reload()}>
122
- {getLocalizedString("Reload page")}
123
- </button>
124
- </div>
125
- );
126
- }
127
-
128
- // ── RouteErrorPage ────────────────────────────────────────────────────────────
129
-
130
- export function RouteErrorPage() {
131
- const error = useRouteError();
132
- const { message, stackTrace, title } = parseError(error);
133
-
134
- useEffect(() => {
135
- (globalThis as any).af?.common?.logError?.(error);
136
- }, [error]);
137
-
138
- return (
139
- <PageContainer className="flow">
140
- <h1>{title}</h1>
141
- {message && <p>{message}</p>}
142
- {stackTrace && <pre className="RouteErrorPage-stack">{stackTrace}</pre>}
143
- </PageContainer>
144
- );
145
- }
146
-
147
- // ── AsyncErrorAlert ───────────────────────────────────────────────────────────
148
-
149
- export function AsyncErrorAlert() {
150
- const error = useAsyncError();
151
- const message = typeof error === "string" ? error : (error as any)?.message;
152
-
153
- return <div className="ErrorAlertFallback-root">{message}</div>;
154
- }
155
-
156
- // ── DataObjectErrorAlert ──────────────────────────────────────────────────────
157
-
158
- export function DataObjectErrorAlert({ dataObject }: { dataObject: DataObject<any> }) {
159
- const error = useError(dataObject);
160
-
161
- return error ? <div className="ErrorAlertFallback-root">{error}</div> : null;
162
- }
1
+ import "./ErrorBoundary.css";
2
+
3
+ import type { DataObject } from "@olenbetong/appframe-data";
4
+ import { useError } from "@olenbetong/appframe-react";
5
+
6
+ import { Component, useEffect } from "react";
7
+ import { useAsyncError, useRouteError } from "react-router";
8
+
9
+ import { PageContainer } from "./PageContainer.js";
10
+ import { getLocalizedString } from "../i18n.js";
11
+
12
+ function parseError(error: unknown) {
13
+ let message = "Unspecified error";
14
+ let stackTrace: string | null = null;
15
+ let title: string = getLocalizedString("Unexpected application error");
16
+
17
+ if (typeof error === "string") {
18
+ message = error;
19
+ } else if (error && typeof error === "object") {
20
+ const err = error as any;
21
+ if (err.status) {
22
+ title = `${err.status} ${err.statusText}`;
23
+ message = err.message ?? "";
24
+ } else if (err.message) {
25
+ message = err.message;
26
+ if (err.type) {
27
+ message = `${err.type}: ${message}`;
28
+ }
29
+ } else {
30
+ message = String(error);
31
+ }
32
+
33
+ if (err.stack || err.stackTrace) {
34
+ stackTrace = err.stack ?? err.stackTrace;
35
+ }
36
+ }
37
+
38
+ return { message, stackTrace, title };
39
+ }
40
+
41
+ // ── AppErrorBoundary ──────────────────────────────────────────────────────────
42
+
43
+ export type AppErrorBoundaryProps = {
44
+ children?: React.ReactNode;
45
+ message?: string | ((error: unknown) => string);
46
+ imgSrc?: string;
47
+ image?: React.ReactNode;
48
+ };
49
+
50
+ type AppErrorBoundaryState = {
51
+ hasError: boolean;
52
+ error: unknown;
53
+ };
54
+
55
+ export class AppErrorBoundary extends Component<AppErrorBoundaryProps, AppErrorBoundaryState> {
56
+ constructor(props: AppErrorBoundaryProps) {
57
+ super(props);
58
+ this.state = { hasError: false, error: null };
59
+ }
60
+
61
+ static getDerivedStateFromError(error: unknown): AppErrorBoundaryState {
62
+ return { hasError: true, error };
63
+ }
64
+
65
+ componentDidCatch(error: unknown, errorInfo: unknown) {
66
+ console.error(error, errorInfo);
67
+ }
68
+
69
+ render(): React.ReactNode {
70
+ if (this.state.hasError) {
71
+ const { imgSrc, message, image } = this.props;
72
+ const { message: errorMessage } = parseError(this.state.error);
73
+ const customMessage = typeof message === "function" ? message(this.state.error) : message;
74
+
75
+ return (
76
+ <div className="AppErrorBoundary-root">
77
+ <div className="AppErrorBoundary-inner">
78
+ {image ?? (
79
+ <img
80
+ alt=""
81
+ src={imgSrc ?? "/file/site/graphic/error-broken-robot.svg"}
82
+ className="AppErrorBoundary-image"
83
+ />
84
+ )}
85
+ <h1 className="AppErrorBoundary-title">{getLocalizedString("Something went terribly wrong!")}</h1>
86
+ <div className="AppErrorBoundary-alert">{errorMessage}</div>
87
+ <p className="AppErrorBoundary-message">
88
+ {customMessage ??
89
+ getLocalizedString(
90
+ "Sometimes it is enough to simply refresh the page. If the error persists, contact the IT department.",
91
+ )}
92
+ </p>
93
+ <button type="button" className="AppErrorBoundary-reload" onClick={() => window.location.reload()}>
94
+ {getLocalizedString("Refresh page")}
95
+ </button>
96
+ </div>
97
+ </div>
98
+ );
99
+ }
100
+
101
+ return this.props.children;
102
+ }
103
+ }
104
+
105
+ // ── ErrorAlertFallback ────────────────────────────────────────────────────────
106
+
107
+ export type ErrorAlertFallbackProps = {
108
+ error: unknown;
109
+ className?: string;
110
+ };
111
+
112
+ export function ErrorAlertFallback({ error, className }: ErrorAlertFallbackProps) {
113
+ const message = error instanceof Error ? error.message : String(error);
114
+
115
+ return (
116
+ <div className={`ErrorAlertFallback-root${className ? ` ${className}` : ""}`}>
117
+ <div className="ErrorAlertFallback-content">
118
+ <p className="ErrorAlertFallback-title">{getLocalizedString("An error occured. Try refreshing the page")}</p>
119
+ <p>{message}</p>
120
+ </div>
121
+ <button type="button" className="ErrorAlertFallback-reload" onClick={() => window.location.reload()}>
122
+ {getLocalizedString("Reload page")}
123
+ </button>
124
+ </div>
125
+ );
126
+ }
127
+
128
+ // ── RouteErrorPage ────────────────────────────────────────────────────────────
129
+
130
+ export function RouteErrorPage() {
131
+ const error = useRouteError();
132
+ const { message, stackTrace, title } = parseError(error);
133
+
134
+ useEffect(() => {
135
+ (globalThis as any).af?.common?.logError?.(error);
136
+ }, [error]);
137
+
138
+ return (
139
+ <PageContainer className="flow">
140
+ <h1>{title}</h1>
141
+ {message && <p>{message}</p>}
142
+ {stackTrace && <pre className="RouteErrorPage-stack">{stackTrace}</pre>}
143
+ </PageContainer>
144
+ );
145
+ }
146
+
147
+ // ── AsyncErrorAlert ───────────────────────────────────────────────────────────
148
+
149
+ export function AsyncErrorAlert() {
150
+ const error = useAsyncError();
151
+ const message = typeof error === "string" ? error : (error as any)?.message;
152
+
153
+ return <div className="ErrorAlertFallback-root">{message}</div>;
154
+ }
155
+
156
+ // ── DataObjectErrorAlert ──────────────────────────────────────────────────────
157
+
158
+ export function DataObjectErrorAlert({ dataObject }: { dataObject: DataObject<any> }) {
159
+ const error = useError(dataObject);
160
+
161
+ return error ? <div className="ErrorAlertFallback-root">{error}</div> : null;
162
+ }
@@ -1,48 +1,48 @@
1
- @media screen {
2
- .ObPageContainer-root {
3
- max-height: 100%;
4
- overflow: auto;
5
- height: auto;
6
- }
7
-
8
- .ObPageContainer-root.ObPageContainer-fullHeight {
9
- height: 100%;
10
- }
11
-
12
- .ObPageContainer-inner {
13
- --ob-page-container-gutter: 1rem;
14
-
15
- margin-left: 0;
16
- height: auto;
17
- padding: 1rem var(--ob-page-container-gutter);
18
- box-sizing: border-box;
19
- max-width: 100%;
20
- }
21
-
22
- .ObPageContainer-fullHeight .ObPageContainer-inner {
23
- height: 100%;
24
- }
25
-
26
- /* The content width on the Page.Block scale (rem for a 14px root); the gutters come on top. */
27
- .ObPageContainer-inner.ObPageContainer-text {
28
- max-width: calc(41rem + 2 * var(--ob-page-container-gutter));
29
- }
30
-
31
- .ObPageContainer-inner.ObPageContainer-md {
32
- max-width: calc(55rem + 2 * var(--ob-page-container-gutter));
33
- }
34
-
35
- .ObPageContainer-inner.ObPageContainer-lg {
36
- max-width: calc(73rem + 2 * var(--ob-page-container-gutter));
37
- }
38
-
39
- .ObPageContainer-inner.ObPageContainer-xl {
40
- max-width: calc(110rem + 2 * var(--ob-page-container-gutter));
41
- }
42
- }
43
-
44
- @media screen and (min-width: 600px) {
45
- .ObPageContainer-inner {
46
- --ob-page-container-gutter: 1.5rem;
47
- }
48
- }
1
+ @media screen {
2
+ .ObPageContainer-root {
3
+ max-height: 100%;
4
+ overflow: auto;
5
+ height: auto;
6
+ }
7
+
8
+ .ObPageContainer-root.ObPageContainer-fullHeight {
9
+ height: 100%;
10
+ }
11
+
12
+ .ObPageContainer-inner {
13
+ --ob-page-container-gutter: 1rem;
14
+
15
+ margin-left: 0;
16
+ height: auto;
17
+ padding: 1rem var(--ob-page-container-gutter);
18
+ box-sizing: border-box;
19
+ max-width: 100%;
20
+ }
21
+
22
+ .ObPageContainer-fullHeight .ObPageContainer-inner {
23
+ height: 100%;
24
+ }
25
+
26
+ /* The content width on the Page.Block scale (rem for a 14px root); the gutters come on top. */
27
+ .ObPageContainer-inner.ObPageContainer-text {
28
+ max-width: calc(41rem + 2 * var(--ob-page-container-gutter));
29
+ }
30
+
31
+ .ObPageContainer-inner.ObPageContainer-md {
32
+ max-width: calc(55rem + 2 * var(--ob-page-container-gutter));
33
+ }
34
+
35
+ .ObPageContainer-inner.ObPageContainer-lg {
36
+ max-width: calc(73rem + 2 * var(--ob-page-container-gutter));
37
+ }
38
+
39
+ .ObPageContainer-inner.ObPageContainer-xl {
40
+ max-width: calc(110rem + 2 * var(--ob-page-container-gutter));
41
+ }
42
+ }
43
+
44
+ @media screen and (min-width: 600px) {
45
+ .ObPageContainer-inner {
46
+ --ob-page-container-gutter: 1.5rem;
47
+ }
48
+ }
@@ -1,41 +1,41 @@
1
- import "./PageContainer.css";
2
-
3
- import clsx from "clsx";
4
- import type React from "react";
5
-
6
- export type PageContainerMaxWidth = "xs" | "sm" | "md" | "lg" | "xl" | false;
7
-
8
- export type PageContainerProps = React.HTMLAttributes<HTMLDivElement> & {
9
- fullHeight?: boolean;
10
- /**
11
- * Predefined max-width of the content, on the `Page.Block` scale (`xs`/`sm` 41rem,
12
- * `md` 55rem, `lg` 73rem, `xl` 110rem, plus the gutters). The content stays against the
13
- * leading edge, as the MUI-era `PageContainer` did; `Page.Block`/`Container` centre it.
14
- * @default max-width: 100%
15
- */
16
- maxWidth?: PageContainerMaxWidth;
17
- };
18
-
19
- const maxWidthClassName: Record<Exclude<PageContainerMaxWidth, false>, string> = {
20
- xs: "ObPageContainer-text",
21
- sm: "ObPageContainer-text",
22
- md: "ObPageContainer-md",
23
- lg: "ObPageContainer-lg",
24
- xl: "ObPageContainer-xl",
25
- };
26
-
27
- /**
28
- * @deprecated Use `Page.Block` instead. `maxWidth` mapping: `xs`/`sm` →
29
- * `width="text"`, `md` → `"md"`, `lg` → `"lg"`, `xl` → `"xl"`, `false` → omit
30
- * `width`. Replace `fullHeight` with `<Page layout="app">` and
31
- * `<Page.Block grow overflow="auto">`.
32
- */
33
- export function PageContainer({ children, className, fullHeight, maxWidth, ...props }: PageContainerProps) {
34
- return (
35
- <div className={clsx("ObPageContainer-root", fullHeight && "ObPageContainer-fullHeight")}>
36
- <div className={clsx("ObPageContainer-inner", maxWidth && maxWidthClassName[maxWidth], className)} {...props}>
37
- {children}
38
- </div>
39
- </div>
40
- );
41
- }
1
+ import "./PageContainer.css";
2
+
3
+ import clsx from "clsx";
4
+ import type React from "react";
5
+
6
+ export type PageContainerMaxWidth = "xs" | "sm" | "md" | "lg" | "xl" | false;
7
+
8
+ export type PageContainerProps = React.HTMLAttributes<HTMLDivElement> & {
9
+ fullHeight?: boolean;
10
+ /**
11
+ * Predefined max-width of the content, on the `Page.Block` scale (`xs`/`sm` 41rem,
12
+ * `md` 55rem, `lg` 73rem, `xl` 110rem, plus the gutters). The content stays against the
13
+ * leading edge, as the MUI-era `PageContainer` did; `Page.Block`/`Container` centre it.
14
+ * @default max-width: 100%
15
+ */
16
+ maxWidth?: PageContainerMaxWidth;
17
+ };
18
+
19
+ const maxWidthClassName: Record<Exclude<PageContainerMaxWidth, false>, string> = {
20
+ xs: "ObPageContainer-text",
21
+ sm: "ObPageContainer-text",
22
+ md: "ObPageContainer-md",
23
+ lg: "ObPageContainer-lg",
24
+ xl: "ObPageContainer-xl",
25
+ };
26
+
27
+ /**
28
+ * @deprecated Use `Page.Block` instead. `maxWidth` mapping: `xs`/`sm` →
29
+ * `width="text"`, `md` → `"md"`, `lg` → `"lg"`, `xl` → `"xl"`, `false` → omit
30
+ * `width`. Replace `fullHeight` with `<Page layout="app">` and
31
+ * `<Page.Block grow overflow="auto">`.
32
+ */
33
+ export function PageContainer({ children, className, fullHeight, maxWidth, ...props }: PageContainerProps) {
34
+ return (
35
+ <div className={clsx("ObPageContainer-root", fullHeight && "ObPageContainer-fullHeight")}>
36
+ <div className={clsx("ObPageContainer-inner", maxWidth && maxWidthClassName[maxWidth], className)} {...props}>
37
+ {children}
38
+ </div>
39
+ </div>
40
+ );
41
+ }