@navikt/ds-react 7.3.1 → 7.4.1
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/cjs/date/utils/parse-date.js +1 -0
- package/cjs/date/utils/parse-date.js.map +1 -1
- package/cjs/expansion-card/ExpansionCardHeader.js +4 -2
- package/cjs/expansion-card/ExpansionCardHeader.js.map +1 -1
- package/cjs/form/checkbox/Checkbox.js +1 -1
- package/cjs/form/checkbox/Checkbox.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/AddNewOption.js +4 -4
- package/cjs/form/combobox/FilteredOptions/AddNewOption.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/FilteredOptionsItem.js +15 -2
- package/cjs/form/combobox/FilteredOptions/FilteredOptionsItem.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +2 -2
- package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js +12 -11
- package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
- package/cjs/form/combobox/Input/Input.js +23 -10
- package/cjs/form/combobox/Input/Input.js.map +1 -1
- package/cjs/form/search/Search.d.ts +1 -2
- package/cjs/form/search/Search.js +20 -20
- package/cjs/form/search/Search.js.map +1 -1
- package/cjs/form/search/SearchButton.js +5 -1
- package/cjs/form/search/SearchButton.js.map +1 -1
- package/cjs/loader/Loader.d.ts +2 -2
- package/cjs/loader/Loader.js +5 -3
- package/cjs/loader/Loader.js.map +1 -1
- package/cjs/modal/ModalHeader.js +3 -1
- package/cjs/modal/ModalHeader.js.map +1 -1
- package/cjs/pagination/Pagination.js +4 -2
- package/cjs/pagination/Pagination.js.map +1 -1
- package/cjs/progress-bar/ProgressBar.js +16 -7
- package/cjs/progress-bar/ProgressBar.js.map +1 -1
- package/cjs/slot/Slot.js +4 -3
- package/cjs/slot/Slot.js.map +1 -1
- package/cjs/table/ExpandableRow.d.ts +1 -1
- package/cjs/table/ExpandableRow.js +11 -7
- package/cjs/table/ExpandableRow.js.map +1 -1
- package/cjs/tabs/parts/tablist/ScrollButtons.js +1 -1
- package/cjs/tabs/parts/tablist/ScrollButtons.js.map +1 -1
- package/cjs/util/i18n/locales/en.d.ts +22 -0
- package/cjs/util/i18n/locales/en.js +22 -0
- package/cjs/util/i18n/locales/en.js.map +1 -1
- package/cjs/util/i18n/locales/nb.d.ts +22 -0
- package/cjs/util/i18n/locales/nb.js +22 -0
- package/cjs/util/i18n/locales/nb.js.map +1 -1
- package/cjs/util/i18n/locales/nn.d.ts +22 -0
- package/cjs/util/i18n/locales/nn.js +22 -0
- package/cjs/util/i18n/locales/nn.js.map +1 -1
- package/esm/date/utils/parse-date.js +1 -0
- package/esm/date/utils/parse-date.js.map +1 -1
- package/esm/expansion-card/ExpansionCardHeader.js +4 -2
- package/esm/expansion-card/ExpansionCardHeader.js.map +1 -1
- package/esm/form/checkbox/Checkbox.js +1 -1
- package/esm/form/checkbox/Checkbox.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/AddNewOption.js +4 -4
- package/esm/form/combobox/FilteredOptions/AddNewOption.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/FilteredOptionsItem.js +15 -2
- package/esm/form/combobox/FilteredOptions/FilteredOptionsItem.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +2 -2
- package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/useVirtualFocus.js +13 -12
- package/esm/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
- package/esm/form/combobox/Input/Input.js +23 -10
- package/esm/form/combobox/Input/Input.js.map +1 -1
- package/esm/form/search/Search.d.ts +1 -2
- package/esm/form/search/Search.js +21 -21
- package/esm/form/search/Search.js.map +1 -1
- package/esm/form/search/SearchButton.js +5 -1
- package/esm/form/search/SearchButton.js.map +1 -1
- package/esm/loader/Loader.d.ts +2 -2
- package/esm/loader/Loader.js +5 -3
- package/esm/loader/Loader.js.map +1 -1
- package/esm/modal/ModalHeader.js +3 -1
- package/esm/modal/ModalHeader.js.map +1 -1
- package/esm/pagination/Pagination.js +4 -2
- package/esm/pagination/Pagination.js.map +1 -1
- package/esm/progress-bar/ProgressBar.js +17 -8
- package/esm/progress-bar/ProgressBar.js.map +1 -1
- package/esm/slot/Slot.js +4 -3
- package/esm/slot/Slot.js.map +1 -1
- package/esm/table/ExpandableRow.d.ts +1 -1
- package/esm/table/ExpandableRow.js +11 -7
- package/esm/table/ExpandableRow.js.map +1 -1
- package/esm/tabs/parts/tablist/ScrollButtons.js +1 -1
- package/esm/tabs/parts/tablist/ScrollButtons.js.map +1 -1
- package/esm/util/i18n/locales/en.d.ts +22 -0
- package/esm/util/i18n/locales/en.js +22 -0
- package/esm/util/i18n/locales/en.js.map +1 -1
- package/esm/util/i18n/locales/nb.d.ts +22 -0
- package/esm/util/i18n/locales/nb.js +22 -0
- package/esm/util/i18n/locales/nb.js.map +1 -1
- package/esm/util/i18n/locales/nn.d.ts +22 -0
- package/esm/util/i18n/locales/nn.js +22 -0
- package/esm/util/i18n/locales/nn.js.map +1 -1
- package/package.json +4 -4
- package/src/date/utils/parse-date.ts +1 -0
- package/src/expansion-card/ExpansionCardHeader.tsx +4 -2
- package/src/form/checkbox/Checkbox.tsx +0 -1
- package/src/form/combobox/FilteredOptions/AddNewOption.tsx +4 -4
- package/src/form/combobox/FilteredOptions/FilteredOptionsItem.tsx +22 -1
- package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +3 -3
- package/src/form/combobox/FilteredOptions/useVirtualFocus.ts +13 -12
- package/src/form/combobox/Input/Input.tsx +26 -15
- package/src/form/combobox/__tests__/combobox.test.tsx +39 -0
- package/src/form/search/Search.tsx +20 -36
- package/src/form/search/SearchButton.tsx +5 -1
- package/src/loader/Loader.tsx +8 -4
- package/src/modal/ModalHeader.tsx +3 -1
- package/src/pagination/Pagination.tsx +6 -4
- package/src/progress-bar/ProgressBar.tsx +21 -14
- package/src/slot/Slot.tsx +8 -3
- package/src/table/ExpandableRow.tsx +12 -13
- package/src/tabs/parts/tablist/ScrollButtons.tsx +1 -5
- package/src/util/i18n/locales/en.ts +24 -0
- package/src/util/i18n/locales/nb.ts +24 -0
- package/src/util/i18n/locales/nn.ts +24 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useState } from "react";
|
|
2
2
|
|
|
3
3
|
export type VirtualFocusType = {
|
|
4
4
|
activeElement: HTMLElement | undefined;
|
|
@@ -43,6 +43,11 @@ const useVirtualFocus = (
|
|
|
43
43
|
: false;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
const setActiveAndScrollToElement = (element?: HTMLElement) => {
|
|
47
|
+
setActiveElement(element);
|
|
48
|
+
element?.scrollIntoView?.({ block: "center" });
|
|
49
|
+
};
|
|
50
|
+
|
|
46
51
|
const moveFocusUp = () => {
|
|
47
52
|
if (!activeElement) {
|
|
48
53
|
return;
|
|
@@ -53,14 +58,14 @@ const useVirtualFocus = (
|
|
|
53
58
|
if (_currentIndex === 0) {
|
|
54
59
|
setActiveElement(undefined);
|
|
55
60
|
} else {
|
|
56
|
-
|
|
61
|
+
setActiveAndScrollToElement(elementAbove);
|
|
57
62
|
}
|
|
58
63
|
};
|
|
59
64
|
|
|
60
65
|
const moveFocusDown = () => {
|
|
61
66
|
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
|
|
62
67
|
if (!activeElement) {
|
|
63
|
-
|
|
68
|
+
setActiveAndScrollToElement(elementsAbleToReceiveFocus[0]);
|
|
64
69
|
return;
|
|
65
70
|
}
|
|
66
71
|
const _currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement);
|
|
@@ -68,17 +73,17 @@ const useVirtualFocus = (
|
|
|
68
73
|
return;
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
|
|
76
|
+
setActiveAndScrollToElement(elementsAbleToReceiveFocus[_currentIndex + 1]);
|
|
72
77
|
};
|
|
73
78
|
|
|
74
79
|
const resetFocus = () => setActiveElement(undefined);
|
|
75
80
|
const moveFocusToTop = () => {
|
|
76
81
|
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
|
|
77
|
-
|
|
82
|
+
setActiveAndScrollToElement(elementsAbleToReceiveFocus[0]);
|
|
78
83
|
};
|
|
79
84
|
const moveFocusToBottom = () => {
|
|
80
85
|
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
|
|
81
|
-
|
|
86
|
+
setActiveAndScrollToElement(
|
|
82
87
|
elementsAbleToReceiveFocus[elementsAbleToReceiveFocus.length - 1],
|
|
83
88
|
);
|
|
84
89
|
};
|
|
@@ -98,7 +103,7 @@ const useVirtualFocus = (
|
|
|
98
103
|
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
|
|
99
104
|
const currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement);
|
|
100
105
|
const newIndex = Math.max(currentIndex - numberOfElements, 0);
|
|
101
|
-
|
|
106
|
+
setActiveAndScrollToElement(elementsAbleToReceiveFocus[newIndex]);
|
|
102
107
|
};
|
|
103
108
|
|
|
104
109
|
const moveFocusDownBy = (numberOfElements: number) => {
|
|
@@ -110,13 +115,9 @@ const useVirtualFocus = (
|
|
|
110
115
|
currentIndex + numberOfElements,
|
|
111
116
|
elementsAbleToReceiveFocus.length - 1,
|
|
112
117
|
);
|
|
113
|
-
|
|
118
|
+
setActiveAndScrollToElement(elementsAbleToReceiveFocus[newIndex]);
|
|
114
119
|
};
|
|
115
120
|
|
|
116
|
-
useEffect(() => {
|
|
117
|
-
activeElement?.scrollIntoView?.({ block: "nearest" });
|
|
118
|
-
}, [activeElement]);
|
|
119
|
-
|
|
120
121
|
return {
|
|
121
122
|
activeElement,
|
|
122
123
|
getElementById,
|
|
@@ -11,6 +11,7 @@ import { useMergeRefs } from "../../../util/hooks";
|
|
|
11
11
|
import filteredOptionsUtil from "../FilteredOptions/filtered-options-util";
|
|
12
12
|
import { useFilteredOptionsContext } from "../FilteredOptions/filteredOptionsContext";
|
|
13
13
|
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
|
|
14
|
+
import { ComboboxOption } from "../types";
|
|
14
15
|
import { useInputContext } from "./Input.context";
|
|
15
16
|
|
|
16
17
|
interface InputProps
|
|
@@ -93,19 +94,23 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
93
94
|
clearInput(event);
|
|
94
95
|
} else if ((allowNewValues || shouldAutocomplete) && value !== "") {
|
|
95
96
|
event.preventDefault();
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
|
|
98
|
+
const autoCompletedOption =
|
|
99
|
+
filteredOptionsUtil.getFirstValueStartingWith(
|
|
100
|
+
value,
|
|
101
|
+
filteredOptions,
|
|
102
|
+
);
|
|
103
|
+
let selectedValue: ComboboxOption | undefined;
|
|
104
|
+
|
|
105
|
+
if (shouldAutocomplete && autoCompletedOption) {
|
|
106
|
+
selectedValue = autoCompletedOption;
|
|
107
|
+
} else if (allowNewValues && isValueNew) {
|
|
108
|
+
selectedValue = { label: value, value };
|
|
109
|
+
}
|
|
104
110
|
|
|
105
111
|
if (!selectedValue) {
|
|
106
112
|
return;
|
|
107
113
|
}
|
|
108
|
-
|
|
109
114
|
toggleOption(selectedValue, event);
|
|
110
115
|
if (!isMultiSelect && !isTextInSelectedOptions(selectedValue.label)) {
|
|
111
116
|
toggleIsListOpen(false);
|
|
@@ -177,10 +182,12 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
177
182
|
if (value !== searchTerm) {
|
|
178
183
|
setValue(searchTerm);
|
|
179
184
|
}
|
|
180
|
-
if (
|
|
185
|
+
if (!isListOpen) {
|
|
181
186
|
toggleIsListOpen(true);
|
|
187
|
+
setTimeout(virtualFocus.moveFocusDown, 0); // Wait until list is visible so that scrollIntoView works
|
|
188
|
+
} else {
|
|
189
|
+
virtualFocus.moveFocusDown();
|
|
182
190
|
}
|
|
183
|
-
virtualFocus.moveFocusDown();
|
|
184
191
|
} else if (e.key === "ArrowUp") {
|
|
185
192
|
if (value !== "" && value !== searchTerm) {
|
|
186
193
|
onChange(value);
|
|
@@ -199,19 +206,23 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
199
206
|
virtualFocus.moveFocusToTop();
|
|
200
207
|
} else if (e.key === "End") {
|
|
201
208
|
e.preventDefault();
|
|
202
|
-
if (
|
|
209
|
+
if (!isListOpen) {
|
|
203
210
|
toggleIsListOpen(true);
|
|
211
|
+
setTimeout(virtualFocus.moveFocusToBottom, 0); // Wait until list is visible so that scrollIntoView works
|
|
212
|
+
} else {
|
|
213
|
+
virtualFocus.moveFocusToBottom();
|
|
204
214
|
}
|
|
205
|
-
virtualFocus.moveFocusToBottom();
|
|
206
215
|
} else if (e.key === "PageUp") {
|
|
207
216
|
e.preventDefault();
|
|
208
217
|
virtualFocus.moveFocusUpBy(6);
|
|
209
218
|
} else if (e.key === "PageDown") {
|
|
210
219
|
e.preventDefault();
|
|
211
|
-
if (
|
|
220
|
+
if (!isListOpen) {
|
|
212
221
|
toggleIsListOpen(true);
|
|
222
|
+
setTimeout(() => virtualFocus.moveFocusDownBy(6), 0); // Wait until list is visible so that scrollIntoView works
|
|
223
|
+
} else {
|
|
224
|
+
virtualFocus.moveFocusDownBy(6);
|
|
213
225
|
}
|
|
214
|
-
virtualFocus.moveFocusDownBy(6);
|
|
215
226
|
}
|
|
216
227
|
},
|
|
217
228
|
[
|
|
@@ -290,6 +290,45 @@ describe("Render combobox", () => {
|
|
|
290
290
|
false,
|
|
291
291
|
);
|
|
292
292
|
});
|
|
293
|
+
|
|
294
|
+
test("and pressing enter to select autocompleted word will select existing word when addNewOptions is true", async () => {
|
|
295
|
+
const onToggleSelected = vi.fn();
|
|
296
|
+
render(
|
|
297
|
+
<App
|
|
298
|
+
onToggleSelected={onToggleSelected}
|
|
299
|
+
options={options.map((opt) => ({
|
|
300
|
+
label: `${opt} (${opt})`,
|
|
301
|
+
value: opt,
|
|
302
|
+
}))}
|
|
303
|
+
shouldAutocomplete
|
|
304
|
+
allowNewValues
|
|
305
|
+
/>,
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const combobox = screen.getByRole("combobox", {
|
|
309
|
+
name: "Hva er dine favorittfrukter?",
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
await act(async () => {
|
|
313
|
+
await userEvent.click(combobox);
|
|
314
|
+
|
|
315
|
+
await userEvent.type(combobox, "p");
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
expect(combobox.getAttribute("value")).toBe(
|
|
319
|
+
"passion fruit (passion fruit)",
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
await act(async () => {
|
|
323
|
+
await userEvent.keyboard("{Enter}");
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
expect(onToggleSelected).toHaveBeenCalledWith(
|
|
327
|
+
"passion fruit",
|
|
328
|
+
true,
|
|
329
|
+
false,
|
|
330
|
+
);
|
|
331
|
+
});
|
|
293
332
|
});
|
|
294
333
|
|
|
295
334
|
describe("has keyboard navigation", () => {
|
|
@@ -2,7 +2,6 @@ import cl from "clsx";
|
|
|
2
2
|
import React, {
|
|
3
3
|
InputHTMLAttributes,
|
|
4
4
|
forwardRef,
|
|
5
|
-
useCallback,
|
|
6
5
|
useRef,
|
|
7
6
|
useState,
|
|
8
7
|
} from "react";
|
|
@@ -10,6 +9,7 @@ import { MagnifyingGlassIcon, XMarkIcon } from "@navikt/aksel-icons";
|
|
|
10
9
|
import { BodyShort, ErrorMessage, Label } from "../../typography";
|
|
11
10
|
import { omit } from "../../util";
|
|
12
11
|
import { useMergeRefs } from "../../util/hooks/useMergeRefs";
|
|
12
|
+
import { useI18n } from "../../util/i18n/i18n.context";
|
|
13
13
|
import { FormFieldProps, useFormField } from "../useFormField";
|
|
14
14
|
import SearchButton, { SearchButtonType } from "./SearchButton";
|
|
15
15
|
import { SearchContext } from "./context";
|
|
@@ -68,13 +68,9 @@ export interface SearchProps
|
|
|
68
68
|
*/
|
|
69
69
|
variant?: "primary" | "secondary" | "simple";
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
71
|
+
* HTML size attribute. Specifies the width of the input, in characters.
|
|
72
72
|
*/
|
|
73
73
|
htmlSize?: number | string;
|
|
74
|
-
/*
|
|
75
|
-
* Exposes role attribute.
|
|
76
|
-
*/
|
|
77
|
-
role?: string;
|
|
78
74
|
}
|
|
79
75
|
|
|
80
76
|
interface SearchComponent
|
|
@@ -123,31 +119,24 @@ export const Search = forwardRef<HTMLInputElement, SearchProps>(
|
|
|
123
119
|
onChange,
|
|
124
120
|
onSearchClick,
|
|
125
121
|
htmlSize,
|
|
126
|
-
role,
|
|
127
122
|
...rest
|
|
128
123
|
} = props;
|
|
129
124
|
|
|
130
125
|
const searchRef = useRef<HTMLInputElement | null>(null);
|
|
131
126
|
const mergedRef = useMergeRefs(searchRef, ref);
|
|
132
|
-
|
|
127
|
+
const translate = useI18n("Search");
|
|
133
128
|
const [internalValue, setInternalValue] = useState(defaultValue ?? "");
|
|
134
129
|
|
|
135
|
-
const handleChange =
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
},
|
|
140
|
-
[onChange, value],
|
|
141
|
-
);
|
|
130
|
+
const handleChange = (newValue: string) => {
|
|
131
|
+
value === undefined && setInternalValue(newValue);
|
|
132
|
+
onChange?.(newValue);
|
|
133
|
+
};
|
|
142
134
|
|
|
143
|
-
const handleClear =
|
|
144
|
-
(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
},
|
|
149
|
-
[handleChange, onClear],
|
|
150
|
-
);
|
|
135
|
+
const handleClear = (clearEvent: SearchClearEvent) => {
|
|
136
|
+
onClear?.(clearEvent);
|
|
137
|
+
handleChange("");
|
|
138
|
+
searchRef.current?.focus?.();
|
|
139
|
+
};
|
|
151
140
|
|
|
152
141
|
const handleClick = () => {
|
|
153
142
|
onSearchClick?.(`${value ?? internalValue}`);
|
|
@@ -156,26 +145,22 @@ export const Search = forwardRef<HTMLInputElement, SearchProps>(
|
|
|
156
145
|
return (
|
|
157
146
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
158
147
|
<div
|
|
159
|
-
onKeyDown={(
|
|
160
|
-
if (
|
|
148
|
+
onKeyDown={(event) => {
|
|
149
|
+
if (event.key !== "Escape") {
|
|
161
150
|
return;
|
|
162
151
|
}
|
|
163
|
-
searchRef.current?.value &&
|
|
164
|
-
|
|
165
|
-
e.preventDefault();
|
|
166
|
-
|
|
167
|
-
handleClear({ trigger: "Escape", event: e });
|
|
152
|
+
searchRef.current?.value && event.preventDefault();
|
|
153
|
+
handleClear({ trigger: "Escape", event });
|
|
168
154
|
}}
|
|
169
155
|
className={cl(
|
|
170
156
|
className,
|
|
171
157
|
"navds-form-field",
|
|
172
158
|
`navds-form-field--${size}`,
|
|
173
159
|
"navds-search",
|
|
174
|
-
|
|
175
160
|
{
|
|
176
161
|
"navds-search--error": hasError,
|
|
177
|
-
"navds-search--disabled":
|
|
178
|
-
"navds-search--with-size":
|
|
162
|
+
"navds-search--disabled": inputProps.disabled,
|
|
163
|
+
"navds-search--with-size": htmlSize,
|
|
179
164
|
},
|
|
180
165
|
)}
|
|
181
166
|
>
|
|
@@ -215,7 +200,6 @@ export const Search = forwardRef<HTMLInputElement, SearchProps>(
|
|
|
215
200
|
value={value ?? internalValue}
|
|
216
201
|
onChange={(e) => handleChange(e.target.value)}
|
|
217
202
|
type="search"
|
|
218
|
-
role={role ?? "searchbox"}
|
|
219
203
|
className={cl(
|
|
220
204
|
className,
|
|
221
205
|
"navds-search__input",
|
|
@@ -229,11 +213,11 @@ export const Search = forwardRef<HTMLInputElement, SearchProps>(
|
|
|
229
213
|
{(value ?? internalValue) && clearButton && (
|
|
230
214
|
<button
|
|
231
215
|
type="button"
|
|
232
|
-
onClick={(
|
|
216
|
+
onClick={(event) => handleClear({ trigger: "Click", event })}
|
|
233
217
|
className="navds-search__button-clear"
|
|
234
218
|
>
|
|
235
219
|
<span className="navds-sr-only">
|
|
236
|
-
{clearButtonLabel
|
|
220
|
+
{clearButtonLabel || translate("clear")}
|
|
237
221
|
</span>
|
|
238
222
|
<XMarkIcon aria-hidden />
|
|
239
223
|
</button>
|
|
@@ -3,6 +3,7 @@ import React, { forwardRef, useContext } from "react";
|
|
|
3
3
|
import { MagnifyingGlassIcon } from "@navikt/aksel-icons";
|
|
4
4
|
import { Button, ButtonProps } from "../../button";
|
|
5
5
|
import { composeEventHandlers } from "../../util/composeEventHandlers";
|
|
6
|
+
import { useI18n } from "../../util/i18n/i18n.context";
|
|
6
7
|
import { SearchContext } from "./context";
|
|
7
8
|
|
|
8
9
|
export interface SearchButtonProps
|
|
@@ -19,6 +20,7 @@ export type SearchButtonType = React.ForwardRefExoticComponent<
|
|
|
19
20
|
|
|
20
21
|
const SearchButton: SearchButtonType = forwardRef(
|
|
21
22
|
({ className, children, disabled, onClick, ...rest }, ref) => {
|
|
23
|
+
const translate = useI18n("Search");
|
|
22
24
|
const context = useContext(SearchContext);
|
|
23
25
|
|
|
24
26
|
if (context === null) {
|
|
@@ -40,7 +42,9 @@ const SearchButton: SearchButtonType = forwardRef(
|
|
|
40
42
|
onClick={composeEventHandlers(onClick, handleClick)}
|
|
41
43
|
icon={
|
|
42
44
|
<MagnifyingGlassIcon
|
|
43
|
-
{...(children
|
|
45
|
+
{...(children
|
|
46
|
+
? { "aria-hidden": true }
|
|
47
|
+
: { title: translate("search") })}
|
|
44
48
|
/>
|
|
45
49
|
}
|
|
46
50
|
>
|
package/src/loader/Loader.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import cl from "clsx";
|
|
|
2
2
|
import React, { SVGProps, forwardRef } from "react";
|
|
3
3
|
import { omit } from "../util";
|
|
4
4
|
import { useId } from "../util/hooks";
|
|
5
|
+
import { useI18n } from "../util/i18n/i18n.context";
|
|
5
6
|
|
|
6
7
|
export interface LoaderProps extends Omit<SVGProps<SVGSVGElement>, "ref"> {
|
|
7
8
|
/**
|
|
@@ -19,7 +20,7 @@ export interface LoaderProps extends Omit<SVGProps<SVGSVGElement>, "ref"> {
|
|
|
19
20
|
| "xsmall";
|
|
20
21
|
/**
|
|
21
22
|
* Title prop on svg
|
|
22
|
-
* @default "
|
|
23
|
+
* @default "Venter…"
|
|
23
24
|
*/
|
|
24
25
|
title?: React.ReactNode;
|
|
25
26
|
/**
|
|
@@ -47,7 +48,7 @@ export type LoaderType = React.ForwardRefExoticComponent<
|
|
|
47
48
|
*
|
|
48
49
|
* @example
|
|
49
50
|
* ```jsx
|
|
50
|
-
* <Loader size="3xlarge" title="Venter
|
|
51
|
+
* <Loader size="3xlarge" title="Venter…" />
|
|
51
52
|
* ```
|
|
52
53
|
*/
|
|
53
54
|
export const Loader: LoaderType = forwardRef<SVGSVGElement, LoaderProps>(
|
|
@@ -55,7 +56,7 @@ export const Loader: LoaderType = forwardRef<SVGSVGElement, LoaderProps>(
|
|
|
55
56
|
{
|
|
56
57
|
className,
|
|
57
58
|
size = "medium",
|
|
58
|
-
title
|
|
59
|
+
title,
|
|
59
60
|
transparent = false,
|
|
60
61
|
variant = "neutral",
|
|
61
62
|
id,
|
|
@@ -64,6 +65,7 @@ export const Loader: LoaderType = forwardRef<SVGSVGElement, LoaderProps>(
|
|
|
64
65
|
ref,
|
|
65
66
|
) => {
|
|
66
67
|
const internalId = useId();
|
|
68
|
+
const translate = useI18n("Loader");
|
|
67
69
|
|
|
68
70
|
return (
|
|
69
71
|
<svg
|
|
@@ -83,7 +85,9 @@ export const Loader: LoaderType = forwardRef<SVGSVGElement, LoaderProps>(
|
|
|
83
85
|
preserveAspectRatio="xMidYMid"
|
|
84
86
|
{...omit(rest, ["children"])}
|
|
85
87
|
>
|
|
86
|
-
<title id={id ?? `loader-${internalId}`}>
|
|
88
|
+
<title id={id ?? `loader-${internalId}`}>
|
|
89
|
+
{title || translate("title")}
|
|
90
|
+
</title>
|
|
87
91
|
<circle
|
|
88
92
|
className="navds-loader__background"
|
|
89
93
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -2,6 +2,7 @@ import cl from "clsx";
|
|
|
2
2
|
import React, { forwardRef } from "react";
|
|
3
3
|
import { XMarkIcon } from "@navikt/aksel-icons";
|
|
4
4
|
import { Button } from "../button";
|
|
5
|
+
import { useI18n } from "../util/i18n/i18n.context";
|
|
5
6
|
import { useModalContext } from "./Modal.context";
|
|
6
7
|
|
|
7
8
|
export interface ModalHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -16,6 +17,7 @@ export interface ModalHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
16
17
|
const ModalHeader = forwardRef<HTMLDivElement, ModalHeaderProps>(
|
|
17
18
|
({ children, className, closeButton = true, ...rest }, ref) => {
|
|
18
19
|
const context = useModalContext();
|
|
20
|
+
const translate = useI18n("Modal");
|
|
19
21
|
|
|
20
22
|
return (
|
|
21
23
|
<div {...rest} ref={ref} className={cl("navds-modal__header", className)}>
|
|
@@ -32,7 +34,7 @@ const ModalHeader = forwardRef<HTMLDivElement, ModalHeaderProps>(
|
|
|
32
34
|
}
|
|
33
35
|
}}
|
|
34
36
|
onClick={context.closeHandler}
|
|
35
|
-
icon={<XMarkIcon title="
|
|
37
|
+
icon={<XMarkIcon title={translate("close")} />}
|
|
36
38
|
/>
|
|
37
39
|
)}
|
|
38
40
|
{children}
|
|
@@ -3,6 +3,7 @@ import React, { forwardRef } from "react";
|
|
|
3
3
|
import { ChevronLeftIcon, ChevronRightIcon } from "@navikt/aksel-icons";
|
|
4
4
|
import { BodyShort, Heading } from "../typography";
|
|
5
5
|
import { useId } from "../util";
|
|
6
|
+
import { useI18n } from "../util/i18n/i18n.context";
|
|
6
7
|
import PaginationItem, {
|
|
7
8
|
PaginationItemProps,
|
|
8
9
|
PaginationItemType,
|
|
@@ -138,6 +139,7 @@ export const Pagination = forwardRef<HTMLElement, PaginationProps>(
|
|
|
138
139
|
ref,
|
|
139
140
|
) => {
|
|
140
141
|
const headingId = useId();
|
|
142
|
+
const translate = useI18n("Pagination");
|
|
141
143
|
|
|
142
144
|
if (page < 1) {
|
|
143
145
|
console.error("page cannot be less than 1");
|
|
@@ -194,11 +196,11 @@ export const Pagination = forwardRef<HTMLElement, PaginationProps>(
|
|
|
194
196
|
<ChevronLeftIcon
|
|
195
197
|
{...(prevNextTexts
|
|
196
198
|
? { "aria-hidden": true }
|
|
197
|
-
: { title: "
|
|
199
|
+
: { title: translate("previous") })}
|
|
198
200
|
/>
|
|
199
201
|
}
|
|
200
202
|
>
|
|
201
|
-
{prevNextTexts &&
|
|
203
|
+
{prevNextTexts && translate("previous")}
|
|
202
204
|
</Item>
|
|
203
205
|
</li>
|
|
204
206
|
{getSteps({ page, count, siblingCount, boundaryCount }).map(
|
|
@@ -241,12 +243,12 @@ export const Pagination = forwardRef<HTMLElement, PaginationProps>(
|
|
|
241
243
|
<ChevronRightIcon
|
|
242
244
|
{...(prevNextTexts
|
|
243
245
|
? { "aria-hidden": true }
|
|
244
|
-
: { title: "
|
|
246
|
+
: { title: translate("next") })}
|
|
245
247
|
/>
|
|
246
248
|
}
|
|
247
249
|
iconPosition="right"
|
|
248
250
|
>
|
|
249
|
-
{prevNextTexts &&
|
|
251
|
+
{prevNextTexts && translate("next")}
|
|
250
252
|
</Item>
|
|
251
253
|
</li>
|
|
252
254
|
</ul>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import cl from "clsx";
|
|
2
|
-
import React, { HTMLAttributes, forwardRef, useRef } from "react";
|
|
2
|
+
import React, { HTMLAttributes, forwardRef, useEffect, useRef } from "react";
|
|
3
|
+
import { useI18n } from "../util/i18n/i18n.context";
|
|
3
4
|
|
|
4
5
|
interface ProgressBarPropsBase
|
|
5
6
|
extends Omit<HTMLAttributes<HTMLDivElement>, "role"> {
|
|
@@ -92,11 +93,12 @@ export const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(
|
|
|
92
93
|
},
|
|
93
94
|
ref,
|
|
94
95
|
) => {
|
|
95
|
-
const
|
|
96
|
+
const translateX = 100 - (Math.round(value) / valueMax) * 100;
|
|
96
97
|
const onTimeoutRef = useRef<() => void>();
|
|
97
98
|
onTimeoutRef.current = simulated?.onTimeout;
|
|
99
|
+
const translate = useI18n("ProgressBar");
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
useEffect(() => {
|
|
100
102
|
if (simulated?.seconds && onTimeoutRef.current) {
|
|
101
103
|
const timeout = setTimeout(
|
|
102
104
|
onTimeoutRef.current,
|
|
@@ -119,8 +121,15 @@ export const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(
|
|
|
119
121
|
aria-valuenow={simulated?.seconds ? 0 : Math.round(value)}
|
|
120
122
|
aria-valuetext={
|
|
121
123
|
simulated?.seconds
|
|
122
|
-
?
|
|
123
|
-
|
|
124
|
+
? translate("progressUnknown", {
|
|
125
|
+
replacements: { seconds: Math.round(simulated?.seconds) },
|
|
126
|
+
})
|
|
127
|
+
: translate("progress", {
|
|
128
|
+
replacements: {
|
|
129
|
+
current: Math.round(value),
|
|
130
|
+
max: Math.round(valueMax),
|
|
131
|
+
},
|
|
132
|
+
})
|
|
124
133
|
}
|
|
125
134
|
// biome-ignore lint/a11y/useAriaPropsForRole: We found that adding valueMin was not needed
|
|
126
135
|
role="progressbar"
|
|
@@ -130,17 +139,15 @@ export const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(
|
|
|
130
139
|
>
|
|
131
140
|
<div
|
|
132
141
|
className={cl("navds-progress-bar__foreground", {
|
|
133
|
-
"navds-progress-bar__foreground--indeterminate":
|
|
134
|
-
simulated?.seconds,
|
|
135
|
-
),
|
|
142
|
+
"navds-progress-bar__foreground--indeterminate":
|
|
143
|
+
simulated?.seconds !== undefined,
|
|
136
144
|
})}
|
|
137
145
|
style={{
|
|
138
|
-
"--__ac-progress-bar-simulated":
|
|
139
|
-
simulated?.seconds
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
"--__ac-progress-bar-translate": `-${translate}%`,
|
|
146
|
+
"--__ac-progress-bar-simulated":
|
|
147
|
+
simulated?.seconds !== undefined
|
|
148
|
+
? `${simulated?.seconds}s`
|
|
149
|
+
: undefined,
|
|
150
|
+
"--__ac-progress-bar-translate": `-${translateX}%`,
|
|
144
151
|
}}
|
|
145
152
|
/>
|
|
146
153
|
</div>
|
package/src/slot/Slot.tsx
CHANGED
|
@@ -10,11 +10,16 @@ const Slot = React.forwardRef<HTMLElement, SlotProps>((props, forwardedRef) => {
|
|
|
10
10
|
const { children, ...slotProps } = props;
|
|
11
11
|
|
|
12
12
|
if (React.isValidElement(children)) {
|
|
13
|
+
const childRef = Object.prototype.propertyIsEnumerable.call(
|
|
14
|
+
children.props,
|
|
15
|
+
"ref",
|
|
16
|
+
)
|
|
17
|
+
? children.props.ref // React 19 (children.ref still works, but gives a warning)
|
|
18
|
+
: (children as any).ref; // React <19
|
|
19
|
+
|
|
13
20
|
return React.cloneElement<any>(children, {
|
|
14
21
|
...mergeProps(slotProps, children.props),
|
|
15
|
-
ref: forwardedRef
|
|
16
|
-
? mergeRefs([forwardedRef, (children as any).ref])
|
|
17
|
-
: (children as any).ref,
|
|
22
|
+
ref: forwardedRef ? mergeRefs([forwardedRef, childRef]) : childRef,
|
|
18
23
|
});
|
|
19
24
|
}
|
|
20
25
|
|
|
@@ -4,6 +4,7 @@ import { ChevronDownIcon } from "@navikt/aksel-icons";
|
|
|
4
4
|
import { composeEventHandlers } from "../util/composeEventHandlers";
|
|
5
5
|
import { useId } from "../util/hooks";
|
|
6
6
|
import { useControllableState } from "../util/hooks/useControllableState";
|
|
7
|
+
import { useI18n } from "../util/i18n/i18n.context";
|
|
7
8
|
import AnimateHeight from "./AnimateHeight";
|
|
8
9
|
import DataCell from "./DataCell";
|
|
9
10
|
import Row, { RowProps } from "./Row";
|
|
@@ -20,7 +21,7 @@ export interface ExpandableRowProps extends Omit<RowProps, "content"> {
|
|
|
20
21
|
togglePlacement?: "left" | "right";
|
|
21
22
|
/**
|
|
22
23
|
* Opens component if 'true', closes if 'false'
|
|
23
|
-
* Using this
|
|
24
|
+
* Using this prop removes automatic control of open-state
|
|
24
25
|
*/
|
|
25
26
|
open?: boolean;
|
|
26
27
|
/**
|
|
@@ -76,21 +77,19 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
|
|
|
76
77
|
value: open,
|
|
77
78
|
onChange: onOpenChange,
|
|
78
79
|
});
|
|
79
|
-
|
|
80
|
+
const translate = useI18n("global");
|
|
80
81
|
const id = useId();
|
|
81
82
|
|
|
82
|
-
const expansionHandler = (
|
|
83
|
-
_setOpen((
|
|
84
|
-
|
|
83
|
+
const expansionHandler = (event: React.MouseEvent<HTMLElement>) => {
|
|
84
|
+
_setOpen((oldOpen) => !oldOpen);
|
|
85
|
+
event.stopPropagation();
|
|
85
86
|
};
|
|
86
87
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
) => {
|
|
93
|
-
!expansionDisabled && expandOnRowClick && onRowClick(e);
|
|
88
|
+
const handleRowClick = (event: React.MouseEvent<HTMLTableRowElement>) => {
|
|
89
|
+
expandOnRowClick &&
|
|
90
|
+
!expansionDisabled &&
|
|
91
|
+
!isInteractiveTarget(event.target as HTMLElement) &&
|
|
92
|
+
expansionHandler(event);
|
|
94
93
|
};
|
|
95
94
|
|
|
96
95
|
return (
|
|
@@ -123,7 +122,7 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
|
|
|
123
122
|
>
|
|
124
123
|
<ChevronDownIcon
|
|
125
124
|
className="navds-table__expandable-icon"
|
|
126
|
-
title={_open ? "
|
|
125
|
+
title={_open ? translate("showLess") : translate("showMore")}
|
|
127
126
|
/>
|
|
128
127
|
</button>
|
|
129
128
|
)}
|
|
@@ -17,11 +17,7 @@ function ScrollButton({ hidden, onClick, dir }: ScrollButtonProps) {
|
|
|
17
17
|
onClick={onClick}
|
|
18
18
|
aria-hidden
|
|
19
19
|
>
|
|
20
|
-
{dir === "left" ?
|
|
21
|
-
<ChevronLeftIcon title="scroll tilbake" />
|
|
22
|
-
) : (
|
|
23
|
-
<ChevronRightIcon title="scroll neste" />
|
|
24
|
-
)}
|
|
20
|
+
{dir === "left" ? <ChevronLeftIcon /> : <ChevronRightIcon />}
|
|
25
21
|
</div>
|
|
26
22
|
);
|
|
27
23
|
}
|