@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.
Files changed (113) hide show
  1. package/cjs/date/parts/DateInput.js +1 -1
  2. package/cjs/date/parts/DateInput.js.map +1 -1
  3. package/cjs/form/ReadOnlyIcon.d.ts +2 -4
  4. package/cjs/form/ReadOnlyIcon.js +5 -7
  5. package/cjs/form/ReadOnlyIcon.js.map +1 -1
  6. package/cjs/form/checkbox/Checkbox.js +1 -1
  7. package/cjs/form/checkbox/Checkbox.js.map +1 -1
  8. package/cjs/form/combobox/Combobox.js +1 -1
  9. package/cjs/form/combobox/Combobox.js.map +1 -1
  10. package/cjs/form/combobox/Input/Input.js +10 -1
  11. package/cjs/form/combobox/Input/Input.js.map +1 -1
  12. package/cjs/form/error-summary/ErrorSummary.js +4 -2
  13. package/cjs/form/error-summary/ErrorSummary.js.map +1 -1
  14. package/cjs/form/fieldset/Fieldset.js +2 -1
  15. package/cjs/form/fieldset/Fieldset.js.map +1 -1
  16. package/cjs/form/form-progress/FormProgress.js +1 -3
  17. package/cjs/form/form-progress/FormProgress.js.map +1 -1
  18. package/cjs/form/select/Select.js +1 -1
  19. package/cjs/form/select/Select.js.map +1 -1
  20. package/cjs/form/switch/Switch.js +9 -9
  21. package/cjs/form/switch/Switch.js.map +1 -1
  22. package/cjs/form/textarea/Textarea.d.ts +0 -3
  23. package/cjs/form/textarea/Textarea.js +2 -4
  24. package/cjs/form/textarea/Textarea.js.map +1 -1
  25. package/cjs/form/textarea/TextareaCounter.d.ts +2 -1
  26. package/cjs/form/textarea/TextareaCounter.js +14 -9
  27. package/cjs/form/textarea/TextareaCounter.js.map +1 -1
  28. package/cjs/form/textfield/TextField.js +1 -1
  29. package/cjs/form/textfield/TextField.js.map +1 -1
  30. package/cjs/pagination/Pagination.d.ts +9 -3
  31. package/cjs/pagination/Pagination.js +7 -3
  32. package/cjs/pagination/Pagination.js.map +1 -1
  33. package/cjs/progress-bar/ProgressBar.js +3 -5
  34. package/cjs/progress-bar/ProgressBar.js.map +1 -1
  35. package/cjs/util/i18n/i18n.context.d.ts +1 -3
  36. package/cjs/util/i18n/i18n.context.js +5 -5
  37. package/cjs/util/i18n/i18n.context.js.map +1 -1
  38. package/cjs/util/i18n/locales/en.d.ts +9 -0
  39. package/cjs/util/i18n/locales/en.js +9 -0
  40. package/cjs/util/i18n/locales/en.js.map +1 -1
  41. package/cjs/util/i18n/locales/nb.d.ts +10 -0
  42. package/cjs/util/i18n/locales/nb.js +10 -0
  43. package/cjs/util/i18n/locales/nb.js.map +1 -1
  44. package/cjs/util/i18n/locales/nn.d.ts +9 -0
  45. package/cjs/util/i18n/locales/nn.js +9 -0
  46. package/cjs/util/i18n/locales/nn.js.map +1 -1
  47. package/esm/date/parts/DateInput.js +1 -1
  48. package/esm/date/parts/DateInput.js.map +1 -1
  49. package/esm/form/ReadOnlyIcon.d.ts +2 -4
  50. package/esm/form/ReadOnlyIcon.js +3 -6
  51. package/esm/form/ReadOnlyIcon.js.map +1 -1
  52. package/esm/form/checkbox/Checkbox.js +2 -2
  53. package/esm/form/checkbox/Checkbox.js.map +1 -1
  54. package/esm/form/combobox/Combobox.js +2 -2
  55. package/esm/form/combobox/Combobox.js.map +1 -1
  56. package/esm/form/combobox/Input/Input.js +10 -1
  57. package/esm/form/combobox/Input/Input.js.map +1 -1
  58. package/esm/form/error-summary/ErrorSummary.js +4 -2
  59. package/esm/form/error-summary/ErrorSummary.js.map +1 -1
  60. package/esm/form/fieldset/Fieldset.js +3 -2
  61. package/esm/form/fieldset/Fieldset.js.map +1 -1
  62. package/esm/form/form-progress/FormProgress.js +1 -3
  63. package/esm/form/form-progress/FormProgress.js.map +1 -1
  64. package/esm/form/select/Select.js +2 -2
  65. package/esm/form/select/Select.js.map +1 -1
  66. package/esm/form/switch/Switch.js +10 -10
  67. package/esm/form/switch/Switch.js.map +1 -1
  68. package/esm/form/textarea/Textarea.d.ts +0 -3
  69. package/esm/form/textarea/Textarea.js +2 -4
  70. package/esm/form/textarea/Textarea.js.map +1 -1
  71. package/esm/form/textarea/TextareaCounter.d.ts +2 -1
  72. package/esm/form/textarea/TextareaCounter.js +14 -9
  73. package/esm/form/textarea/TextareaCounter.js.map +1 -1
  74. package/esm/form/textfield/TextField.js +1 -1
  75. package/esm/form/textfield/TextField.js.map +1 -1
  76. package/esm/pagination/Pagination.d.ts +9 -3
  77. package/esm/pagination/Pagination.js +7 -3
  78. package/esm/pagination/Pagination.js.map +1 -1
  79. package/esm/progress-bar/ProgressBar.js +3 -5
  80. package/esm/progress-bar/ProgressBar.js.map +1 -1
  81. package/esm/util/i18n/i18n.context.d.ts +1 -3
  82. package/esm/util/i18n/i18n.context.js +5 -5
  83. package/esm/util/i18n/i18n.context.js.map +1 -1
  84. package/esm/util/i18n/locales/en.d.ts +9 -0
  85. package/esm/util/i18n/locales/en.js +9 -0
  86. package/esm/util/i18n/locales/en.js.map +1 -1
  87. package/esm/util/i18n/locales/nb.d.ts +10 -0
  88. package/esm/util/i18n/locales/nb.js +10 -0
  89. package/esm/util/i18n/locales/nb.js.map +1 -1
  90. package/esm/util/i18n/locales/nn.d.ts +9 -0
  91. package/esm/util/i18n/locales/nn.js +9 -0
  92. package/esm/util/i18n/locales/nn.js.map +1 -1
  93. package/package.json +3 -3
  94. package/src/date/parts/DateInput.tsx +1 -1
  95. package/src/form/ReadOnlyIcon.tsx +14 -17
  96. package/src/form/checkbox/Checkbox.tsx +2 -4
  97. package/src/form/combobox/Combobox.tsx +2 -2
  98. package/src/form/combobox/Input/Input.tsx +14 -1
  99. package/src/form/error-summary/ErrorSummary.tsx +4 -2
  100. package/src/form/fieldset/Fieldset.tsx +3 -2
  101. package/src/form/form-progress/FormProgress.tsx +1 -3
  102. package/src/form/select/Select.tsx +2 -2
  103. package/src/form/switch/Switch.tsx +9 -10
  104. package/src/form/textarea/Textarea.tsx +8 -15
  105. package/src/form/textarea/TextareaCounter.tsx +31 -7
  106. package/src/form/textfield/TextField.tsx +1 -1
  107. package/src/pagination/Pagination.tsx +16 -6
  108. package/src/progress-bar/ProgressBar.tsx +3 -5
  109. package/src/util/i18n/i18n.context.test.tsx +4 -6
  110. package/src/util/i18n/i18n.context.ts +5 -5
  111. package/src/util/i18n/locales/en.ts +9 -0
  112. package/src/util/i18n/locales/nb.ts +10 -0
  113. 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.1",
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.1",
608
- "@navikt/ds-tokens": "^7.4.1",
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"
@@ -94,7 +94,7 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
94
94
  "navds-sr-only": hideLabel,
95
95
  })}
96
96
  >
97
- <ReadOnlyIcon readOnly={readOnly} />
97
+ {readOnly && <ReadOnlyIcon />}
98
98
  {label}
99
99
  </Label>
100
100
  {!!description && (
@@ -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
- readOnly,
6
- nativeReadOnly = true,
7
- }: {
8
- readOnly?: boolean;
9
- nativeReadOnly?: boolean;
10
- }) => {
11
- if (readOnly) {
12
- return (
13
- <PadlockLockedFillIcon
14
- {...(nativeReadOnly ? { "aria-hidden": true } : { title: "readonly" })}
15
- className="navds-form-field__readonly-icon"
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 { ReadOnlyIcon } from "../ReadOnlyIcon";
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 { ReadOnlyIcon } from "../ReadOnlyIcon";
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
- <ReadOnlyIcon nativeReadOnly={false} readOnly={readOnly} />
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 (shouldAutocomplete && autoCompletedOption) {
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 = "Du må rette disse feilene før du kan fortsette:",
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
- <ReadOnlyIcon readOnly={readOnly} nativeReadOnly={nativeReadOnly} />
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 { ReadOnlyIcon } from "../ReadOnlyIcon";
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
- <ReadOnlyIcon readOnly={readOnly} nativeReadOnly={false} />
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 { ReadOnlyIcon } from "../ReadOnlyIcon";
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={(e) => {
119
+ onChange={(event) => {
121
120
  if (readOnly) {
122
121
  return;
123
122
  }
124
- setChecked(e.target.checked);
125
- props.onChange?.(e);
123
+ setChecked(event.target.checked);
124
+ props.onChange?.(event);
126
125
  }}
127
- onClick={(e) => {
126
+ onClick={(event) => {
128
127
  if (readOnly) {
129
- e.preventDefault();
128
+ event.preventDefault();
130
129
  return;
131
130
  }
132
- props?.onClick?.(e);
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": !!description && !hideLabel,
152
+ "navds-switch--with-description": description && !hideLabel,
154
153
  })}
155
154
  >
156
155
  <BodyShort as="div" size={size} className="navds-switch__label">
157
- <ReadOnlyIcon readOnly={readOnly} nativeReadOnly={false} />
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 readOnly={readOnly} />
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
- <span id={maxLengthId} className="navds-sr-only">
185
- {`Tekstområde med plass til ${maxLength} tegn.`}
186
- </span>
187
- <Counter
188
- maxLength={maxLength}
189
- currentLength={props.value?.length ?? uncontrolledValue.length}
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 = ({ maxLength, currentLength, size, i18n }: Props) => {
15
- const difference = maxLength - currentLength;
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, i18n)}
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, i18n)}
65
+ {getCounterText(difference, translate)}
48
66
  </BodyShort>
49
67
  </>
50
68
  );
51
69
  };
52
70
 
53
- const getCounterText = (difference: number, i18n: TextareaProps["i18n"]) =>
71
+ const getCounterText = (
72
+ difference: number,
73
+ translate: (
74
+ key: "charsTooMany" | "charsLeft",
75
+ replacements?: { chars: number },
76
+ ) => string,
77
+ ) =>
54
78
  difference < 0
55
- ? `${Math.abs(difference)} ${i18n?.counterTooMuch ?? "tegn for mye"}`
56
- : `${difference} ${i18n?.counterLeft ?? "tegn igjen"}`;
79
+ ? translate("charsTooMany", { chars: Math.abs(difference) })
80
+ : translate("charsLeft", { chars: difference });
57
81
 
58
82
  export default TextareaCounter;
@@ -91,7 +91,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
91
91
  "navds-sr-only": hideLabel,
92
92
  })}
93
93
  >
94
- <ReadOnlyIcon readOnly={readOnly} />
94
+ {readOnly && <ReadOnlyIcon />}
95
95
  {label}
96
96
  </Label>
97
97
 
@@ -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 (item: PaginationItemProps) => <PaginationItem {...item} />
60
+ * @default PaginationItem
50
61
  */
51
- renderItem?: (item: PaginationItemProps) => ReturnType<React.FC>;
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={(x) => setPageState(x)}
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 = (item: PaginationItemProps) => (
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
- replacements: { seconds: Math.round(simulated?.seconds) },
125
+ seconds: Math.round(simulated?.seconds),
126
126
  })
127
127
  : translate("progress", {
128
- replacements: {
129
- current: Math.round(value),
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
- translate("item.uploading", { replacements: { name: "John", cnt: 3 } }),
84
- ).toBe("Hello, John. You have 3 messages.");
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
- options?: { replacements: Record<string, string | number> },
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 (options?.replacements) {
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 (options.replacements[replacement] === undefined) {
45
- const replacementData = JSON.stringify(options.replacements);
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 options.replacements[replacement] as string; // can also be a number, but JS doesn't mind...
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;