@olenbetong/appframe-ds 1.6.1 → 1.7.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.
@@ -1,130 +1,130 @@
1
- import "./Lookup.css";
2
-
3
- import { Button, Textfield, type TextfieldProps } from "@digdir/designsystemet-react";
4
- import { MagnifyingGlassIcon, XMarkIcon } from "@navikt/aksel-icons";
5
- import type { DistributiveOmit } from "@olenbetong/appframe-react";
6
-
7
- import clsx from "clsx";
8
- import type React from "react";
9
-
10
- import type { LookupComboboxProps } from "./LookupCombobox.js";
11
- import { useLookup } from "./Lookup.js";
12
- import { getLocalizedString } from "../i18n.js";
13
-
14
- export type AfLookupEditProps<T extends Record<string, unknown>> = DistributiveOmit<
15
- LookupComboboxProps<T>,
16
- "value" | "onChange"
17
- > & {
18
- /**
19
- * Allows the user to enter a value manually that might not be in the list.
20
- */
21
- editable?: boolean;
22
- fullWidth?: boolean;
23
- label?: string;
24
- value?: string | null;
25
- lookupField: keyof T;
26
- onChange?: (evt: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
27
- slotProps?: {
28
- dialog?: Partial<React.HTMLProps<HTMLDialogElement>>;
29
- textField?: Partial<TextfieldProps>;
30
- };
31
- };
32
-
33
- export function AfLookupEdit<T extends Record<string, unknown>>({
34
- fullWidth = false,
35
- label,
36
- onChange,
37
- slotProps,
38
- nullable,
39
- lookupField,
40
- value,
41
- ...props
42
- }: AfLookupEditProps<T>) {
43
- let { inputId, dialog, anchorName, openDialog, onKeyDown } = useLookup<T>({
44
- ...props,
45
- nullable,
46
- slotProps: { dialog: slotProps?.dialog },
47
- onChange: (item) => applyValue(item),
48
- });
49
-
50
- // The popup selects a single value from the dataset: the item's lookupField
51
- // is written to the input through the native value setter so the same
52
- // change event fires whether the user types or picks from the list/grid.
53
- function applyValue(item: T | null) {
54
- let input = document.getElementById(inputId) as HTMLInputElement;
55
- let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
56
-
57
- if (input && nativeInputValueSetter) {
58
- nativeInputValueSetter.call(input, item ? String(item[lookupField]) : "");
59
- input.dispatchEvent(new Event("input", { bubbles: true }));
60
- }
61
- }
62
-
63
- function handleKeyDown(event: React.KeyboardEvent<HTMLElement>) {
64
- if (event.key === "Enter") {
65
- event.preventDefault();
66
- event.stopPropagation();
67
- openDialog();
68
- } else {
69
- onKeyDown(event);
70
- }
71
- }
72
-
73
- let hasClear = !!(nullable && value && onChange);
74
-
75
- return (
76
- <div style={{ anchorName } as React.CSSProperties}>
77
- <div
78
- className="ObLookup-inputWrap"
79
- style={
80
- {
81
- display: fullWidth ? "block" : "inline-block",
82
- "--ObLookup-buttons-width": hasClear ? "5.5rem" : "3rem",
83
- } as React.CSSProperties
84
- }
85
- >
86
- <Textfield
87
- id={inputId}
88
- value={value ?? ""}
89
- label={label}
90
- onChange={onChange}
91
- onKeyDown={handleKeyDown}
92
- className={clsx(slotProps?.textField?.className, "ObLookup-input")}
93
- {...(slotProps?.textField as any)}
94
- style={{ width: fullWidth ? "100%" : undefined, ...slotProps?.textField?.style }}
95
- />
96
- <div className="ObLookup-buttons">
97
- {hasClear && (
98
- <Button
99
- icon
100
- variant="tertiary"
101
- data-size="sm"
102
- aria-label={getLocalizedString("Clear")}
103
- onKeyDown={(evt) => evt.stopPropagation()}
104
- onClick={(evt) => {
105
- evt.stopPropagation();
106
- applyValue(null);
107
- document.getElementById(inputId)?.focus();
108
- }}
109
- >
110
- <XMarkIcon />
111
- </Button>
112
- )}
113
- <Button
114
- icon
115
- variant="tertiary"
116
- data-size="sm"
117
- aria-label={getLocalizedString("Search")}
118
- onClick={(evt) => {
119
- evt.stopPropagation();
120
- openDialog();
121
- }}
122
- >
123
- <MagnifyingGlassIcon />
124
- </Button>
125
- </div>
126
- </div>
127
- {dialog}
128
- </div>
129
- );
130
- }
1
+ import "./Lookup.css";
2
+
3
+ import { Button, Textfield, type TextfieldProps } from "@digdir/designsystemet-react";
4
+ import { MagnifyingGlassIcon, XMarkIcon } from "@navikt/aksel-icons";
5
+ import type { DistributiveOmit } from "@olenbetong/appframe-react";
6
+
7
+ import clsx from "clsx";
8
+ import type React from "react";
9
+
10
+ import type { LookupComboboxProps } from "./LookupCombobox.js";
11
+ import { useLookup } from "./Lookup.js";
12
+ import { getLocalizedString } from "../i18n.js";
13
+
14
+ export type AfLookupEditProps<T extends Record<string, unknown>> = DistributiveOmit<
15
+ LookupComboboxProps<T>,
16
+ "value" | "onChange"
17
+ > & {
18
+ /**
19
+ * Allows the user to enter a value manually that might not be in the list.
20
+ */
21
+ editable?: boolean;
22
+ fullWidth?: boolean;
23
+ label?: string;
24
+ value?: string | null;
25
+ lookupField: keyof T;
26
+ onChange?: (evt: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
27
+ slotProps?: {
28
+ dialog?: Partial<React.HTMLProps<HTMLDialogElement>>;
29
+ textField?: Partial<TextfieldProps>;
30
+ };
31
+ };
32
+
33
+ export function AfLookupEdit<T extends Record<string, unknown>>({
34
+ fullWidth = false,
35
+ label,
36
+ onChange,
37
+ slotProps,
38
+ nullable,
39
+ lookupField,
40
+ value,
41
+ ...props
42
+ }: AfLookupEditProps<T>) {
43
+ let { inputId, dialog, anchorName, openDialog, onKeyDown } = useLookup<T>({
44
+ ...props,
45
+ nullable,
46
+ slotProps: { dialog: slotProps?.dialog },
47
+ onChange: (item) => applyValue(item),
48
+ });
49
+
50
+ // The popup selects a single value from the dataset: the item's lookupField
51
+ // is written to the input through the native value setter so the same
52
+ // change event fires whether the user types or picks from the list/grid.
53
+ function applyValue(item: T | null) {
54
+ let input = document.getElementById(inputId) as HTMLInputElement;
55
+ let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
56
+
57
+ if (input && nativeInputValueSetter) {
58
+ nativeInputValueSetter.call(input, item ? String(item[lookupField]) : "");
59
+ input.dispatchEvent(new Event("input", { bubbles: true }));
60
+ }
61
+ }
62
+
63
+ function handleKeyDown(event: React.KeyboardEvent<HTMLElement>) {
64
+ if (event.key === "Enter") {
65
+ event.preventDefault();
66
+ event.stopPropagation();
67
+ openDialog();
68
+ } else {
69
+ onKeyDown(event);
70
+ }
71
+ }
72
+
73
+ let hasClear = !!(nullable && value && onChange);
74
+
75
+ return (
76
+ <div style={{ anchorName } as React.CSSProperties}>
77
+ <div
78
+ className="ObLookup-inputWrap"
79
+ style={
80
+ {
81
+ display: fullWidth ? "block" : "inline-block",
82
+ "--ObLookup-buttons-width": hasClear ? "5.5rem" : "3rem",
83
+ } as React.CSSProperties
84
+ }
85
+ >
86
+ <Textfield
87
+ id={inputId}
88
+ value={value ?? ""}
89
+ label={label}
90
+ onChange={onChange}
91
+ onKeyDown={handleKeyDown}
92
+ className={clsx(slotProps?.textField?.className, "ObLookup-input")}
93
+ {...(slotProps?.textField as any)}
94
+ style={{ width: fullWidth ? "100%" : undefined, ...slotProps?.textField?.style }}
95
+ />
96
+ <div className="ObLookup-buttons">
97
+ {hasClear && (
98
+ <Button
99
+ icon
100
+ variant="tertiary"
101
+ data-size="sm"
102
+ aria-label={getLocalizedString("Clear")}
103
+ onKeyDown={(evt) => evt.stopPropagation()}
104
+ onClick={(evt) => {
105
+ evt.stopPropagation();
106
+ applyValue(null);
107
+ document.getElementById(inputId)?.focus();
108
+ }}
109
+ >
110
+ <XMarkIcon />
111
+ </Button>
112
+ )}
113
+ <Button
114
+ icon
115
+ variant="tertiary"
116
+ data-size="sm"
117
+ aria-label={getLocalizedString("Search")}
118
+ onClick={(evt) => {
119
+ evt.stopPropagation();
120
+ openDialog();
121
+ }}
122
+ >
123
+ <MagnifyingGlassIcon />
124
+ </Button>
125
+ </div>
126
+ </div>
127
+ {dialog}
128
+ </div>
129
+ );
130
+ }
@@ -1,145 +1,145 @@
1
- .ObLookupGrid-root {
2
- height: 100%;
3
- overflow: hidden;
4
- overscroll-behavior: contain;
5
- border: 1px solid var(--ds-color-neutral-border-subtle);
6
- }
7
-
8
- /* No text selection while a column is being dragged */
9
- .ObLookupGrid-root[data-resizing] {
10
- user-select: none;
11
- cursor: col-resize;
12
- }
13
-
14
- /*
15
- * The header does not scroll itself: the list is the only scroll container, and the header
16
- * row inside is moved sideways with a transform to follow it.
17
- */
18
- .ObLookupGrid-header {
19
- height: 40px;
20
- overflow: hidden;
21
- border-bottom: 1px solid var(--ds-color-neutral-border-default);
22
- background-color: var(--ds-color-neutral-background-tinted);
23
- font-weight: 600;
24
- }
25
-
26
- .ObLookupGrid-headerRow {
27
- display: grid;
28
- align-items: center;
29
- height: 100%;
30
- box-sizing: border-box;
31
- }
32
-
33
- .ObLookupGrid-headerCell {
34
- position: relative;
35
- display: flex;
36
- align-items: center;
37
- height: 100%;
38
- padding: 0 8px;
39
- overflow: hidden;
40
- }
41
-
42
- .ObLookupGrid-headerText {
43
- overflow: hidden;
44
- text-overflow: ellipsis;
45
- white-space: nowrap;
46
- }
47
-
48
- /* A sortable header: the whole cell text is the button, the arrow shows on hover and when sorted */
49
- .ObLookupGrid-sortButton {
50
- display: flex;
51
- align-items: center;
52
- gap: var(--ds-size-1, 0.25rem);
53
- min-width: 0;
54
- max-width: 100%;
55
- padding: 0;
56
- border: 0;
57
- background: none;
58
- color: inherit;
59
- font: inherit;
60
- cursor: pointer;
61
- text-align: start;
62
-
63
- .ObLookupGrid-sortIcon {
64
- flex-shrink: 0;
65
- font-size: 1.125em;
66
- opacity: 0;
67
- transition: opacity 0.15s ease-out;
68
- }
69
-
70
- &:hover .ObLookupGrid-sortIcon {
71
- opacity: 0.5;
72
- }
73
-
74
- &.sorted .ObLookupGrid-sortIcon {
75
- opacity: 1;
76
- }
77
- }
78
-
79
- /* The drag handle: a wide hit area at the end of the cell with a thin line drawn in it */
80
- .ObLookupGrid-resizer {
81
- position: absolute;
82
- inset-block: 0;
83
- inset-inline-end: 0;
84
- width: 9px;
85
- cursor: col-resize;
86
- touch-action: none;
87
-
88
- &::after {
89
- content: "";
90
- position: absolute;
91
- inset-block: 25%;
92
- inset-inline-end: 4px;
93
- width: 1px;
94
- background-color: var(--ds-color-neutral-border-default);
95
- }
96
-
97
- &:hover::after {
98
- inset-block: 15%;
99
- width: 2px;
100
- background-color: var(--ds-color-neutral-border-strong);
101
- }
102
- }
103
-
104
- /* Keeps the client width stable whether or not there are enough rows for a scrollbar */
105
- .ObLookupGrid-list {
106
- scrollbar-gutter: stable;
107
- }
108
-
109
- .ObLookupGrid-row {
110
- display: grid;
111
- align-items: center;
112
- box-sizing: border-box;
113
- cursor: pointer;
114
- border-bottom: 1px solid var(--ds-color-neutral-border-subtle);
115
-
116
- &.active {
117
- background-color: var(--ds-color-neutral-surface-active);
118
- }
119
-
120
- &:hover {
121
- background-color: var(--ds-color-neutral-surface-hover);
122
- }
123
- }
124
-
125
- .ObLookupGrid-cell {
126
- padding: 0 8px;
127
- height: 100%;
128
- display: flex;
129
- align-items: center;
130
- overflow: hidden;
131
- white-space: nowrap;
132
-
133
- &.active {
134
- outline: 2px solid var(--ds-color-neutral-border-strong);
135
- outline-offset: -2px;
136
- }
137
- }
138
-
139
- /* text-overflow does not apply to text directly inside a flex container, so it sits on this span */
140
- .ObLookupGrid-cellText {
141
- min-width: 0;
142
- overflow: hidden;
143
- text-overflow: ellipsis;
144
- white-space: nowrap;
145
- }
1
+ .ObLookupGrid-root {
2
+ height: 100%;
3
+ overflow: hidden;
4
+ overscroll-behavior: contain;
5
+ border: 1px solid var(--ds-color-neutral-border-subtle);
6
+ }
7
+
8
+ /* No text selection while a column is being dragged */
9
+ .ObLookupGrid-root[data-resizing] {
10
+ user-select: none;
11
+ cursor: col-resize;
12
+ }
13
+
14
+ /*
15
+ * The header does not scroll itself: the list is the only scroll container, and the header
16
+ * row inside is moved sideways with a transform to follow it.
17
+ */
18
+ .ObLookupGrid-header {
19
+ height: 40px;
20
+ overflow: hidden;
21
+ border-bottom: 1px solid var(--ds-color-neutral-border-default);
22
+ background-color: var(--ds-color-neutral-background-tinted);
23
+ font-weight: 600;
24
+ }
25
+
26
+ .ObLookupGrid-headerRow {
27
+ display: grid;
28
+ align-items: center;
29
+ height: 100%;
30
+ box-sizing: border-box;
31
+ }
32
+
33
+ .ObLookupGrid-headerCell {
34
+ position: relative;
35
+ display: flex;
36
+ align-items: center;
37
+ height: 100%;
38
+ padding: 0 8px;
39
+ overflow: hidden;
40
+ }
41
+
42
+ .ObLookupGrid-headerText {
43
+ overflow: hidden;
44
+ text-overflow: ellipsis;
45
+ white-space: nowrap;
46
+ }
47
+
48
+ /* A sortable header: the whole cell text is the button, the arrow shows on hover and when sorted */
49
+ .ObLookupGrid-sortButton {
50
+ display: flex;
51
+ align-items: center;
52
+ gap: var(--ds-size-1, 0.25rem);
53
+ min-width: 0;
54
+ max-width: 100%;
55
+ padding: 0;
56
+ border: 0;
57
+ background: none;
58
+ color: inherit;
59
+ font: inherit;
60
+ cursor: pointer;
61
+ text-align: start;
62
+
63
+ .ObLookupGrid-sortIcon {
64
+ flex-shrink: 0;
65
+ font-size: 1.125em;
66
+ opacity: 0;
67
+ transition: opacity 0.15s ease-out;
68
+ }
69
+
70
+ &:hover .ObLookupGrid-sortIcon {
71
+ opacity: 0.5;
72
+ }
73
+
74
+ &.sorted .ObLookupGrid-sortIcon {
75
+ opacity: 1;
76
+ }
77
+ }
78
+
79
+ /* The drag handle: a wide hit area at the end of the cell with a thin line drawn in it */
80
+ .ObLookupGrid-resizer {
81
+ position: absolute;
82
+ inset-block: 0;
83
+ inset-inline-end: 0;
84
+ width: 9px;
85
+ cursor: col-resize;
86
+ touch-action: none;
87
+
88
+ &::after {
89
+ content: "";
90
+ position: absolute;
91
+ inset-block: 25%;
92
+ inset-inline-end: 4px;
93
+ width: 1px;
94
+ background-color: var(--ds-color-neutral-border-default);
95
+ }
96
+
97
+ &:hover::after {
98
+ inset-block: 15%;
99
+ width: 2px;
100
+ background-color: var(--ds-color-neutral-border-strong);
101
+ }
102
+ }
103
+
104
+ /* Keeps the client width stable whether or not there are enough rows for a scrollbar */
105
+ .ObLookupGrid-list {
106
+ scrollbar-gutter: stable;
107
+ }
108
+
109
+ .ObLookupGrid-row {
110
+ display: grid;
111
+ align-items: center;
112
+ box-sizing: border-box;
113
+ cursor: pointer;
114
+ border-bottom: 1px solid var(--ds-color-neutral-border-subtle);
115
+
116
+ &.active {
117
+ background-color: var(--ds-color-neutral-surface-active);
118
+ }
119
+
120
+ &:hover {
121
+ background-color: var(--ds-color-neutral-surface-hover);
122
+ }
123
+ }
124
+
125
+ .ObLookupGrid-cell {
126
+ padding: 0 8px;
127
+ height: 100%;
128
+ display: flex;
129
+ align-items: center;
130
+ overflow: hidden;
131
+ white-space: nowrap;
132
+
133
+ &.active {
134
+ outline: 2px solid var(--ds-color-neutral-border-strong);
135
+ outline-offset: -2px;
136
+ }
137
+ }
138
+
139
+ /* text-overflow does not apply to text directly inside a flex container, so it sits on this span */
140
+ .ObLookupGrid-cellText {
141
+ min-width: 0;
142
+ overflow: hidden;
143
+ text-overflow: ellipsis;
144
+ white-space: nowrap;
145
+ }