@navikt/ds-react 7.4.1 → 7.4.3
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/parts/DateInput.js +1 -1
- package/cjs/date/parts/DateInput.js.map +1 -1
- package/cjs/form/ReadOnlyIcon.d.ts +2 -4
- package/cjs/form/ReadOnlyIcon.js +5 -7
- package/cjs/form/ReadOnlyIcon.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/Combobox.js +1 -1
- package/cjs/form/combobox/Combobox.js.map +1 -1
- package/cjs/form/combobox/Input/Input.js +10 -1
- 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/fieldset/Fieldset.js +2 -1
- package/cjs/form/fieldset/Fieldset.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/select/Select.js +1 -1
- package/cjs/form/select/Select.js.map +1 -1
- package/cjs/form/switch/Switch.js +9 -9
- package/cjs/form/switch/Switch.js.map +1 -1
- package/cjs/form/textarea/Textarea.d.ts +0 -3
- package/cjs/form/textarea/Textarea.js +2 -4
- 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/form/textfield/TextField.js +1 -1
- package/cjs/form/textfield/TextField.js.map +1 -1
- package/cjs/pagination/Pagination.d.ts +9 -3
- package/cjs/pagination/Pagination.js +7 -3
- package/cjs/pagination/Pagination.js.map +1 -1
- package/cjs/progress-bar/ProgressBar.js +3 -5
- package/cjs/progress-bar/ProgressBar.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 +9 -0
- package/cjs/util/i18n/locales/en.js +9 -0
- package/cjs/util/i18n/locales/en.js.map +1 -1
- package/cjs/util/i18n/locales/nb.d.ts +10 -0
- package/cjs/util/i18n/locales/nb.js +10 -0
- package/cjs/util/i18n/locales/nb.js.map +1 -1
- package/cjs/util/i18n/locales/nn.d.ts +9 -0
- package/cjs/util/i18n/locales/nn.js +9 -0
- package/cjs/util/i18n/locales/nn.js.map +1 -1
- package/esm/date/parts/DateInput.js +1 -1
- package/esm/date/parts/DateInput.js.map +1 -1
- package/esm/form/ReadOnlyIcon.d.ts +2 -4
- package/esm/form/ReadOnlyIcon.js +3 -6
- package/esm/form/ReadOnlyIcon.js.map +1 -1
- package/esm/form/checkbox/Checkbox.js +2 -2
- package/esm/form/checkbox/Checkbox.js.map +1 -1
- package/esm/form/combobox/Combobox.js +2 -2
- package/esm/form/combobox/Combobox.js.map +1 -1
- package/esm/form/combobox/Input/Input.js +10 -1
- 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/fieldset/Fieldset.js +3 -2
- package/esm/form/fieldset/Fieldset.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/select/Select.js +2 -2
- package/esm/form/select/Select.js.map +1 -1
- package/esm/form/switch/Switch.js +10 -10
- package/esm/form/switch/Switch.js.map +1 -1
- package/esm/form/textarea/Textarea.d.ts +0 -3
- package/esm/form/textarea/Textarea.js +2 -4
- 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/form/textfield/TextField.js +1 -1
- package/esm/form/textfield/TextField.js.map +1 -1
- package/esm/pagination/Pagination.d.ts +9 -3
- package/esm/pagination/Pagination.js +7 -3
- package/esm/pagination/Pagination.js.map +1 -1
- package/esm/progress-bar/ProgressBar.js +3 -5
- package/esm/progress-bar/ProgressBar.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 +9 -0
- package/esm/util/i18n/locales/en.js +9 -0
- package/esm/util/i18n/locales/en.js.map +1 -1
- package/esm/util/i18n/locales/nb.d.ts +10 -0
- package/esm/util/i18n/locales/nb.js +10 -0
- package/esm/util/i18n/locales/nb.js.map +1 -1
- package/esm/util/i18n/locales/nn.d.ts +9 -0
- package/esm/util/i18n/locales/nn.js +9 -0
- package/esm/util/i18n/locales/nn.js.map +1 -1
- package/package.json +3 -3
- package/src/date/parts/DateInput.tsx +1 -1
- package/src/form/ReadOnlyIcon.tsx +14 -17
- package/src/form/checkbox/Checkbox.tsx +2 -4
- package/src/form/combobox/Combobox.tsx +2 -2
- package/src/form/combobox/Input/Input.tsx +14 -1
- package/src/form/error-summary/ErrorSummary.tsx +4 -2
- package/src/form/fieldset/Fieldset.tsx +3 -2
- package/src/form/form-progress/FormProgress.tsx +1 -3
- package/src/form/select/Select.tsx +2 -2
- package/src/form/switch/Switch.tsx +9 -10
- package/src/form/textarea/Textarea.tsx +8 -15
- package/src/form/textarea/TextareaCounter.tsx +31 -7
- package/src/form/textfield/TextField.tsx +1 -1
- package/src/pagination/Pagination.tsx +16 -6
- package/src/progress-bar/ProgressBar.tsx +3 -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 +9 -0
- package/src/util/i18n/locales/nb.ts +10 -0
- package/src/util/i18n/locales/nn.ts +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@navikt/ds-react",
|
|
3
|
-
"version": "7.4.
|
|
3
|
+
"version": "7.4.3",
|
|
4
4
|
"description": "React components from the Norwegian Labour and Welfare Administration.",
|
|
5
5
|
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -604,8 +604,8 @@
|
|
|
604
604
|
"dependencies": {
|
|
605
605
|
"@floating-ui/react": "0.25.4",
|
|
606
606
|
"@floating-ui/react-dom": "^2.0.9",
|
|
607
|
-
"@navikt/aksel-icons": "^7.4.
|
|
608
|
-
"@navikt/ds-tokens": "^7.4.
|
|
607
|
+
"@navikt/aksel-icons": "^7.4.3",
|
|
608
|
+
"@navikt/ds-tokens": "^7.4.3",
|
|
609
609
|
"clsx": "^2.1.0",
|
|
610
610
|
"date-fns": "^3.0.0",
|
|
611
611
|
"react-day-picker": "8.10.1"
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { PadlockLockedFillIcon } from "@navikt/aksel-icons";
|
|
3
|
+
import { useI18n } from "../util/i18n/i18n.context";
|
|
3
4
|
|
|
4
|
-
export const ReadOnlyIcon = (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
};
|
|
5
|
+
export const ReadOnlyIcon = () => (
|
|
6
|
+
<PadlockLockedFillIcon
|
|
7
|
+
aria-hidden
|
|
8
|
+
className="navds-form-field__readonly-icon"
|
|
9
|
+
/>
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
export const ReadOnlyIconWithTitle = () => (
|
|
13
|
+
<PadlockLockedFillIcon
|
|
14
|
+
title={useI18n("global")("readOnly")}
|
|
15
|
+
className="navds-form-field__readonly-icon"
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
@@ -3,7 +3,7 @@ import React, { forwardRef } from "react";
|
|
|
3
3
|
import { BodyShort } from "../../typography";
|
|
4
4
|
import { omit } from "../../util";
|
|
5
5
|
import { useId } from "../../util/hooks";
|
|
6
|
-
import {
|
|
6
|
+
import { ReadOnlyIconWithTitle } from "../ReadOnlyIcon";
|
|
7
7
|
import { CheckboxProps } from "./types";
|
|
8
8
|
import useCheckbox from "./useCheckbox";
|
|
9
9
|
|
|
@@ -90,9 +90,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
90
90
|
className="navds-checkbox__label-text"
|
|
91
91
|
aria-hidden
|
|
92
92
|
>
|
|
93
|
-
{!nested &&
|
|
94
|
-
<ReadOnlyIcon readOnly={readOnly} nativeReadOnly={false} />
|
|
95
|
-
)}
|
|
93
|
+
{!nested && readOnly && <ReadOnlyIconWithTitle />}
|
|
96
94
|
{props.children}
|
|
97
95
|
</BodyShort>
|
|
98
96
|
{props.description && (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import cl from "clsx";
|
|
2
2
|
import React, { forwardRef } from "react";
|
|
3
3
|
import { BodyShort, ErrorMessage, Label } from "../../typography";
|
|
4
|
-
import {
|
|
4
|
+
import { ReadOnlyIconWithTitle } from "../ReadOnlyIcon";
|
|
5
5
|
import ComboboxWrapper from "./ComboboxWrapper";
|
|
6
6
|
import FilteredOptions from "./FilteredOptions/FilteredOptions";
|
|
7
7
|
import { useFilteredOptionsContext } from "./FilteredOptions/filteredOptionsContext";
|
|
@@ -46,7 +46,7 @@ export const Combobox = forwardRef<
|
|
|
46
46
|
"navds-sr-only": hideLabel,
|
|
47
47
|
})}
|
|
48
48
|
>
|
|
49
|
-
|
|
49
|
+
{readOnly && <ReadOnlyIconWithTitle />}
|
|
50
50
|
{label}
|
|
51
51
|
</Label>
|
|
52
52
|
{!!description && (
|
|
@@ -100,9 +100,22 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
100
100
|
value,
|
|
101
101
|
filteredOptions,
|
|
102
102
|
);
|
|
103
|
+
|
|
104
|
+
/*
|
|
105
|
+
* User can have matching results, while not using the autocomplete result
|
|
106
|
+
* E.g. User types "Oslo", list has is "Oslo kommune", but user hits backspace, canceling autocomplete.
|
|
107
|
+
*/
|
|
108
|
+
const autoCompleteMatchesValue =
|
|
109
|
+
filteredOptionsUtil.normalizeText(value) ===
|
|
110
|
+
filteredOptionsUtil.normalizeText(autoCompletedOption?.label ?? "");
|
|
111
|
+
|
|
103
112
|
let selectedValue: ComboboxOption | undefined;
|
|
104
113
|
|
|
105
|
-
if (
|
|
114
|
+
if (
|
|
115
|
+
shouldAutocomplete &&
|
|
116
|
+
autoCompletedOption &&
|
|
117
|
+
autoCompleteMatchesValue
|
|
118
|
+
) {
|
|
106
119
|
selectedValue = autoCompletedOption;
|
|
107
120
|
} else if (allowNewValues && isValueNew) {
|
|
108
121
|
selectedValue = { label: value, value };
|
|
@@ -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}
|
|
@@ -2,7 +2,7 @@ import cl from "clsx";
|
|
|
2
2
|
import React, { FieldsetHTMLAttributes, forwardRef, useContext } from "react";
|
|
3
3
|
import { BodyShort, ErrorMessage, Label } from "../../typography";
|
|
4
4
|
import { omit } from "../../util";
|
|
5
|
-
import { ReadOnlyIcon } from "../ReadOnlyIcon";
|
|
5
|
+
import { ReadOnlyIcon, ReadOnlyIconWithTitle } from "../ReadOnlyIcon";
|
|
6
6
|
import { FormFieldProps } from "../useFormField";
|
|
7
7
|
import { FieldsetContext } from "./context";
|
|
8
8
|
import { useFieldset } from "./useFieldset";
|
|
@@ -89,7 +89,8 @@ export const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(
|
|
|
89
89
|
"navds-sr-only": !!hideLegend,
|
|
90
90
|
})}
|
|
91
91
|
>
|
|
92
|
-
|
|
92
|
+
{readOnly &&
|
|
93
|
+
(nativeReadOnly ? <ReadOnlyIcon /> : <ReadOnlyIconWithTitle />)}
|
|
93
94
|
{legend}
|
|
94
95
|
</Label>
|
|
95
96
|
{!!description && (
|
|
@@ -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
|
|
@@ -3,7 +3,7 @@ import React, { SelectHTMLAttributes, forwardRef } from "react";
|
|
|
3
3
|
import { ChevronDownIcon } from "@navikt/aksel-icons";
|
|
4
4
|
import { BodyShort, ErrorMessage, Label } from "../../typography";
|
|
5
5
|
import { omit } from "../../util";
|
|
6
|
-
import {
|
|
6
|
+
import { ReadOnlyIconWithTitle } from "../ReadOnlyIcon";
|
|
7
7
|
import { FormFieldProps, useFormField } from "../useFormField";
|
|
8
8
|
|
|
9
9
|
export interface SelectProps
|
|
@@ -112,7 +112,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
|
|
|
112
112
|
"navds-sr-only": hideLabel,
|
|
113
113
|
})}
|
|
114
114
|
>
|
|
115
|
-
|
|
115
|
+
{readOnly && <ReadOnlyIconWithTitle />}
|
|
116
116
|
{label}
|
|
117
117
|
</Label>
|
|
118
118
|
{!!description && (
|
|
@@ -8,7 +8,7 @@ import React, {
|
|
|
8
8
|
import { Loader } from "../../loader";
|
|
9
9
|
import { BodyShort } from "../../typography";
|
|
10
10
|
import { omit } from "../../util";
|
|
11
|
-
import {
|
|
11
|
+
import { ReadOnlyIconWithTitle } from "../ReadOnlyIcon";
|
|
12
12
|
import { FormFieldProps, useFormField } from "../useFormField";
|
|
13
13
|
|
|
14
14
|
const SelectedIcon = () => (
|
|
@@ -21,7 +21,6 @@ const SelectedIcon = () => (
|
|
|
21
21
|
focusable={false}
|
|
22
22
|
role="img"
|
|
23
23
|
aria-hidden
|
|
24
|
-
aria-label="Deaktiver valg"
|
|
25
24
|
>
|
|
26
25
|
<path
|
|
27
26
|
fillRule="evenodd"
|
|
@@ -117,19 +116,19 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
117
116
|
defaultChecked={defaultChecked}
|
|
118
117
|
ref={ref}
|
|
119
118
|
type="checkbox"
|
|
120
|
-
onChange={(
|
|
119
|
+
onChange={(event) => {
|
|
121
120
|
if (readOnly) {
|
|
122
121
|
return;
|
|
123
122
|
}
|
|
124
|
-
setChecked(
|
|
125
|
-
props.onChange?.(
|
|
123
|
+
setChecked(event.target.checked);
|
|
124
|
+
props.onChange?.(event);
|
|
126
125
|
}}
|
|
127
|
-
onClick={(
|
|
126
|
+
onClick={(event) => {
|
|
128
127
|
if (readOnly) {
|
|
129
|
-
|
|
128
|
+
event.preventDefault();
|
|
130
129
|
return;
|
|
131
130
|
}
|
|
132
|
-
props
|
|
131
|
+
props.onClick?.(event);
|
|
133
132
|
}}
|
|
134
133
|
className={cl(className, "navds-switch__input")}
|
|
135
134
|
/>
|
|
@@ -150,11 +149,11 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
150
149
|
<div
|
|
151
150
|
className={cl("navds-switch__content", {
|
|
152
151
|
"navds-sr-only": hideLabel,
|
|
153
|
-
"navds-switch--with-description":
|
|
152
|
+
"navds-switch--with-description": description && !hideLabel,
|
|
154
153
|
})}
|
|
155
154
|
>
|
|
156
155
|
<BodyShort as="div" size={size} className="navds-switch__label">
|
|
157
|
-
|
|
156
|
+
{readOnly && <ReadOnlyIconWithTitle />}
|
|
158
157
|
{children}
|
|
159
158
|
</BodyShort>
|
|
160
159
|
{description && (
|
|
@@ -9,9 +9,6 @@ import { ReadOnlyIcon } from "../ReadOnlyIcon";
|
|
|
9
9
|
import { FormFieldProps, useFormField } from "./../useFormField";
|
|
10
10
|
import Counter from "./TextareaCounter";
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* TODO: Mulighet for lokalisering av sr-only/counter text
|
|
14
|
-
*/
|
|
15
12
|
export interface TextareaProps
|
|
16
13
|
extends FormFieldProps,
|
|
17
14
|
React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
@@ -144,7 +141,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
144
141
|
"navds-sr-only": hideLabel,
|
|
145
142
|
})}
|
|
146
143
|
>
|
|
147
|
-
<ReadOnlyIcon
|
|
144
|
+
{readOnly && <ReadOnlyIcon />}
|
|
148
145
|
{label}
|
|
149
146
|
</Label>
|
|
150
147
|
{!!description && (
|
|
@@ -180,17 +177,13 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
180
177
|
{...(describedBy ? { "aria-describedby": describedBy } : {})}
|
|
181
178
|
/>
|
|
182
179
|
{hasMaxLength && !readOnly && !inputProps.disabled && (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
size={size}
|
|
191
|
-
i18n={i18n}
|
|
192
|
-
/>
|
|
193
|
-
</>
|
|
180
|
+
<Counter
|
|
181
|
+
maxLengthId={maxLengthId}
|
|
182
|
+
maxLength={maxLength}
|
|
183
|
+
currentLength={props.value?.length ?? uncontrolledValue.length}
|
|
184
|
+
size={size}
|
|
185
|
+
i18n={i18n}
|
|
186
|
+
/>
|
|
194
187
|
)}
|
|
195
188
|
<div
|
|
196
189
|
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;
|
|
@@ -9,6 +9,17 @@ import PaginationItem, {
|
|
|
9
9
|
PaginationItemType,
|
|
10
10
|
} from "./PaginationItem";
|
|
11
11
|
|
|
12
|
+
interface RenderItemProps
|
|
13
|
+
extends Pick<
|
|
14
|
+
PaginationItemProps,
|
|
15
|
+
"className" | "disabled" | "selected" | "icon" | "iconPosition"
|
|
16
|
+
> {
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
|
19
|
+
page: number;
|
|
20
|
+
size: Exclude<PaginationProps["size"], undefined>;
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
export interface PaginationProps extends React.HTMLAttributes<HTMLElement> {
|
|
13
24
|
/**
|
|
14
25
|
* Current page.
|
|
@@ -46,9 +57,9 @@ export interface PaginationProps extends React.HTMLAttributes<HTMLElement> {
|
|
|
46
57
|
prevNextTexts?: boolean;
|
|
47
58
|
/**
|
|
48
59
|
* Override pagination item rendering.
|
|
49
|
-
* @default
|
|
60
|
+
* @default PaginationItem
|
|
50
61
|
*/
|
|
51
|
-
renderItem?: (item:
|
|
62
|
+
renderItem?: (item: RenderItemProps) => ReturnType<React.FC>;
|
|
52
63
|
/**
|
|
53
64
|
* Pagination heading. We recommend adding heading instead of `aria-label` to help assistive technologies with an extra navigation-stop.
|
|
54
65
|
*/
|
|
@@ -111,7 +122,7 @@ export const getSteps = ({
|
|
|
111
122
|
* ```jsx
|
|
112
123
|
* <Pagination
|
|
113
124
|
* page={pageState}
|
|
114
|
-
* onPageChange={
|
|
125
|
+
* onPageChange={setPageState}
|
|
115
126
|
* count={9}
|
|
116
127
|
* boundaryCount={1}
|
|
117
128
|
* siblingCount={1}
|
|
@@ -131,9 +142,7 @@ export const Pagination = forwardRef<HTMLElement, PaginationProps>(
|
|
|
131
142
|
prevNextTexts = false,
|
|
132
143
|
srHeading,
|
|
133
144
|
"aria-labelledby": ariaLabelledBy,
|
|
134
|
-
renderItem: Item =
|
|
135
|
-
<PaginationItem {...item} />
|
|
136
|
-
),
|
|
145
|
+
renderItem: Item = PaginationItem,
|
|
137
146
|
...rest
|
|
138
147
|
},
|
|
139
148
|
ref,
|
|
@@ -218,6 +227,7 @@ export const Pagination = forwardRef<HTMLElement, PaginationProps>(
|
|
|
218
227
|
) : (
|
|
219
228
|
<li key={step}>
|
|
220
229
|
<Item
|
|
230
|
+
/* Remember to update RenderItemProps if you make changes to props sent into Item */
|
|
221
231
|
onClick={() => onPageChange?.(n)}
|
|
222
232
|
selected={page === n}
|
|
223
233
|
page={n}
|
|
@@ -122,13 +122,11 @@ export const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(
|
|
|
122
122
|
aria-valuetext={
|
|
123
123
|
simulated?.seconds
|
|
124
124
|
? translate("progressUnknown", {
|
|
125
|
-
|
|
125
|
+
seconds: Math.round(simulated?.seconds),
|
|
126
126
|
})
|
|
127
127
|
: translate("progress", {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
max: Math.round(valueMax),
|
|
131
|
-
},
|
|
128
|
+
current: Math.round(value),
|
|
129
|
+
max: Math.round(valueMax),
|
|
132
130
|
})
|
|
133
131
|
}
|
|
134
132
|
// biome-ignore lint/a11y/useAriaPropsForRole: We found that adding valueMin was not needed
|
|
@@ -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
|
|
|
@@ -4,6 +4,7 @@ export default {
|
|
|
4
4
|
global: {
|
|
5
5
|
showMore: "Show more",
|
|
6
6
|
showLess: "Show less",
|
|
7
|
+
readOnly: "Read-only",
|
|
7
8
|
},
|
|
8
9
|
|
|
9
10
|
FileUpload: {
|
|
@@ -42,6 +43,9 @@ export default {
|
|
|
42
43
|
labelSuffix: "delete",
|
|
43
44
|
},
|
|
44
45
|
},
|
|
46
|
+
ErrorSummary: {
|
|
47
|
+
heading: "You must correct the following errors before you can continue:",
|
|
48
|
+
},
|
|
45
49
|
Loader: {
|
|
46
50
|
title: "Waiting…",
|
|
47
51
|
},
|
|
@@ -61,4 +65,9 @@ export default {
|
|
|
61
65
|
clear: "Clear",
|
|
62
66
|
search: "Search",
|
|
63
67
|
},
|
|
68
|
+
Textarea: {
|
|
69
|
+
maxLength: "Text area with a {maxLength} character limit.",
|
|
70
|
+
charsTooMany: "{chars} characters too many",
|
|
71
|
+
charsLeft: "{chars} characters left",
|
|
72
|
+
},
|
|
64
73
|
} satisfies Translations;
|
|
@@ -10,6 +10,7 @@ export default {
|
|
|
10
10
|
global: {
|
|
11
11
|
showMore: "Vis mer",
|
|
12
12
|
showLess: "Vis mindre",
|
|
13
|
+
readOnly: "Skrivebeskyttet",
|
|
13
14
|
},
|
|
14
15
|
|
|
15
16
|
FileUpload: {
|
|
@@ -49,6 +50,9 @@ export default {
|
|
|
49
50
|
labelSuffix: "slett",
|
|
50
51
|
},
|
|
51
52
|
},
|
|
53
|
+
ErrorSummary: {
|
|
54
|
+
heading: "Du må rette disse feilene før du kan fortsette:",
|
|
55
|
+
},
|
|
52
56
|
Loader: {
|
|
53
57
|
title: "Venter…",
|
|
54
58
|
},
|
|
@@ -68,4 +72,10 @@ export default {
|
|
|
68
72
|
clear: "Tøm",
|
|
69
73
|
search: "Søk",
|
|
70
74
|
},
|
|
75
|
+
Textarea: {
|
|
76
|
+
/** Screen readers only */
|
|
77
|
+
maxLength: "Tekstområde med plass til {maxLength} tegn.",
|
|
78
|
+
charsTooMany: "{chars} tegn for mye",
|
|
79
|
+
charsLeft: "{chars} tegn igjen",
|
|
80
|
+
},
|
|
71
81
|
} satisfies TranslationMap;
|
|
@@ -4,6 +4,7 @@ export default {
|
|
|
4
4
|
global: {
|
|
5
5
|
showMore: "Vis meir",
|
|
6
6
|
showLess: "Vis mindre",
|
|
7
|
+
readOnly: "Skrivebeskytta",
|
|
7
8
|
},
|
|
8
9
|
|
|
9
10
|
FileUpload: {
|
|
@@ -42,6 +43,9 @@ export default {
|
|
|
42
43
|
labelSuffix: "slett",
|
|
43
44
|
},
|
|
44
45
|
},
|
|
46
|
+
ErrorSummary: {
|
|
47
|
+
heading: "Du må rette desse feila før du kan halde fram:",
|
|
48
|
+
},
|
|
45
49
|
Loader: {
|
|
46
50
|
title: "Ventar…",
|
|
47
51
|
},
|
|
@@ -61,4 +65,9 @@ export default {
|
|
|
61
65
|
clear: "Tøm",
|
|
62
66
|
search: "Søk",
|
|
63
67
|
},
|
|
68
|
+
Textarea: {
|
|
69
|
+
maxLength: "Tekstområde med plass til {maxLength} teikn.",
|
|
70
|
+
charsTooMany: "{chars} teikn for mykje",
|
|
71
|
+
charsLeft: "{chars} teikn igjen",
|
|
72
|
+
},
|
|
64
73
|
} satisfies Translations;
|