@olenbetong/appframe-ds 0.4.1 → 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 +10 -0
- package/package.json +1 -1
- package/scripts/copyAssets.mjs +8 -0
- 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 -32
- package/es/AfLookup/Combobox.js +0 -76
- package/es/AfLookup/Lookup.css +0 -125
- package/es/AfLookup/Lookup.d.ts +0 -35
- package/es/AfLookup/Lookup.js +0 -59
- 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
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import "./PageHeader.css";
|
|
2
|
+
|
|
3
|
+
import { getLocalizedString } from "@olenbetong/appframe-core";
|
|
4
|
+
|
|
5
|
+
import clsx from "clsx";
|
|
6
|
+
import type React from "react";
|
|
7
|
+
import { useEffect, useRef, useState } from "react";
|
|
8
|
+
import { useLocation } from "react-router";
|
|
9
|
+
|
|
10
|
+
import { AppMenu, type AppMenuProps } from "../layout/AppMenu.js";
|
|
11
|
+
import { PageBlock, type PageBlockWidth } from "./PageBlock.js";
|
|
12
|
+
|
|
13
|
+
export type PageHeaderVariant = "appbar" | "plain";
|
|
14
|
+
|
|
15
|
+
export type PageHeaderProps = Omit<React.HTMLAttributes<HTMLElement>, "title"> & {
|
|
16
|
+
/**
|
|
17
|
+
* Actions displayed at the end of the header (buttons, search, etc.)
|
|
18
|
+
*/
|
|
19
|
+
actions?: React.ReactNode;
|
|
20
|
+
/**
|
|
21
|
+
* Props passed to the `AppMenu` wrapping the `menu` content
|
|
22
|
+
*/
|
|
23
|
+
AppMenuProps?: AppMenuProps;
|
|
24
|
+
children?: React.ReactNode;
|
|
25
|
+
/**
|
|
26
|
+
* App menu (navigation) content. When provided, the `appbar` variant
|
|
27
|
+
* renders a menu toggle button and a drawer hosting the menu — docked on
|
|
28
|
+
* desktop (≥1200px), overlay on smaller screens.
|
|
29
|
+
*/
|
|
30
|
+
menu?: React.ReactNode;
|
|
31
|
+
/**
|
|
32
|
+
* Application title
|
|
33
|
+
*/
|
|
34
|
+
title?: React.ReactNode;
|
|
35
|
+
/**
|
|
36
|
+
* Adds a standardised responsive inline padding to the header content.
|
|
37
|
+
* See `Page.Block`.
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
gutters?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Constrains and centers the header content to a predefined max-width,
|
|
43
|
+
* so it can align with a `Page.Block` of the same width. See `Page.Block`.
|
|
44
|
+
*/
|
|
45
|
+
width?: PageBlockWidth;
|
|
46
|
+
/**
|
|
47
|
+
* - `"appbar"` (default): a top app bar with menu toggle, title and
|
|
48
|
+
* actions. For apps that have an app menu.
|
|
49
|
+
* - `"plain"`: no app bar chrome — for simple apps without an app menu
|
|
50
|
+
* (e.g. report-issue). Renders the title as a regular heading block.
|
|
51
|
+
*
|
|
52
|
+
* Both variants reserve an end-slot (`.ObPageHeader-end`) that will host
|
|
53
|
+
* the app-launcher and user profile menu in the future sidebar-free site
|
|
54
|
+
* layout. It is hidden under the current sidebar layout.
|
|
55
|
+
* @default "appbar"
|
|
56
|
+
*/
|
|
57
|
+
variant?: PageHeaderVariant;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function useIsMobile() {
|
|
61
|
+
let [isMobile, setIsMobile] = useState(() => {
|
|
62
|
+
if (typeof window === "undefined") return false;
|
|
63
|
+
return window.matchMedia("(max-width: 1199px)").matches;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
let mq = window.matchMedia("(max-width: 1199px)");
|
|
68
|
+
let handler = (e: MediaQueryListEvent) => setIsMobile(e.matches);
|
|
69
|
+
mq.addEventListener("change", handler);
|
|
70
|
+
return () => mq.removeEventListener("change", handler);
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
73
|
+
return isMobile;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type PageHeaderDrawerProps = {
|
|
77
|
+
AppMenuProps?: AppMenuProps;
|
|
78
|
+
menu: React.ReactNode;
|
|
79
|
+
onClose: () => void;
|
|
80
|
+
isMobile: boolean;
|
|
81
|
+
open: boolean;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
function PageHeaderDrawer({ AppMenuProps, menu, onClose, isMobile, open }: PageHeaderDrawerProps) {
|
|
85
|
+
let location = useLocation();
|
|
86
|
+
let prevLocation = useRef(location);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (location !== prevLocation.current) {
|
|
90
|
+
prevLocation.current = location;
|
|
91
|
+
if (isMobile) onClose();
|
|
92
|
+
}
|
|
93
|
+
}, [location, isMobile, onClose]);
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<>
|
|
97
|
+
{isMobile && (
|
|
98
|
+
<div
|
|
99
|
+
className={clsx("ObPageHeader-drawerOverlay", open && "ObPageHeader-drawerOpen")}
|
|
100
|
+
onClick={onClose}
|
|
101
|
+
aria-hidden="true"
|
|
102
|
+
/>
|
|
103
|
+
)}
|
|
104
|
+
<div
|
|
105
|
+
className={clsx(
|
|
106
|
+
"ObPageHeader-drawer",
|
|
107
|
+
"AppMenu-drawer",
|
|
108
|
+
isMobile ? "ObPageHeader-drawerOverlayMode" : "ObPageHeader-drawerDocked",
|
|
109
|
+
open && "ObPageHeader-drawerOpen",
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
<AppMenu {...AppMenuProps}>{menu}</AppMenu>
|
|
113
|
+
</div>
|
|
114
|
+
</>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Page header for use inside `Page`. See the `variant` prop for the two
|
|
120
|
+
* supported header styles.
|
|
121
|
+
*/
|
|
122
|
+
export function PageHeader({
|
|
123
|
+
actions,
|
|
124
|
+
AppMenuProps,
|
|
125
|
+
children,
|
|
126
|
+
className,
|
|
127
|
+
gutters,
|
|
128
|
+
menu,
|
|
129
|
+
title,
|
|
130
|
+
variant = "appbar",
|
|
131
|
+
width,
|
|
132
|
+
...props
|
|
133
|
+
}: PageHeaderProps) {
|
|
134
|
+
let isMobile = useIsMobile();
|
|
135
|
+
let [desktopOpen, setDesktopOpen] = useState(true);
|
|
136
|
+
let [mobileOpen, setMobileOpen] = useState(false);
|
|
137
|
+
|
|
138
|
+
let open = isMobile ? mobileOpen : desktopOpen;
|
|
139
|
+
let toggle = () => (isMobile ? setMobileOpen((o) => !o) : setDesktopOpen((o) => !o));
|
|
140
|
+
|
|
141
|
+
if (variant === "plain") {
|
|
142
|
+
return (
|
|
143
|
+
<header className={clsx("ObPageHeader-root", "ObPageHeader-plain", className)} {...props}>
|
|
144
|
+
<PageBlock className="ObPageHeader-plainInner" width={width} gutters={gutters}>
|
|
145
|
+
{title && <h1 className="ObPageHeader-plainTitle">{title}</h1>}
|
|
146
|
+
{actions && <div className="ObPageHeader-actions">{actions}</div>}
|
|
147
|
+
{children}
|
|
148
|
+
<div className="ObPageHeader-end" />
|
|
149
|
+
</PageBlock>
|
|
150
|
+
</header>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<header className={clsx("ObPageHeader-root", "ObPageHeader-appbar", className)} {...props}>
|
|
156
|
+
<PageBlock className="ObPageHeader-bar" width={width} gutters={gutters}>
|
|
157
|
+
{menu && (
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
className="ObPageHeader-toggle"
|
|
161
|
+
onClick={toggle}
|
|
162
|
+
aria-label={getLocalizedString("Toggle menu")}
|
|
163
|
+
aria-expanded={open}
|
|
164
|
+
>
|
|
165
|
+
☰
|
|
166
|
+
</button>
|
|
167
|
+
)}
|
|
168
|
+
{title && <h1 className="ObPageHeader-title">{title}</h1>}
|
|
169
|
+
{actions && <div className="ObPageHeader-actions">{actions}</div>}
|
|
170
|
+
<div className="ObPageHeader-end" />
|
|
171
|
+
</PageBlock>
|
|
172
|
+
{children}
|
|
173
|
+
{menu && (
|
|
174
|
+
<PageHeaderDrawer
|
|
175
|
+
AppMenuProps={AppMenuProps}
|
|
176
|
+
menu={menu}
|
|
177
|
+
onClose={() => setMobileOpen(false)}
|
|
178
|
+
isMobile={isMobile}
|
|
179
|
+
open={open}
|
|
180
|
+
/>
|
|
181
|
+
)}
|
|
182
|
+
</header>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.ObPaper-root {
|
|
2
|
+
background-color: var(--ds-color-neutral-surface-default, #fff);
|
|
3
|
+
color: var(--ds-color-neutral-text-default, #1e2b3c);
|
|
4
|
+
border-radius: var(--ds-border-radius-md, 0.375rem);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.ObPaper-shadow-xs {
|
|
8
|
+
box-shadow: var(--ds-shadow-xs);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.ObPaper-shadow-sm {
|
|
12
|
+
box-shadow: var(--ds-shadow-sm);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.ObPaper-shadow-md {
|
|
16
|
+
box-shadow: var(--ds-shadow-md);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.ObPaper-shadow-lg {
|
|
20
|
+
box-shadow: var(--ds-shadow-lg);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.ObPaper-shadow-xl {
|
|
24
|
+
box-shadow: var(--ds-shadow-xl);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.ObPaper-border {
|
|
28
|
+
border: var(--ds-border-width-default, 1px) solid var(--ds-color-neutral-border-subtle, #dfdfdf);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@media print {
|
|
32
|
+
.ObPaper-root {
|
|
33
|
+
box-shadow: none;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import "./Paper.css";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import type React from "react";
|
|
5
|
+
|
|
6
|
+
export const paperShadows = ["none", "xs", "sm", "md", "lg", "xl"] as const;
|
|
7
|
+
|
|
8
|
+
export type PaperShadow = (typeof paperShadows)[number];
|
|
9
|
+
|
|
10
|
+
export type PaperProps = React.HTMLAttributes<HTMLElement> & {
|
|
11
|
+
/**
|
|
12
|
+
* Overrides the rendered element (e.g. `"section"`, `"ul"`)
|
|
13
|
+
* @default "div"
|
|
14
|
+
*/
|
|
15
|
+
as?: React.ElementType;
|
|
16
|
+
/**
|
|
17
|
+
* Adds a subtle border. Combine with `shadow="none"` for an outlined
|
|
18
|
+
* variant (MUI `variant="outlined"` equivalent).
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
border?: boolean;
|
|
22
|
+
children?: React.ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* Elevation shadow, using the designsystemet `--ds-shadow-*` tokens.
|
|
25
|
+
* @default "sm"
|
|
26
|
+
*/
|
|
27
|
+
shadow?: PaperShadow;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Surface component equivalent to MUI `Paper`: a neutral surface with
|
|
32
|
+
* rounded corners and an elevation shadow. Use it instead of hand-rolled
|
|
33
|
+
* `background` + `box-shadow` CSS so surfaces stay consistent with the
|
|
34
|
+
* designsystemet tokens (and adapt to dark mode).
|
|
35
|
+
*/
|
|
36
|
+
export function Paper({ as: Component = "div", border, children, className, shadow = "sm", ...props }: PaperProps) {
|
|
37
|
+
return (
|
|
38
|
+
<Component
|
|
39
|
+
className={clsx(
|
|
40
|
+
"ObPaper-root",
|
|
41
|
+
shadow !== "none" && `ObPaper-shadow-${shadow}`,
|
|
42
|
+
border && "ObPaper-border",
|
|
43
|
+
className,
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
{children}
|
|
48
|
+
</Component>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Paper.js";
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { CurrentRow, DataObject } from "@olenbetong/appframe-data";
|
|
2
|
-
import { type DistributiveOmit } from "@olenbetong/appframe-react";
|
|
3
|
-
import { type AfLookupProps } from "./Lookup.js";
|
|
4
|
-
export type BoundLookupProps<TBoundData extends Record<string, unknown>, TLookupData extends Record<string, unknown>> = DistributiveOmit<AfLookupProps<TLookupData>, "onChange"> & {
|
|
5
|
-
dataObject: DataObject<TLookupData>;
|
|
6
|
-
displayField?: keyof TBoundData;
|
|
7
|
-
getChanges: (selectedItem: TLookupData | null) => Partial<TBoundData>;
|
|
8
|
-
getDisplayValue?: (currentRow: CurrentRow<TBoundData>) => string;
|
|
9
|
-
};
|
|
10
|
-
export declare function BoundLookup<TBoundData extends Record<string, unknown>, TLookupData extends Record<string, unknown> = any>({ dataObject, displayField, getChanges, getDisplayValue, ...props }: BoundLookupProps<TBoundData, TLookupData>): import("react").JSX.Element;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useCurrentRow, useDataObject } from "@olenbetong/appframe-react";
|
|
3
|
-
import { AfLookup } from "./Lookup.js";
|
|
4
|
-
export function BoundLookup({ dataObject, displayField, getChanges, getDisplayValue, ...props }) {
|
|
5
|
-
let boundDataObject = useDataObject();
|
|
6
|
-
let row = useCurrentRow(boundDataObject);
|
|
7
|
-
function handleChange(item) {
|
|
8
|
-
let changes = getChanges(item);
|
|
9
|
-
for (let key in changes) {
|
|
10
|
-
let newValue = changes[key];
|
|
11
|
-
boundDataObject.currentRow(key, newValue);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
let displayValue = getDisplayValue ? getDisplayValue(row) : displayField ? String(row[displayField] ?? "") : "";
|
|
15
|
-
return _jsx(AfLookup, { dataObject: dataObject, value: displayValue, onChange: handleChange, ...props });
|
|
16
|
-
}
|
package/es/AfLookup/Combobox.css
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
.ObCombobox-root {
|
|
2
|
-
height: 100%;
|
|
3
|
-
display: grid;
|
|
4
|
-
grid-template-rows: min-content auto 1fr;
|
|
5
|
-
gap: 0.5rem;
|
|
6
|
-
|
|
7
|
-
overscroll-behavior: contain;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.ObCombobox-actions {
|
|
11
|
-
display: flex;
|
|
12
|
-
justify-content: space-between;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.ObCombobox-listWrap {
|
|
16
|
-
overflow: hidden;
|
|
17
|
-
min-height: 0;
|
|
18
|
-
border: 1px solid rgb(0 0 0 / 0.12);
|
|
19
|
-
border-radius: 0.25rem;
|
|
20
|
-
background-color: white;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.ObCombobox-list {
|
|
24
|
-
border: 1px solid rgb(0 0 0 / 0.06);
|
|
25
|
-
height: 100%;
|
|
26
|
-
overflow-y: hidden;
|
|
27
|
-
overscroll-behavior: contain;
|
|
28
|
-
list-style: none;
|
|
29
|
-
margin: 0;
|
|
30
|
-
padding: 0;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.ObCombobox-input {
|
|
34
|
-
background-color: white;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.ObCombobox-listItem {
|
|
38
|
-
--active-color: oklch(from var(--brand-secondary, #999) l c h / 50%);
|
|
39
|
-
cursor: pointer;
|
|
40
|
-
|
|
41
|
-
&.active {
|
|
42
|
-
background-color: var(--active-color);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
&:hover {
|
|
46
|
-
background-color: rgb(0 0 0 / 0.06);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
&.active:hover {
|
|
50
|
-
background-color: oklch(from var(--active-color) calc(l + 0.1) calc(c - 0.04) h);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.ObCombobox-progress {
|
|
55
|
-
position: relative;
|
|
56
|
-
height: 4px;
|
|
57
|
-
overflow: hidden;
|
|
58
|
-
background-color: rgb(0 0 0 / 0.08);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.ObCombobox-progress-bar {
|
|
62
|
-
position: absolute;
|
|
63
|
-
top: 0;
|
|
64
|
-
left: -100%;
|
|
65
|
-
height: 100%;
|
|
66
|
-
width: 50%;
|
|
67
|
-
background-color: var(--ds-color-accent-base-default, #0062ba);
|
|
68
|
-
animation: ObCombobox-progress-anim 1.5s ease-in-out infinite;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
@keyframes ObCombobox-progress-anim {
|
|
72
|
-
0% {
|
|
73
|
-
left: -100%;
|
|
74
|
-
}
|
|
75
|
-
100% {
|
|
76
|
-
left: 200%;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import "./Combobox.css";
|
|
2
|
-
import type { DataObject } from "@olenbetong/appframe-data";
|
|
3
|
-
import { type LookupColumn } from "@olenbetong/appframe-react";
|
|
4
|
-
export interface ComboboxBaseProps<T extends Record<string, unknown>> {
|
|
5
|
-
nullable?: boolean;
|
|
6
|
-
dataObject: DataObject<T>;
|
|
7
|
-
uniqueIdField?: string;
|
|
8
|
-
initialSearchValue?: string;
|
|
9
|
-
isItemSelected?: (item: T) => boolean;
|
|
10
|
-
onChange?: (item: T | null) => void;
|
|
11
|
-
value?: T;
|
|
12
|
-
onClose?: () => void;
|
|
13
|
-
}
|
|
14
|
-
export interface ComboboxListProps<T extends Record<string, unknown>> extends ComboboxBaseProps<T> {
|
|
15
|
-
/**
|
|
16
|
-
* Renders the popup content as a list (default).
|
|
17
|
-
*/
|
|
18
|
-
variant?: "list";
|
|
19
|
-
renderItem: (props: React.ComponentPropsWithRef<"li"> & {
|
|
20
|
-
item: T;
|
|
21
|
-
}) => React.ReactNode;
|
|
22
|
-
}
|
|
23
|
-
export interface ComboboxGridProps<T extends Record<string, unknown>> extends ComboboxBaseProps<T> {
|
|
24
|
-
/**
|
|
25
|
-
* Renders the popup content as a grid with a header row and one column per
|
|
26
|
-
* entry in `columns`.
|
|
27
|
-
*/
|
|
28
|
-
variant: "grid";
|
|
29
|
-
columns: LookupColumn<T>[];
|
|
30
|
-
}
|
|
31
|
-
export type ComboboxProps<T extends Record<string, unknown>> = ComboboxListProps<T> | ComboboxGridProps<T>;
|
|
32
|
-
export declare function Combobox<T extends Record<string, unknown>>(props: ComboboxProps<T>): import("react").JSX.Element;
|
package/es/AfLookup/Combobox.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import "./Combobox.css";
|
|
3
|
-
import { Button, Textfield } from "@digdir/designsystemet-react";
|
|
4
|
-
import { getLocalizedString } from "@olenbetong/appframe-core";
|
|
5
|
-
import { useComboboxData } from "@olenbetong/appframe-react";
|
|
6
|
-
import clsx from "clsx";
|
|
7
|
-
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
8
|
-
import { VariableSizeList as VirtualList } from "react-window";
|
|
9
|
-
import { LookupGrid } from "./LookupGrid.js";
|
|
10
|
-
export function Combobox(props) {
|
|
11
|
-
let { nullable, dataObject, isItemSelected, uniqueIdField = "PrimKey", initialSearchValue, onChange, onClose, } = props;
|
|
12
|
-
let isGrid = props.variant === "grid";
|
|
13
|
-
let columns = isGrid ? props.columns : undefined;
|
|
14
|
-
let listRef = useRef(null);
|
|
15
|
-
let [isDesktop, setIsDesktop] = useState(() => typeof window !== "undefined" && window.matchMedia("(width >= 768px) and (pointer: fine)").matches);
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
let mq = window.matchMedia("(width >= 768px) and (pointer: fine)");
|
|
18
|
-
let handler = (e) => setIsDesktop(e.matches);
|
|
19
|
-
mq.addEventListener("change", handler);
|
|
20
|
-
return () => mq.removeEventListener("change", handler);
|
|
21
|
-
}, []);
|
|
22
|
-
let { id, data, loading, inputValue, setInputValue, currentIndex, currentColumn, getItemId, handleSelectItem, handleKeyDown, } = useComboboxData({
|
|
23
|
-
dataObject,
|
|
24
|
-
uniqueIdField,
|
|
25
|
-
columnCount: columns?.length ?? 1,
|
|
26
|
-
onChange,
|
|
27
|
-
});
|
|
28
|
-
let wrapperRef = useRef(null);
|
|
29
|
-
let [height, setHeight] = useState(0);
|
|
30
|
-
useLayoutEffect(() => {
|
|
31
|
-
if (!wrapperRef.current)
|
|
32
|
-
return;
|
|
33
|
-
let resizeObserver = new ResizeObserver(() => {
|
|
34
|
-
let newHeight = wrapperRef.current.offsetHeight;
|
|
35
|
-
if (newHeight > 0)
|
|
36
|
-
setHeight(newHeight);
|
|
37
|
-
});
|
|
38
|
-
resizeObserver.observe(wrapperRef.current);
|
|
39
|
-
return () => resizeObserver.disconnect();
|
|
40
|
-
}, []);
|
|
41
|
-
let sizeMap = useRef(new Map());
|
|
42
|
-
let getItemSize = (index) => sizeMap.current.get(index) ?? 56;
|
|
43
|
-
let setSize = useCallback((index, size) => {
|
|
44
|
-
if (sizeMap.current.get(index) !== size) {
|
|
45
|
-
sizeMap.current.set(index, size);
|
|
46
|
-
// listRef will not be set on the initial render, so delay resetting
|
|
47
|
-
setTimeout(() => listRef.current?.resetAfterIndex(index, true));
|
|
48
|
-
}
|
|
49
|
-
}, []);
|
|
50
|
-
let Row = ({ index, style: { height, ...style } }) => {
|
|
51
|
-
let rowRef = useRef(null);
|
|
52
|
-
useLayoutEffect(() => {
|
|
53
|
-
if (!rowRef.current)
|
|
54
|
-
return;
|
|
55
|
-
setSize(index, rowRef.current.offsetHeight);
|
|
56
|
-
}, [index]);
|
|
57
|
-
let item = data[index];
|
|
58
|
-
let itemId = getItemId(item);
|
|
59
|
-
let active = index === currentIndex;
|
|
60
|
-
let ItemComponent = props.renderItem;
|
|
61
|
-
return (_jsx(ItemComponent, { className: clsx("ObCombobox-listItem", active && "active", isItemSelected?.(item) && "selected"), "aria-selected": active, onClick: () => handleSelectItem(item), item: item, style: {
|
|
62
|
-
...style,
|
|
63
|
-
padding: isDesktop ? "0 8px" : "8px",
|
|
64
|
-
}, ref: rowRef, id: itemId }, itemId));
|
|
65
|
-
};
|
|
66
|
-
useEffect(() => listRef.current?.scrollToItem(currentIndex, "smart"), [currentIndex]);
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
if (initialSearchValue !== undefined) {
|
|
69
|
-
setInputValue(initialSearchValue);
|
|
70
|
-
}
|
|
71
|
-
}, [initialSearchValue, setInputValue]);
|
|
72
|
-
return (_jsxs("div", { className: "ObCombobox-root", id: id, onClick: (evt) => evt.stopPropagation(), children: [_jsxs("div", { className: "ObCombobox-actions", children: [nullable && (_jsx(Button, { className: "ObCombobox-clear", "data-size": isDesktop ? "sm" : "md", variant: "tertiary", onClick: () => {
|
|
73
|
-
onChange?.(null);
|
|
74
|
-
}, children: getLocalizedString("Clear") })), onClose && (_jsx(Button, { "data-size": isDesktop ? "sm" : "md", variant: "tertiary", className: "ObCombobox-close", onClick: onClose, children: getLocalizedString("Close") }))] }), _jsx(Textfield, { type: "search", className: "ObCombobox-input", "aria-label": getLocalizedString("Search"), placeholder: getLocalizedString("Search"), onKeyDown: handleKeyDown, "aria-activedescendant": getItemId(data[currentIndex]) ?? "", value: inputValue, onChange: (event) => setInputValue(event.target.value) }), _jsxs("div", { className: "ObCombobox-listWrap", ref: wrapperRef, children: [loading && (_jsx("div", { className: "ObCombobox-progress", children: _jsx("div", { className: "ObCombobox-progress-bar" }) })), height > 0 &&
|
|
75
|
-
(columns ? (_jsx(LookupGrid, { columns: columns, data: data, height: height, currentIndex: currentIndex, currentColumn: currentColumn, getItemId: getItemId, isItemSelected: isItemSelected, onSelectItem: handleSelectItem })) : (_jsx("div", { className: "ObCombobox-list", role: "listbox", children: _jsx(VirtualList, { ref: listRef, height: height, width: "100%", itemCount: data.length, itemSize: getItemSize, overscanCount: 5, children: Row }) })))] })] }));
|
|
76
|
-
}
|
package/es/AfLookup/Lookup.css
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
.ObLookup-inputWrap {
|
|
2
|
-
position: relative;
|
|
3
|
-
|
|
4
|
-
.ds-input {
|
|
5
|
-
padding-inline-end: var(--ObLookup-buttons-width, 3rem);
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.ObLookup-buttons {
|
|
10
|
-
position: absolute;
|
|
11
|
-
inset-inline-end: 0;
|
|
12
|
-
inset-block-end: 0;
|
|
13
|
-
/* Match the .ds-input height so the buttons center on the input row,
|
|
14
|
-
also when a label or description is rendered above the input */
|
|
15
|
-
height: var(--ds-size-12, 3rem);
|
|
16
|
-
display: flex;
|
|
17
|
-
align-items: center;
|
|
18
|
-
padding-inline-end: var(--ds-size-1, 0.25rem);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.ObLookup-dialog {
|
|
22
|
-
border: 0;
|
|
23
|
-
margin-block-start: 0;
|
|
24
|
-
padding: 1rem;
|
|
25
|
-
overflow: hidden;
|
|
26
|
-
|
|
27
|
-
background-color: whitesmoke;
|
|
28
|
-
|
|
29
|
-
transition:
|
|
30
|
-
display 0.2s allow-discrete,
|
|
31
|
-
transform 0.2s ease-out,
|
|
32
|
-
scale 0.2s ease-out,
|
|
33
|
-
opacity 0.2s ease-out;
|
|
34
|
-
|
|
35
|
-
&::backdrop {
|
|
36
|
-
opacity: 0;
|
|
37
|
-
background: rgb(0 0 0 / 0.2);
|
|
38
|
-
transition:
|
|
39
|
-
display 0.2s allow-discrete,
|
|
40
|
-
opacity 0.2s ease-out;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
&[open]::backdrop {
|
|
44
|
-
opacity: 1;
|
|
45
|
-
|
|
46
|
-
@starting-style {
|
|
47
|
-
opacity: 0;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@media (width < 768px) {
|
|
52
|
-
inset: 10vh 0.5rem 0;
|
|
53
|
-
|
|
54
|
-
border-radius: 1rem 1rem 0 0;
|
|
55
|
-
height: 90vh;
|
|
56
|
-
width: calc(100vw - 1rem);
|
|
57
|
-
max-width: 100vw;
|
|
58
|
-
transform: translateY(100%);
|
|
59
|
-
z-index: 1500;
|
|
60
|
-
|
|
61
|
-
opacity: 0;
|
|
62
|
-
|
|
63
|
-
&[open] {
|
|
64
|
-
transform: translateY(0);
|
|
65
|
-
opacity: 1;
|
|
66
|
-
|
|
67
|
-
@starting-style {
|
|
68
|
-
transform: translateY(100%);
|
|
69
|
-
opacity: 0;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
@media (width >= 768px) {
|
|
75
|
-
inset-block-start: 50%;
|
|
76
|
-
inset-inline-start: 50%;
|
|
77
|
-
translate: -50% -50%;
|
|
78
|
-
scale: 1 0;
|
|
79
|
-
opacity: 0;
|
|
80
|
-
|
|
81
|
-
border-radius: 0.25rem;
|
|
82
|
-
|
|
83
|
-
height: clamp(300px, 80vh, 500px);
|
|
84
|
-
min-width: 320px;
|
|
85
|
-
width: var(--default-width, 400px);
|
|
86
|
-
|
|
87
|
-
&[open] {
|
|
88
|
-
scale: 1;
|
|
89
|
-
opacity: 1;
|
|
90
|
-
|
|
91
|
-
@starting-style {
|
|
92
|
-
scale: 1 0;
|
|
93
|
-
opacity: 0;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
@supports (inset-block-start: anchor(bottom)) {
|
|
99
|
-
@media (width < 768px) or (pointer: coarse) {
|
|
100
|
-
anchor-name: unset !important;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
@media screen and (width >= 768px) and (pointer: fine) {
|
|
104
|
-
inset-block-start: anchor(bottom);
|
|
105
|
-
inset-block-end: unset;
|
|
106
|
-
|
|
107
|
-
inset-inline-start: anchor(left);
|
|
108
|
-
inset-inline-end: unset;
|
|
109
|
-
|
|
110
|
-
translate: unset;
|
|
111
|
-
transform-origin: top left;
|
|
112
|
-
|
|
113
|
-
resize: both;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
&[data-anchor="manual"] {
|
|
118
|
-
inset: unset;
|
|
119
|
-
position: fixed;
|
|
120
|
-
inset-inline-start: var(--lookup-anchor-inline-start, 50%);
|
|
121
|
-
inset-block-start: var(--lookup-anchor-block-start, 50%);
|
|
122
|
-
translate: unset;
|
|
123
|
-
transform: none;
|
|
124
|
-
}
|
|
125
|
-
}
|
package/es/AfLookup/Lookup.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import "./Lookup.css";
|
|
2
|
-
import { type TextfieldProps } from "@digdir/designsystemet-react";
|
|
3
|
-
import { type DistributiveOmit } from "@olenbetong/appframe-react";
|
|
4
|
-
import { type ComboboxProps } from "./Combobox.js";
|
|
5
|
-
export type AfLookupProps<T extends Record<string, unknown>> = DistributiveOmit<ComboboxProps<T>, "value"> & {
|
|
6
|
-
/**
|
|
7
|
-
* Allows the user to enter a value manually that might not be in the list.
|
|
8
|
-
*/
|
|
9
|
-
editable?: boolean;
|
|
10
|
-
fullWidth?: boolean;
|
|
11
|
-
label?: string;
|
|
12
|
-
value?: string | null;
|
|
13
|
-
onChange?: (item: T | null) => void;
|
|
14
|
-
slotProps?: {
|
|
15
|
-
dialog?: Partial<React.HTMLProps<HTMLDialogElement>>;
|
|
16
|
-
textField?: Partial<TextfieldProps>;
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
export type LookupProps<T extends Record<string, unknown>> = DistributiveOmit<ComboboxProps<T>, "value"> & {
|
|
20
|
-
slotProps?: {
|
|
21
|
-
dialog?: Partial<React.HTMLProps<HTMLDialogElement>>;
|
|
22
|
-
};
|
|
23
|
-
getAnchorElement?: () => Element | null;
|
|
24
|
-
};
|
|
25
|
-
export declare function useLookup<T extends Record<string, unknown>>({ getAnchorElement, slotProps, ...props }: LookupProps<T>): {
|
|
26
|
-
open: boolean;
|
|
27
|
-
openDialog: (searchValue?: string) => void;
|
|
28
|
-
onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;
|
|
29
|
-
onChange: (item: T | null) => void;
|
|
30
|
-
dialogRef: import("react").RefObject<HTMLDialogElement | null>;
|
|
31
|
-
anchorName: string;
|
|
32
|
-
inputId: string;
|
|
33
|
-
dialog: import("react").ReactPortal;
|
|
34
|
-
};
|
|
35
|
-
export declare function AfLookup<T extends Record<string, unknown>>({ fullWidth, label, slotProps, value, ...props }: AfLookupProps<T>): import("react").JSX.Element;
|