@olenbetong/appframe-ds 0.4.0 → 0.5.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.
- package/CHANGELOG.md +16 -0
- package/package.json +10 -10
- package/scripts/copyAssets.mjs +30 -0
- package/src/AfLookup/Combobox.tsx +16 -1
- package/src/AfLookup/Lookup.tsx +37 -6
- package/src/index.ts +3 -0
- package/src/layout/AppLayoutDesktop.tsx +3 -0
- package/src/layout/AppLayoutMobile.tsx +3 -0
- package/src/layout/AppMenu.css +1 -0
- package/src/layout/PageContainer.tsx +6 -0
- package/src/layout/index.tsx +5 -0
- package/src/list/ItemList.css +30 -0
- package/src/list/ItemList.tsx +52 -0
- package/src/list/ItemListItem.tsx +41 -0
- package/src/list/ItemListItemText.tsx +23 -0
- package/src/list/index.ts +3 -0
- package/src/page/Page.css +45 -0
- package/src/page/Page.tsx +70 -0
- package/src/page/PageBlock.css +59 -0
- package/src/page/PageBlock.tsx +78 -0
- package/src/page/PageHeader.css +148 -0
- package/src/page/PageHeader.tsx +184 -0
- package/src/page/index.ts +3 -0
- package/src/paper/Paper.css +35 -0
- package/src/paper/Paper.tsx +50 -0
- package/src/paper/index.ts +1 -0
- package/es/AfLookup/BoundLookup.d.ts +0 -10
- package/es/AfLookup/BoundLookup.js +0 -16
- package/es/AfLookup/Combobox.css +0 -78
- package/es/AfLookup/Combobox.d.ts +0 -31
- package/es/AfLookup/Combobox.js +0 -71
- package/es/AfLookup/Lookup.css +0 -125
- package/es/AfLookup/Lookup.d.ts +0 -35
- package/es/AfLookup/Lookup.js +0 -42
- package/es/AfLookup/LookupEdit.d.ts +0 -21
- package/es/AfLookup/LookupEdit.js +0 -49
- package/es/AfLookup/LookupGrid.css +0 -57
- package/es/AfLookup/LookupGrid.d.ts +0 -13
- package/es/AfLookup/LookupGrid.js +0 -27
- package/es/AfLookup/index.d.ts +0 -5
- package/es/AfLookup/index.js +0 -5
- package/es/binding/BoundCheckbox.d.ts +0 -9
- package/es/binding/BoundCheckbox.js +0 -13
- package/es/binding/BoundDatePicker.d.ts +0 -7
- package/es/binding/BoundDatePicker.js +0 -12
- package/es/binding/BoundDateTimePicker.d.ts +0 -7
- package/es/binding/BoundDateTimePicker.js +0 -14
- package/es/binding/BoundInput.d.ts +0 -6
- package/es/binding/BoundInput.js +0 -12
- package/es/binding/BoundNumericTextField.d.ts +0 -20
- package/es/binding/BoundNumericTextField.js +0 -58
- package/es/binding/BoundSelect.d.ts +0 -9
- package/es/binding/BoundSelect.js +0 -13
- package/es/binding/BoundSwitch.d.ts +0 -9
- package/es/binding/BoundSwitch.js +0 -13
- package/es/binding/BoundTextField.d.ts +0 -7
- package/es/binding/BoundTextField.js +0 -22
- package/es/binding/CancelButton.d.ts +0 -3
- package/es/binding/CancelButton.js +0 -14
- package/es/binding/CancelIconButton.d.ts +0 -2
- package/es/binding/CancelIconButton.js +0 -15
- package/es/binding/DataEditToolbar.d.ts +0 -13
- package/es/binding/DataEditToolbar.js +0 -28
- package/es/binding/DataObjectSelect.d.ts +0 -50
- package/es/binding/DataObjectSelect.js +0 -26
- package/es/binding/DeleteButton.d.ts +0 -8
- package/es/binding/DeleteButton.js +0 -14
- package/es/binding/DeleteIconButton.d.ts +0 -10
- package/es/binding/DeleteIconButton.js +0 -15
- package/es/binding/RefreshButton.d.ts +0 -3
- package/es/binding/RefreshButton.js +0 -14
- package/es/binding/RefreshIconButton.d.ts +0 -2
- package/es/binding/RefreshIconButton.js +0 -16
- package/es/binding/RefreshRowIconButton.d.ts +0 -7
- package/es/binding/RefreshRowIconButton.js +0 -15
- package/es/binding/SaveButton.d.ts +0 -3
- package/es/binding/SaveButton.js +0 -14
- package/es/binding/SaveIconButton.d.ts +0 -2
- package/es/binding/SaveIconButton.js +0 -15
- package/es/binding/index.d.ts +0 -19
- package/es/binding/index.js +0 -19
- package/es/index.d.ts +0 -3
- package/es/index.js +0 -3
- package/es/layout/AppLayoutDesktop.css +0 -65
- package/es/layout/AppLayoutDesktop.d.ts +0 -3
- package/es/layout/AppLayoutDesktop.js +0 -19
- package/es/layout/AppLayoutMobile.css +0 -75
- package/es/layout/AppLayoutMobile.d.ts +0 -3
- package/es/layout/AppLayoutMobile.js +0 -18
- package/es/layout/AppMenu.css +0 -67
- package/es/layout/AppMenu.d.ts +0 -8
- package/es/layout/AppMenu.js +0 -36
- package/es/layout/AppMenuItem.css +0 -97
- package/es/layout/AppMenuItem.d.ts +0 -50
- package/es/layout/AppMenuItem.js +0 -14
- package/es/layout/AppMenuSubheader.css +0 -18
- package/es/layout/AppMenuSubheader.d.ts +0 -4
- package/es/layout/AppMenuSubheader.js +0 -6
- package/es/layout/ErrorBoundary.css +0 -101
- package/es/layout/ErrorBoundary.d.ts +0 -30
- package/es/layout/ErrorBoundary.js +0 -81
- package/es/layout/PageContainer.css +0 -29
- package/es/layout/PageContainer.d.ts +0 -7
- package/es/layout/PageContainer.js +0 -6
- package/es/layout/PartnerToggle.d.ts +0 -5
- package/es/layout/PartnerToggle.js +0 -9
- package/es/layout/constants.d.ts +0 -2
- package/es/layout/constants.js +0 -2
- package/es/layout/index.css +0 -65
- package/es/layout/index.d.ts +0 -33
- package/es/layout/index.js +0 -31
- package/tsconfig.build.tsbuildinfo +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @olenbetong/appframe-ds
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
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}>`).
|
|
12
|
+
|
|
13
|
+
## 0.4.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- f7f6cea: AfLookup now supports typeahead from the input field: typing opens the lookup popup and forwards the typed character to the combobox search field.
|
|
18
|
+
|
|
3
19
|
## 0.4.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olenbetong/appframe-ds",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Components that bind Designsystemet components to Appframe data objects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./es/index.d.ts",
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
"peerDependencies": {
|
|
18
18
|
"@digdir/designsystemet-react": ">=1.18.0",
|
|
19
19
|
"@mui/icons-material": ">=7.0.0",
|
|
20
|
-
"react": ">=19.2.
|
|
21
|
-
"react-dom": ">=19.2.
|
|
22
|
-
"react-error-boundary": ">=
|
|
23
|
-
"react-router": ">=
|
|
20
|
+
"react": ">=19.2.8",
|
|
21
|
+
"react-dom": ">=19.2.8",
|
|
22
|
+
"react-error-boundary": ">=6.1.2",
|
|
23
|
+
"react-router": ">=8.3.0"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@olenbetong/appframe-core": "2.11.10",
|
|
27
27
|
"@olenbetong/appframe-data": "1.5.0",
|
|
28
|
-
"@olenbetong/appframe-react": "1.
|
|
28
|
+
"@olenbetong/appframe-react": "1.23.0",
|
|
29
29
|
"react-window": "^1.8.11",
|
|
30
30
|
"react-virtualized-auto-sizer": "^1.0.26",
|
|
31
31
|
"clsx": "^2.1.1"
|
|
@@ -36,16 +36,16 @@
|
|
|
36
36
|
"@types/react": "19.2.17",
|
|
37
37
|
"@types/react-dom": "19.2.3",
|
|
38
38
|
"@types/react-window": "^1.8.8",
|
|
39
|
-
"react": "19.2.
|
|
40
|
-
"react-dom": "19.2.
|
|
39
|
+
"react": "19.2.8",
|
|
40
|
+
"react-dom": "19.2.8",
|
|
41
41
|
"react-error-boundary": "6.1.2",
|
|
42
|
-
"react-router": "
|
|
42
|
+
"react-router": "8.3.0",
|
|
43
43
|
"typescript": "7.0.2"
|
|
44
44
|
},
|
|
45
45
|
"optionalDependencies": {},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "pnpm run build:tsc && pnpm run build:css",
|
|
48
48
|
"build:tsc": "node --no-warnings ../../scripts/clean.ts ./es tsconfig.build.tsbuildinfo && tsc -p tsconfig.build.json",
|
|
49
|
-
"build:css": "
|
|
49
|
+
"build:css": "node scripts/copyAssets.mjs"
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { cpSync, mkdirSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
const files = [
|
|
4
|
+
["src/AfLookup/Lookup.css", "es/AfLookup/Lookup.css"],
|
|
5
|
+
["src/AfLookup/LookupGrid.css", "es/AfLookup/LookupGrid.css"],
|
|
6
|
+
["src/AfLookup/Combobox.css", "es/AfLookup/Combobox.css"],
|
|
7
|
+
["src/layout/AppMenu.css", "es/layout/AppMenu.css"],
|
|
8
|
+
["src/layout/AppMenuItem.css", "es/layout/AppMenuItem.css"],
|
|
9
|
+
["src/layout/AppMenuSubheader.css", "es/layout/AppMenuSubheader.css"],
|
|
10
|
+
["src/layout/AppLayoutDesktop.css", "es/layout/AppLayoutDesktop.css"],
|
|
11
|
+
["src/layout/AppLayoutMobile.css", "es/layout/AppLayoutMobile.css"],
|
|
12
|
+
["src/layout/index.css", "es/layout/index.css"],
|
|
13
|
+
["src/layout/ErrorBoundary.css", "es/layout/ErrorBoundary.css"],
|
|
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/paper/Paper.css", "es/paper/Paper.css"],
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
mkdirSync("es/layout", { recursive: true });
|
|
23
|
+
mkdirSync("es/list", { recursive: true });
|
|
24
|
+
mkdirSync("es/page", { recursive: true });
|
|
25
|
+
mkdirSync("es/paper", { recursive: true });
|
|
26
|
+
|
|
27
|
+
for (let [src, dest] of files) {
|
|
28
|
+
cpSync(src, dest);
|
|
29
|
+
console.log(`Copied ${src} → ${dest}`);
|
|
30
|
+
}
|
|
@@ -15,6 +15,7 @@ export interface ComboboxBaseProps<T extends Record<string, unknown>> {
|
|
|
15
15
|
nullable?: boolean;
|
|
16
16
|
dataObject: DataObject<T>;
|
|
17
17
|
uniqueIdField?: string;
|
|
18
|
+
initialSearchValue?: string;
|
|
18
19
|
isItemSelected?: (item: T) => boolean;
|
|
19
20
|
onChange?: (item: T | null) => void;
|
|
20
21
|
value?: T;
|
|
@@ -41,7 +42,15 @@ export interface ComboboxGridProps<T extends Record<string, unknown>> extends Co
|
|
|
41
42
|
export type ComboboxProps<T extends Record<string, unknown>> = ComboboxListProps<T> | ComboboxGridProps<T>;
|
|
42
43
|
|
|
43
44
|
export function Combobox<T extends Record<string, unknown>>(props: ComboboxProps<T>) {
|
|
44
|
-
let {
|
|
45
|
+
let {
|
|
46
|
+
nullable,
|
|
47
|
+
dataObject,
|
|
48
|
+
isItemSelected,
|
|
49
|
+
uniqueIdField = "PrimKey",
|
|
50
|
+
initialSearchValue,
|
|
51
|
+
onChange,
|
|
52
|
+
onClose,
|
|
53
|
+
} = props;
|
|
45
54
|
let isGrid = props.variant === "grid";
|
|
46
55
|
let columns = isGrid ? (props as ComboboxGridProps<T>).columns : undefined;
|
|
47
56
|
let listRef = useRef<VirtualList>(null);
|
|
@@ -133,6 +142,12 @@ export function Combobox<T extends Record<string, unknown>>(props: ComboboxProps
|
|
|
133
142
|
|
|
134
143
|
useEffect(() => listRef.current?.scrollToItem(currentIndex, "smart"), [currentIndex]);
|
|
135
144
|
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (initialSearchValue !== undefined) {
|
|
147
|
+
setInputValue(initialSearchValue);
|
|
148
|
+
}
|
|
149
|
+
}, [initialSearchValue, setInputValue]);
|
|
150
|
+
|
|
136
151
|
return (
|
|
137
152
|
// oxlint-disable-next-line jsx-a11y/click-events-have-key-events -- element is not interactive, click is used to prevent dialog close
|
|
138
153
|
// oxlint-disable-next-line jsx-a11y/no-static-element-interactions -- element is not interactive, click is used to prevent dialog close
|
package/src/AfLookup/Lookup.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import { getLocalizedString } from "@olenbetong/appframe-core";
|
|
|
7
7
|
import { type DistributiveOmit, useLookupState } from "@olenbetong/appframe-react";
|
|
8
8
|
|
|
9
9
|
import clsx from "clsx";
|
|
10
|
+
import { useRef } from "react";
|
|
10
11
|
import { createPortal } from "react-dom";
|
|
11
12
|
|
|
12
13
|
import { Combobox, type ComboboxProps } from "./Combobox.js";
|
|
@@ -42,15 +43,21 @@ export function useLookup<T extends Record<string, unknown>>({
|
|
|
42
43
|
getAnchorElement,
|
|
43
44
|
scrollParentSelector: "[data-virtualized-scroller]",
|
|
44
45
|
});
|
|
46
|
+
let initialSearchValueRef = useRef<string | undefined>(undefined);
|
|
45
47
|
|
|
46
48
|
function handleChange(item: T | null) {
|
|
47
49
|
props.onChange?.(item);
|
|
48
50
|
dialogRef.current?.close();
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
function handleOpenDialog(searchValue?: string) {
|
|
54
|
+
initialSearchValueRef.current = searchValue;
|
|
55
|
+
openDialog();
|
|
56
|
+
}
|
|
57
|
+
|
|
51
58
|
return {
|
|
52
59
|
open,
|
|
53
|
-
openDialog,
|
|
60
|
+
openDialog: handleOpenDialog,
|
|
54
61
|
onKeyDown,
|
|
55
62
|
onChange: handleChange,
|
|
56
63
|
dialogRef,
|
|
@@ -63,7 +70,14 @@ export function useLookup<T extends Record<string, unknown>>({
|
|
|
63
70
|
onKeyDown={(evt) => evt.stopPropagation()}
|
|
64
71
|
style={{ positionAnchor: anchorName, ...slotProps?.dialog?.style } as React.CSSProperties}
|
|
65
72
|
>
|
|
66
|
-
{open &&
|
|
73
|
+
{open && (
|
|
74
|
+
<Combobox
|
|
75
|
+
{...props}
|
|
76
|
+
initialSearchValue={initialSearchValueRef.current}
|
|
77
|
+
onClose={() => dialogRef.current?.close()}
|
|
78
|
+
onChange={handleChange}
|
|
79
|
+
/>
|
|
80
|
+
)}
|
|
67
81
|
</dialog>,
|
|
68
82
|
document.body,
|
|
69
83
|
inputId,
|
|
@@ -82,6 +96,18 @@ export function AfLookup<T extends Record<string, unknown>>({
|
|
|
82
96
|
|
|
83
97
|
let hasClear = !!(props.nullable && value && props.onChange);
|
|
84
98
|
|
|
99
|
+
function handleInputKeyDown(event: React.KeyboardEvent<HTMLElement>) {
|
|
100
|
+
let hasModifier = event.ctrlKey || event.altKey || event.metaKey;
|
|
101
|
+
let isTypeaheadKey = event.key.length === 1 && !hasModifier && !event.nativeEvent.isComposing;
|
|
102
|
+
if (isTypeaheadKey) {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
event.stopPropagation();
|
|
105
|
+
openDialog(event.key);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
onKeyDown(event);
|
|
109
|
+
}
|
|
110
|
+
|
|
85
111
|
return (
|
|
86
112
|
<div style={{ anchorName } as React.CSSProperties}>
|
|
87
113
|
<div
|
|
@@ -97,9 +123,8 @@ export function AfLookup<T extends Record<string, unknown>>({
|
|
|
97
123
|
id={inputId}
|
|
98
124
|
value={value ?? ""}
|
|
99
125
|
label={label}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
onKeyDown={onKeyDown}
|
|
126
|
+
onClick={() => openDialog()}
|
|
127
|
+
onKeyDown={handleInputKeyDown}
|
|
103
128
|
className={clsx(slotProps?.textField?.className, "ObLookup-input")}
|
|
104
129
|
{...(slotProps?.textField as any)}
|
|
105
130
|
style={{ width: fullWidth ? "100%" : undefined, ...slotProps?.textField?.style }}
|
|
@@ -121,7 +146,13 @@ export function AfLookup<T extends Record<string, unknown>>({
|
|
|
121
146
|
<ClearIcon />
|
|
122
147
|
</Button>
|
|
123
148
|
)}
|
|
124
|
-
<Button
|
|
149
|
+
<Button
|
|
150
|
+
icon
|
|
151
|
+
variant="tertiary"
|
|
152
|
+
data-size="sm"
|
|
153
|
+
aria-label={getLocalizedString("Search")}
|
|
154
|
+
onClick={() => openDialog()}
|
|
155
|
+
>
|
|
125
156
|
<SearchIcon />
|
|
126
157
|
</Button>
|
|
127
158
|
</div>
|
package/src/index.ts
CHANGED
|
@@ -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();
|
package/src/layout/AppMenu.css
CHANGED
|
@@ -8,6 +8,12 @@ export type PageContainerProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
8
8
|
maxWidth?: "xs" | "sm" | "md" | "lg" | "xl" | false;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @deprecated Use `Page.Block` instead. `maxWidth` mapping: `xs`/`sm` →
|
|
13
|
+
* `width="text"`, `md` → `"md"`, `lg` → `"lg"`, `xl` → `"xl"`, `false` → omit
|
|
14
|
+
* `width`. Replace `fullHeight` with `<Page layout="app">` and
|
|
15
|
+
* `<Page.Block grow overflow="auto">`.
|
|
16
|
+
*/
|
|
11
17
|
export function PageContainer({ children, className, fullHeight, maxWidth, ...props }: PageContainerProps) {
|
|
12
18
|
return (
|
|
13
19
|
<div className={clsx("ObPageContainer-root", fullHeight && "ObPageContainer-fullHeight")}>
|
package/src/layout/index.tsx
CHANGED
|
@@ -54,6 +54,11 @@ export * from "./ErrorBoundary.js";
|
|
|
54
54
|
export * from "./PageContainer.js";
|
|
55
55
|
export * from "./PartnerToggle.js";
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* @deprecated Use the `Page` primitives instead (`Page`, `Page.Header`,
|
|
59
|
+
* `Page.Block`). `<AppLayout menu={...} title={...}>` becomes
|
|
60
|
+
* `<Page layout="app"><Page.Header title={...} menu={...} />...</Page>`.
|
|
61
|
+
*/
|
|
57
62
|
export function AppLayout(props: AppLayoutProps) {
|
|
58
63
|
let isSmall = useIsMobile();
|
|
59
64
|
let Component = isSmall ? AppLayoutMobile : AppLayoutDesktop;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.ObItemList-root {
|
|
2
|
+
list-style: none;
|
|
3
|
+
margin: 0;
|
|
4
|
+
padding: 0;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.ObItemList-dividers > .ObItemListItem-root:not(:last-child) {
|
|
8
|
+
border-bottom: var(--ds-border-width-default, 1px) solid var(--ds-color-neutral-border-subtle, #dfdfdf);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.ObItemListItem-root {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: var(--ds-size-3, 0.75rem);
|
|
15
|
+
padding: var(--ds-size-2, 0.5rem) var(--ds-size-3, 0.75rem);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.ObItemListItem-disablePadding {
|
|
19
|
+
padding: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.ObItemListItemText-root {
|
|
23
|
+
flex: 1 1 auto;
|
|
24
|
+
min-width: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.ObItemListItemText-secondary {
|
|
28
|
+
font-size: 0.875rem;
|
|
29
|
+
color: var(--ds-color-neutral-text-subtle, #666);
|
|
30
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import "./ItemList.css";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import type React from "react";
|
|
5
|
+
|
|
6
|
+
import { ItemListItem } from "./ItemListItem.js";
|
|
7
|
+
import { ItemListItemText } from "./ItemListItemText.js";
|
|
8
|
+
|
|
9
|
+
export type ItemListProps = React.HTMLAttributes<HTMLElement> & {
|
|
10
|
+
/**
|
|
11
|
+
* Overrides the rendered element (e.g. `"ol"`, `"menu"`)
|
|
12
|
+
* @default "ul"
|
|
13
|
+
*/
|
|
14
|
+
as?: React.ElementType;
|
|
15
|
+
children?: React.ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* Renders a divider between items
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
dividers?: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function ItemListComponent({ as: Component = "ul", children, className, dividers = true, ...props }: ItemListProps) {
|
|
24
|
+
return (
|
|
25
|
+
<Component className={clsx("ObItemList-root", dividers && "ObItemList-dividers", className)} {...props}>
|
|
26
|
+
{children}
|
|
27
|
+
</Component>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* List of flex-row items, equivalent to MUI `List`/`ListItem`. Named
|
|
33
|
+
* `ItemList` to avoid clashing with the designsystemet `List` component
|
|
34
|
+
* (which renders regular text lists). Typically used inside a `Paper`.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```jsx
|
|
38
|
+
* <Paper as={ItemList}>
|
|
39
|
+
* {records.map((record) => (
|
|
40
|
+
* <ItemList.Item key={record.PrimKey}>
|
|
41
|
+
* <img src="..." alt="" />
|
|
42
|
+
* <ItemList.ItemText primary={record.Title} secondary={record.Slug} />
|
|
43
|
+
* <Button>...</Button>
|
|
44
|
+
* </ItemList.Item>
|
|
45
|
+
* ))}
|
|
46
|
+
* </Paper>
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export const ItemList = Object.assign(ItemListComponent, {
|
|
50
|
+
Item: ItemListItem,
|
|
51
|
+
ItemText: ItemListItemText,
|
|
52
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import type React from "react";
|
|
3
|
+
|
|
4
|
+
export type ItemListItemProps = React.HTMLAttributes<HTMLElement> & {
|
|
5
|
+
/**
|
|
6
|
+
* Overrides the rendered element
|
|
7
|
+
* @default "li"
|
|
8
|
+
*/
|
|
9
|
+
as?: React.ElementType;
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
ref?: React.Ref<HTMLElement>;
|
|
12
|
+
/**
|
|
13
|
+
* Removes the inline/block padding, e.g. when the item content is a
|
|
14
|
+
* link or button that should span the full row (add the padding on the
|
|
15
|
+
* interactive child instead so the whole row is clickable).
|
|
16
|
+
* @default false
|
|
17
|
+
*/
|
|
18
|
+
disablePadding?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A single flex-row item inside an `ItemList`. Children are laid out
|
|
23
|
+
* horizontally with a standard gap; use `ItemList.ItemText` for the
|
|
24
|
+
* primary/secondary text block.
|
|
25
|
+
*/
|
|
26
|
+
export function ItemListItem({
|
|
27
|
+
as: Component = "li",
|
|
28
|
+
children,
|
|
29
|
+
className,
|
|
30
|
+
disablePadding,
|
|
31
|
+
...props
|
|
32
|
+
}: ItemListItemProps) {
|
|
33
|
+
return (
|
|
34
|
+
<Component
|
|
35
|
+
className={clsx("ObItemListItem-root", disablePadding && "ObItemListItem-disablePadding", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
>
|
|
38
|
+
{children}
|
|
39
|
+
</Component>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import type React from "react";
|
|
3
|
+
|
|
4
|
+
export type ItemListItemTextProps = React.HTMLAttributes<HTMLElement> & {
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
/** Primary line. `children` can be used instead. */
|
|
7
|
+
primary?: React.ReactNode;
|
|
8
|
+
/** Secondary line, rendered smaller with subtle text color */
|
|
9
|
+
secondary?: React.ReactNode;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Text block inside an `ItemList.Item`. Fills the remaining row space and
|
|
14
|
+
* renders a primary and an optional secondary line, like MUI `ListItemText`.
|
|
15
|
+
*/
|
|
16
|
+
export function ItemListItemText({ children, className, primary, secondary, ...props }: ItemListItemTextProps) {
|
|
17
|
+
return (
|
|
18
|
+
<div className={clsx("ObItemListItemText-root", className)} {...props}>
|
|
19
|
+
<div className="ObItemListItemText-primary">{primary ?? children}</div>
|
|
20
|
+
{secondary != null && <div className="ObItemListItemText-secondary">{secondary}</div>}
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
@media screen {
|
|
2
|
+
/*
|
|
3
|
+
* Pages get a tinted background so surfaces (Paper, drawer, dialogs —
|
|
4
|
+
* which use neutral-background/surface-default) stand out, matching the
|
|
5
|
+
* old site design. Scoped to Page usage so the token itself is untouched.
|
|
6
|
+
*/
|
|
7
|
+
body:has(.ObPage-root) {
|
|
8
|
+
background-color: var(--ds-color-neutral-background-tinted, #f3f3f4);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.ObPage-root {
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ObPage-document {
|
|
18
|
+
min-height: 100%;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.ObPage-app {
|
|
22
|
+
height: 100vh;
|
|
23
|
+
height: 100dvh;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Offset content when a docked Page.Header drawer is open (desktop) */
|
|
28
|
+
.ObPage-root:has(.ObPageHeader-drawer.ObPageHeader-drawerDocked.ObPageHeader-drawerOpen) {
|
|
29
|
+
padding-left: var(--ob-page-drawer-width, 20rem);
|
|
30
|
+
transition: padding-left 225ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@media print {
|
|
35
|
+
.ObPage-root,
|
|
36
|
+
.ObPage-app {
|
|
37
|
+
display: block;
|
|
38
|
+
height: auto;
|
|
39
|
+
overflow: visible;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.ObPage-root:has(.ObPageHeader-drawer.ObPageHeader-drawerDocked.ObPageHeader-drawerOpen) {
|
|
43
|
+
padding-left: 0;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import "./Page.css";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import type React from "react";
|
|
5
|
+
|
|
6
|
+
import { PageBlock } from "./PageBlock.js";
|
|
7
|
+
import { PageHeader } from "./PageHeader.js";
|
|
8
|
+
|
|
9
|
+
export type PageLayout = "document" | "app";
|
|
10
|
+
|
|
11
|
+
export type PageProps = React.HTMLAttributes<HTMLElement> & {
|
|
12
|
+
/**
|
|
13
|
+
* Overrides the rendered element
|
|
14
|
+
* @default "div"
|
|
15
|
+
*/
|
|
16
|
+
as?: React.ElementType;
|
|
17
|
+
children?: React.ReactNode;
|
|
18
|
+
/**
|
|
19
|
+
* Top-level layout strategy for the page.
|
|
20
|
+
*
|
|
21
|
+
* - `"document"` (default): normal document flow — the page grows with its
|
|
22
|
+
* content and the document scrolls.
|
|
23
|
+
* - `"app"`: the page fills the available viewport height with
|
|
24
|
+
* `overflow: hidden`, and scrolling is delegated down the tree (use
|
|
25
|
+
* `Page.Block` with `grow`/`overflow="auto"`, or utilities like
|
|
26
|
+
* `.scroll-view`). Replaces the old `PageContainer` `fullHeight` prop.
|
|
27
|
+
*
|
|
28
|
+
* @default "document"
|
|
29
|
+
*/
|
|
30
|
+
layout?: PageLayout;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function PageComponent({ as: Component = "div", children, className, layout = "document", ...props }: PageProps) {
|
|
34
|
+
return (
|
|
35
|
+
<Component
|
|
36
|
+
className={clsx("ObPage-root", layout === "app" ? "ObPage-app" : "ObPage-document", className)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
</Component>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Top-level layout primitive for application pages, inspired by the Aksel
|
|
46
|
+
* `Page` primitives. Composes with `Page.Header` and `Page.Block`.
|
|
47
|
+
*
|
|
48
|
+
* Works both in the current sidebar-based site layout and the upcoming
|
|
49
|
+
* toolbar-based (sidebar-free) site layout.
|
|
50
|
+
*
|
|
51
|
+
* @example Document-style page (content scrolls with the document)
|
|
52
|
+
* ```jsx
|
|
53
|
+
* <Page>
|
|
54
|
+
* <Page.Header variant="plain" title="Report issue" />
|
|
55
|
+
* <Page.Block as="main" width="text" gutters>...</Page.Block>
|
|
56
|
+
* </Page>
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @example App-style page (fills the viewport, scrolling delegated down the tree)
|
|
60
|
+
* ```jsx
|
|
61
|
+
* <Page layout="app">
|
|
62
|
+
* <Page.Header title="Products" menu={<ProductsMenu />} />
|
|
63
|
+
* <Page.Block as="main" gutters grow overflow="auto">...</Page.Block>
|
|
64
|
+
* </Page>
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export const Page = Object.assign(PageComponent, {
|
|
68
|
+
Block: PageBlock,
|
|
69
|
+
Header: PageHeader,
|
|
70
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
.ObPageBlock-root {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
width: 100%;
|
|
4
|
+
margin-inline: auto;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.ObPageBlock-text {
|
|
8
|
+
max-width: calc(36rem + 2 * var(--ob-page-gutter, 0rem));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.ObPageBlock-md {
|
|
12
|
+
max-width: 48rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.ObPageBlock-lg {
|
|
16
|
+
max-width: 64rem;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.ObPageBlock-xl {
|
|
20
|
+
max-width: 80rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.ObPageBlock-2xl {
|
|
24
|
+
max-width: 90rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.ObPageBlock-gutters {
|
|
28
|
+
--ob-page-gutter: 1rem;
|
|
29
|
+
|
|
30
|
+
padding-inline: var(--ob-page-gutter);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@media screen and (min-width: 600px) {
|
|
34
|
+
.ObPageBlock-gutters {
|
|
35
|
+
--ob-page-gutter: 1.5rem;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@media screen {
|
|
40
|
+
.ObPageBlock-grow {
|
|
41
|
+
flex: 1 1 auto;
|
|
42
|
+
min-height: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.ObPageBlock-overflowAuto {
|
|
46
|
+
overflow: auto;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.ObPageBlock-overflowHidden {
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@media print {
|
|
55
|
+
.ObPageBlock-overflowAuto,
|
|
56
|
+
.ObPageBlock-overflowHidden {
|
|
57
|
+
overflow: visible;
|
|
58
|
+
}
|
|
59
|
+
}
|