@navikt/ds-react 7.4.0 → 7.4.2
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/combobox/FilteredOptions/useVirtualFocus.js +1 -1
- package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
- package/cjs/form/combobox/Input/Input.js +17 -4
- package/cjs/form/combobox/Input/Input.js.map +1 -1
- package/cjs/form/error-summary/ErrorSummary.js +4 -2
- package/cjs/form/error-summary/ErrorSummary.js.map +1 -1
- package/cjs/form/form-progress/FormProgress.js +1 -3
- package/cjs/form/form-progress/FormProgress.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/form/textarea/Textarea.js +1 -3
- package/cjs/form/textarea/Textarea.js.map +1 -1
- package/cjs/form/textarea/TextareaCounter.d.ts +2 -1
- package/cjs/form/textarea/TextareaCounter.js +14 -9
- package/cjs/form/textarea/TextareaCounter.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 +14 -7
- package/cjs/progress-bar/ProgressBar.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/i18n.context.d.ts +1 -3
- package/cjs/util/i18n/i18n.context.js +5 -5
- package/cjs/util/i18n/i18n.context.js.map +1 -1
- package/cjs/util/i18n/locales/en.d.ts +30 -0
- package/cjs/util/i18n/locales/en.js +30 -0
- package/cjs/util/i18n/locales/en.js.map +1 -1
- package/cjs/util/i18n/locales/nb.d.ts +31 -0
- package/cjs/util/i18n/locales/nb.js +31 -0
- package/cjs/util/i18n/locales/nb.js.map +1 -1
- package/cjs/util/i18n/locales/nn.d.ts +30 -0
- package/cjs/util/i18n/locales/nn.js +30 -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/combobox/FilteredOptions/useVirtualFocus.js +1 -1
- package/esm/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
- package/esm/form/combobox/Input/Input.js +17 -4
- package/esm/form/combobox/Input/Input.js.map +1 -1
- package/esm/form/error-summary/ErrorSummary.js +4 -2
- package/esm/form/error-summary/ErrorSummary.js.map +1 -1
- package/esm/form/form-progress/FormProgress.js +1 -3
- package/esm/form/form-progress/FormProgress.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/form/textarea/Textarea.js +1 -3
- package/esm/form/textarea/Textarea.js.map +1 -1
- package/esm/form/textarea/TextareaCounter.d.ts +2 -1
- package/esm/form/textarea/TextareaCounter.js +14 -9
- package/esm/form/textarea/TextareaCounter.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 +15 -8
- package/esm/progress-bar/ProgressBar.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/i18n.context.d.ts +1 -3
- package/esm/util/i18n/i18n.context.js +5 -5
- package/esm/util/i18n/i18n.context.js.map +1 -1
- package/esm/util/i18n/locales/en.d.ts +30 -0
- package/esm/util/i18n/locales/en.js +30 -0
- package/esm/util/i18n/locales/en.js.map +1 -1
- package/esm/util/i18n/locales/nb.d.ts +31 -0
- package/esm/util/i18n/locales/nb.js +31 -0
- package/esm/util/i18n/locales/nb.js.map +1 -1
- package/esm/util/i18n/locales/nn.d.ts +30 -0
- package/esm/util/i18n/locales/nn.js +30 -0
- package/esm/util/i18n/locales/nn.js.map +1 -1
- package/package.json +3 -3
- package/src/date/utils/parse-date.ts +1 -0
- package/src/expansion-card/ExpansionCardHeader.tsx +4 -2
- package/src/form/combobox/FilteredOptions/useVirtualFocus.ts +1 -1
- package/src/form/combobox/Input/Input.tsx +27 -9
- package/src/form/combobox/__tests__/combobox.test.tsx +39 -0
- package/src/form/error-summary/ErrorSummary.tsx +4 -2
- package/src/form/form-progress/FormProgress.tsx +1 -3
- package/src/form/search/Search.tsx +20 -36
- package/src/form/search/SearchButton.tsx +5 -1
- package/src/form/textarea/Textarea.tsx +7 -11
- package/src/form/textarea/TextareaCounter.tsx +31 -7
- 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 +19 -14
- package/src/table/ExpandableRow.tsx +12 -13
- package/src/tabs/parts/tablist/ScrollButtons.tsx +1 -5
- package/src/util/i18n/i18n.context.test.tsx +4 -6
- package/src/util/i18n/i18n.context.ts +5 -5
- package/src/util/i18n/locales/en.ts +32 -0
- package/src/util/i18n/locales/nb.ts +33 -0
- package/src/util/i18n/locales/nn.ts +32 -0
|
@@ -3,6 +3,7 @@ import React, { HTMLAttributes, forwardRef, useRef } from "react";
|
|
|
3
3
|
import { BodyShort, Heading } from "../../typography";
|
|
4
4
|
import { composeEventHandlers } from "../../util/composeEventHandlers";
|
|
5
5
|
import { useMergeRefs } from "../../util/hooks";
|
|
6
|
+
import { useI18n } from "../../util/i18n/i18n.context";
|
|
6
7
|
import ErrorSummaryItem from "./ErrorSummaryItem";
|
|
7
8
|
|
|
8
9
|
export interface ErrorSummaryProps
|
|
@@ -72,11 +73,12 @@ export const ErrorSummary = forwardRef<HTMLDivElement, ErrorSummaryProps>(
|
|
|
72
73
|
className,
|
|
73
74
|
size = "medium",
|
|
74
75
|
headingTag = "h2",
|
|
75
|
-
heading
|
|
76
|
+
heading,
|
|
76
77
|
...rest
|
|
77
78
|
},
|
|
78
79
|
ref,
|
|
79
80
|
) => {
|
|
81
|
+
const translate = useI18n("ErrorSummary");
|
|
80
82
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
81
83
|
const headingRef = useRef<HTMLHeadingElement>(null);
|
|
82
84
|
|
|
@@ -105,7 +107,7 @@ export const ErrorSummary = forwardRef<HTMLDivElement, ErrorSummaryProps>(
|
|
|
105
107
|
ref={headingRef}
|
|
106
108
|
tabIndex={-1}
|
|
107
109
|
>
|
|
108
|
-
{heading}
|
|
110
|
+
{heading ?? translate("heading")}
|
|
109
111
|
</Heading>
|
|
110
112
|
<BodyShort as="ul" size={size} className="navds-error-summary__list">
|
|
111
113
|
{children}
|
|
@@ -105,9 +105,7 @@ export const FormProgress = forwardRef<HTMLDivElement, FormProgressProps>(
|
|
|
105
105
|
<Collapsible lazy open={open} onOpenChange={onOpenChange}>
|
|
106
106
|
<HStack justify="space-between" align="center">
|
|
107
107
|
<BodyShort as="span">
|
|
108
|
-
{translate("step", {
|
|
109
|
-
replacements: { activeStep, totalSteps },
|
|
110
|
-
})}
|
|
108
|
+
{translate("step", { activeStep, totalSteps })}
|
|
111
109
|
</BodyShort>
|
|
112
110
|
<Collapsible.Trigger asChild aria-expanded={undefined}>
|
|
113
111
|
<Button
|
|
@@ -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
|
>
|
|
@@ -180,17 +180,13 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
180
180
|
{...(describedBy ? { "aria-describedby": describedBy } : {})}
|
|
181
181
|
/>
|
|
182
182
|
{hasMaxLength && !readOnly && !inputProps.disabled && (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
size={size}
|
|
191
|
-
i18n={i18n}
|
|
192
|
-
/>
|
|
193
|
-
</>
|
|
183
|
+
<Counter
|
|
184
|
+
maxLengthId={maxLengthId}
|
|
185
|
+
maxLength={maxLength}
|
|
186
|
+
currentLength={props.value?.length ?? uncontrolledValue.length}
|
|
187
|
+
size={size}
|
|
188
|
+
i18n={i18n}
|
|
189
|
+
/>
|
|
194
190
|
)}
|
|
195
191
|
<div
|
|
196
192
|
className="navds-form-field__error"
|
|
@@ -2,18 +2,32 @@ import cl from "clsx";
|
|
|
2
2
|
import React, { useEffect, useState } from "react";
|
|
3
3
|
import { BodyShort } from "../../typography";
|
|
4
4
|
import debounce from "../../util/debounce";
|
|
5
|
+
import { useI18n } from "../../util/i18n/i18n.context";
|
|
5
6
|
import type { TextareaProps } from "./Textarea";
|
|
6
7
|
|
|
7
8
|
interface Props {
|
|
9
|
+
maxLengthId: string;
|
|
8
10
|
maxLength: number;
|
|
9
11
|
currentLength: number;
|
|
10
12
|
size: TextareaProps["size"];
|
|
11
13
|
i18n: TextareaProps["i18n"];
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
const TextareaCounter = ({
|
|
15
|
-
|
|
16
|
+
const TextareaCounter = ({
|
|
17
|
+
maxLengthId,
|
|
18
|
+
maxLength,
|
|
19
|
+
currentLength,
|
|
20
|
+
size,
|
|
21
|
+
i18n,
|
|
22
|
+
}: Props) => {
|
|
23
|
+
const translate = useI18n("Textarea", {
|
|
24
|
+
charsLeft: i18n?.counterLeft ? `{chars} ${i18n.counterLeft}` : undefined,
|
|
25
|
+
charsTooMany: i18n?.counterTooMuch
|
|
26
|
+
? `{chars} ${i18n.counterTooMuch}`
|
|
27
|
+
: undefined,
|
|
28
|
+
});
|
|
16
29
|
|
|
30
|
+
const difference = maxLength - currentLength;
|
|
17
31
|
const [debouncedDiff, setDebouncedDiff] = useState(difference);
|
|
18
32
|
|
|
19
33
|
useEffect(() => {
|
|
@@ -29,12 +43,16 @@ const TextareaCounter = ({ maxLength, currentLength, size, i18n }: Props) => {
|
|
|
29
43
|
|
|
30
44
|
return (
|
|
31
45
|
<>
|
|
46
|
+
<span id={maxLengthId} className="navds-sr-only">
|
|
47
|
+
{translate("maxLength", { maxLength })}
|
|
48
|
+
</span>
|
|
49
|
+
|
|
32
50
|
{difference < 20 && (
|
|
33
51
|
<span
|
|
34
52
|
role="status"
|
|
35
53
|
className="navds-textarea__sr-counter navds-sr-only"
|
|
36
54
|
>
|
|
37
|
-
{getCounterText(debouncedDiff,
|
|
55
|
+
{getCounterText(debouncedDiff, translate)}
|
|
38
56
|
</span>
|
|
39
57
|
)}
|
|
40
58
|
|
|
@@ -44,15 +62,21 @@ const TextareaCounter = ({ maxLength, currentLength, size, i18n }: Props) => {
|
|
|
44
62
|
})}
|
|
45
63
|
size={size}
|
|
46
64
|
>
|
|
47
|
-
{getCounterText(difference,
|
|
65
|
+
{getCounterText(difference, translate)}
|
|
48
66
|
</BodyShort>
|
|
49
67
|
</>
|
|
50
68
|
);
|
|
51
69
|
};
|
|
52
70
|
|
|
53
|
-
const getCounterText = (
|
|
71
|
+
const getCounterText = (
|
|
72
|
+
difference: number,
|
|
73
|
+
translate: (
|
|
74
|
+
key: "charsTooMany" | "charsLeft",
|
|
75
|
+
replacements?: { chars: number },
|
|
76
|
+
) => string,
|
|
77
|
+
) =>
|
|
54
78
|
difference < 0
|
|
55
|
-
?
|
|
56
|
-
:
|
|
79
|
+
? translate("charsTooMany", { chars: Math.abs(difference) })
|
|
80
|
+
: translate("charsLeft", { chars: difference });
|
|
57
81
|
|
|
58
82
|
export default TextareaCounter;
|
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,13 @@ 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
|
+
seconds: Math.round(simulated?.seconds),
|
|
126
|
+
})
|
|
127
|
+
: translate("progress", {
|
|
128
|
+
current: Math.round(value),
|
|
129
|
+
max: Math.round(valueMax),
|
|
130
|
+
})
|
|
124
131
|
}
|
|
125
132
|
// biome-ignore lint/a11y/useAriaPropsForRole: We found that adding valueMin was not needed
|
|
126
133
|
role="progressbar"
|
|
@@ -130,17 +137,15 @@ export const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(
|
|
|
130
137
|
>
|
|
131
138
|
<div
|
|
132
139
|
className={cl("navds-progress-bar__foreground", {
|
|
133
|
-
"navds-progress-bar__foreground--indeterminate":
|
|
134
|
-
simulated?.seconds,
|
|
135
|
-
),
|
|
140
|
+
"navds-progress-bar__foreground--indeterminate":
|
|
141
|
+
simulated?.seconds !== undefined,
|
|
136
142
|
})}
|
|
137
143
|
style={{
|
|
138
|
-
"--__ac-progress-bar-simulated":
|
|
139
|
-
simulated?.seconds
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
"--__ac-progress-bar-translate": `-${translate}%`,
|
|
144
|
+
"--__ac-progress-bar-simulated":
|
|
145
|
+
simulated?.seconds !== undefined
|
|
146
|
+
? `${simulated?.seconds}s`
|
|
147
|
+
: undefined,
|
|
148
|
+
"--__ac-progress-bar-translate": `-${translateX}%`,
|
|
144
149
|
}}
|
|
145
150
|
/>
|
|
146
151
|
</div>
|
|
@@ -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
|
}
|
|
@@ -79,17 +79,15 @@ describe("useI18n", () => {
|
|
|
79
79
|
};
|
|
80
80
|
const { result } = renderHook(() => useI18n("FileUpload", i18n));
|
|
81
81
|
const translate = result.current;
|
|
82
|
-
expect(
|
|
83
|
-
|
|
84
|
-
)
|
|
82
|
+
expect(translate("item.uploading", { name: "John", cnt: 3 })).toBe(
|
|
83
|
+
"Hello, John. You have 3 messages.",
|
|
84
|
+
);
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
test("should throw an error if replacement key is not found", () => {
|
|
88
88
|
const i18n = { item: { uploading: "Hello, {name}" } };
|
|
89
89
|
const { result } = renderHook(() => useI18n("FileUpload", i18n));
|
|
90
90
|
const translate = result.current;
|
|
91
|
-
expect(() =>
|
|
92
|
-
translate("item.uploading", { replacements: { other: "John" } }),
|
|
93
|
-
).toThrowError();
|
|
91
|
+
expect(() => translate("item.uploading", { other: "John" })).toThrowError();
|
|
94
92
|
});
|
|
95
93
|
});
|
|
@@ -27,7 +27,7 @@ export function useI18n<T extends Component>(
|
|
|
27
27
|
*/
|
|
28
28
|
const translate = (
|
|
29
29
|
keypath: NestedKeyOf<Translations[T]>,
|
|
30
|
-
|
|
30
|
+
replacements?: Record<string, string | number>,
|
|
31
31
|
) => {
|
|
32
32
|
const text = get(
|
|
33
33
|
keypath,
|
|
@@ -37,19 +37,19 @@ export function useI18n<T extends Component>(
|
|
|
37
37
|
: [i18n[componentName]]),
|
|
38
38
|
);
|
|
39
39
|
|
|
40
|
-
if (
|
|
40
|
+
if (replacements) {
|
|
41
41
|
return text.replace(REPLACE_REGEX, (match) => {
|
|
42
42
|
const replacement = match.substring(1, match.length - 1);
|
|
43
43
|
|
|
44
|
-
if (
|
|
45
|
-
const replacementData = JSON.stringify(
|
|
44
|
+
if (replacements[replacement] === undefined) {
|
|
45
|
+
const replacementData = JSON.stringify(replacements);
|
|
46
46
|
|
|
47
47
|
throw new Error(
|
|
48
48
|
`Error translating key '${keypath}'. No replacement syntax ({}) found for key '${replacement}'. The following replacements were passed: '${replacementData}'`,
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
return
|
|
52
|
+
return replacements[replacement] as string; // can also be a number, but JS doesn't mind...
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { Translations } from "../i18n.types";
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
|
+
global: {
|
|
5
|
+
showMore: "Show more",
|
|
6
|
+
showLess: "Show less",
|
|
7
|
+
},
|
|
8
|
+
|
|
4
9
|
FileUpload: {
|
|
5
10
|
dropzone: {
|
|
6
11
|
button: "Choose file",
|
|
@@ -37,4 +42,31 @@ export default {
|
|
|
37
42
|
labelSuffix: "delete",
|
|
38
43
|
},
|
|
39
44
|
},
|
|
45
|
+
ErrorSummary: {
|
|
46
|
+
heading: "You must correct the following errors before you can continue:",
|
|
47
|
+
},
|
|
48
|
+
Loader: {
|
|
49
|
+
title: "Waiting…",
|
|
50
|
+
},
|
|
51
|
+
Modal: {
|
|
52
|
+
close: "Close",
|
|
53
|
+
},
|
|
54
|
+
Pagination: {
|
|
55
|
+
previous: "Previous",
|
|
56
|
+
next: "Next",
|
|
57
|
+
},
|
|
58
|
+
ProgressBar: {
|
|
59
|
+
progress: "{current} of {max}",
|
|
60
|
+
progressUnknown:
|
|
61
|
+
"Progress is unknown, estimated time is {seconds} seconds.",
|
|
62
|
+
},
|
|
63
|
+
Search: {
|
|
64
|
+
clear: "Clear",
|
|
65
|
+
search: "Search",
|
|
66
|
+
},
|
|
67
|
+
Textarea: {
|
|
68
|
+
maxLength: "Text area with a {maxLength} character limit.",
|
|
69
|
+
charsTooMany: "{chars} characters too many",
|
|
70
|
+
charsLeft: "{chars} characters left",
|
|
71
|
+
},
|
|
40
72
|
} satisfies Translations;
|