@navikt/ds-react 4.5.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/_docs.json +285 -0
  2. package/cjs/date/DateInput.js +9 -4
  3. package/cjs/date/hooks/useDatepicker.js +3 -0
  4. package/cjs/date/hooks/useMonthPicker.js +3 -0
  5. package/cjs/date/hooks/useRangeDatepicker.js +3 -0
  6. package/cjs/form/Fieldset/Fieldset.js +11 -4
  7. package/cjs/form/ReadOnlyIcon.js +15 -0
  8. package/cjs/form/Select.js +23 -3
  9. package/cjs/form/Switch.js +21 -8
  10. package/cjs/form/TextField.js +7 -3
  11. package/cjs/form/Textarea.js +7 -3
  12. package/cjs/form/checkbox/Checkbox.js +7 -2
  13. package/cjs/form/checkbox/CheckboxGroup.js +1 -1
  14. package/cjs/form/checkbox/useCheckbox.js +12 -2
  15. package/cjs/form/radio/Radio.js +3 -2
  16. package/cjs/form/radio/RadioGroup.js +2 -2
  17. package/cjs/form/radio/useRadio.js +12 -2
  18. package/cjs/form/search/Search.js +1 -1
  19. package/cjs/form/useFormField.js +11 -9
  20. package/esm/date/DateInput.js +9 -4
  21. package/esm/date/DateInput.js.map +1 -1
  22. package/esm/date/hooks/useDatepicker.js +3 -0
  23. package/esm/date/hooks/useDatepicker.js.map +1 -1
  24. package/esm/date/hooks/useMonthPicker.js +3 -0
  25. package/esm/date/hooks/useMonthPicker.js.map +1 -1
  26. package/esm/date/hooks/useRangeDatepicker.js +3 -0
  27. package/esm/date/hooks/useRangeDatepicker.js.map +1 -1
  28. package/esm/form/ConfirmationPanel.d.ts +1 -1
  29. package/esm/form/Fieldset/Fieldset.d.ts +5 -0
  30. package/esm/form/Fieldset/Fieldset.js +11 -4
  31. package/esm/form/Fieldset/Fieldset.js.map +1 -1
  32. package/esm/form/Fieldset/useFieldset.d.ts +2 -1
  33. package/esm/form/ReadOnlyIcon.d.ts +5 -0
  34. package/esm/form/ReadOnlyIcon.js +9 -0
  35. package/esm/form/ReadOnlyIcon.js.map +1 -0
  36. package/esm/form/Select.js +23 -3
  37. package/esm/form/Select.js.map +1 -1
  38. package/esm/form/Switch.d.ts +1 -1
  39. package/esm/form/Switch.js +21 -8
  40. package/esm/form/Switch.js.map +1 -1
  41. package/esm/form/TextField.js +7 -3
  42. package/esm/form/TextField.js.map +1 -1
  43. package/esm/form/Textarea.js +7 -3
  44. package/esm/form/Textarea.js.map +1 -1
  45. package/esm/form/checkbox/Checkbox.js +7 -2
  46. package/esm/form/checkbox/Checkbox.js.map +1 -1
  47. package/esm/form/checkbox/CheckboxGroup.js +1 -1
  48. package/esm/form/checkbox/CheckboxGroup.js.map +1 -1
  49. package/esm/form/checkbox/useCheckbox.d.ts +5 -2
  50. package/esm/form/checkbox/useCheckbox.js +12 -2
  51. package/esm/form/checkbox/useCheckbox.js.map +1 -1
  52. package/esm/form/radio/Radio.d.ts +1 -1
  53. package/esm/form/radio/Radio.js +3 -2
  54. package/esm/form/radio/Radio.js.map +1 -1
  55. package/esm/form/radio/RadioGroup.js +2 -2
  56. package/esm/form/radio/RadioGroup.js.map +1 -1
  57. package/esm/form/radio/useRadio.d.ts +4 -2
  58. package/esm/form/radio/useRadio.js +12 -2
  59. package/esm/form/radio/useRadio.js.map +1 -1
  60. package/esm/form/search/Search.d.ts +1 -1
  61. package/esm/form/search/Search.js +1 -1
  62. package/esm/form/search/Search.js.map +1 -1
  63. package/esm/form/useFormField.d.ts +7 -2
  64. package/esm/form/useFormField.js +11 -9
  65. package/esm/form/useFormField.js.map +1 -1
  66. package/package.json +2 -2
  67. package/src/date/DateInput.tsx +8 -2
  68. package/src/date/datepicker/datepicker.stories.tsx +22 -0
  69. package/src/date/hooks/useDatepicker.tsx +3 -0
  70. package/src/date/hooks/useMonthPicker.tsx +3 -0
  71. package/src/date/hooks/useRangeDatepicker.tsx +3 -0
  72. package/src/form/ConfirmationPanel.tsx +1 -1
  73. package/src/form/Fieldset/Fieldset.tsx +15 -2
  74. package/src/form/ReadOnlyIcon.tsx +20 -0
  75. package/src/form/Select.tsx +28 -2
  76. package/src/form/Switch.tsx +20 -9
  77. package/src/form/TextField.tsx +5 -0
  78. package/src/form/Textarea.tsx +5 -0
  79. package/src/form/checkbox/Checkbox.tsx +7 -1
  80. package/src/form/checkbox/CheckboxGroup.tsx +1 -0
  81. package/src/form/checkbox/checkbox.stories.tsx +35 -1
  82. package/src/form/checkbox/useCheckbox.ts +13 -1
  83. package/src/form/radio/Radio.tsx +4 -3
  84. package/src/form/radio/RadioGroup.tsx +3 -0
  85. package/src/form/radio/radio.stories.tsx +27 -0
  86. package/src/form/radio/useRadio.ts +12 -1
  87. package/src/form/search/Search.tsx +2 -2
  88. package/src/form/stories/select.stories.tsx +17 -0
  89. package/src/form/stories/switch.stories.tsx +14 -0
  90. package/src/form/stories/text-field.stories.tsx +14 -0
  91. package/src/form/stories/textarea.stories.tsx +19 -0
  92. package/src/form/useFormField.ts +25 -3
@@ -25,6 +25,10 @@ export interface FormFieldProps {
25
25
  * Override internal id
26
26
  */
27
27
  id?: string;
28
+ /**
29
+ * Read only-state
30
+ */
31
+ readOnly?: boolean;
28
32
  }
29
33
  /**
30
34
  * Handles props and their state for various form-fields in context with Fieldset
@@ -35,10 +39,11 @@ export declare const useFormField: (props: FormFieldProps, prefix: string) => {
35
39
  errorId: string;
36
40
  inputDescriptionId: string;
37
41
  size: "medium" | "small";
42
+ readOnly: true | undefined;
38
43
  inputProps: {
39
- id: string;
40
- "aria-invalid": boolean;
41
44
  "aria-describedby": string | undefined;
42
45
  disabled: boolean | undefined;
46
+ "aria-invalid"?: boolean | undefined;
47
+ id: string;
43
48
  };
44
49
  };
@@ -14,24 +14,26 @@ export const useFormField = (props, prefix) => {
14
14
  const errorId = propErrorId !== null && propErrorId !== void 0 ? propErrorId : `${prefix}-error-${genId}`;
15
15
  const inputDescriptionId = `${prefix}-description-${genId}`;
16
16
  const disabled = (fieldset === null || fieldset === void 0 ? void 0 : fieldset.disabled) || props.disabled;
17
- const hasError = !disabled && !!(error || (fieldset === null || fieldset === void 0 ? void 0 : fieldset.error));
18
- const showErrorMsg = !disabled && !!error && typeof error !== "boolean";
17
+ const readOnly = (((fieldset === null || fieldset === void 0 ? void 0 : fieldset.readOnly) || props.readOnly) && !disabled) || undefined;
18
+ const hasError = !disabled && !readOnly && !!(error || (fieldset === null || fieldset === void 0 ? void 0 : fieldset.error));
19
+ const showErrorMsg = !disabled && !readOnly && !!error && typeof error !== "boolean";
20
+ const ariaInvalid = Object.assign({}, (hasError ? { "aria-invalid": true } : {}));
21
+ if ((props === null || props === void 0 ? void 0 : props.required) && process.env.NODE_ENV !== "production") {
22
+ console.warn("Aksel: Use of 'required' in form-elements is heavily discuouraged. Docs about why here:");
23
+ console.warn("https://aksel.nav.no/god-praksis/artikler/obligatoriske-og-valgfrie-skjemafelter#h3bfe00453471");
24
+ }
19
25
  return {
20
26
  showErrorMsg,
21
27
  hasError,
22
28
  errorId,
23
29
  inputDescriptionId,
24
30
  size: (_b = size !== null && size !== void 0 ? size : fieldset === null || fieldset === void 0 ? void 0 : fieldset.size) !== null && _b !== void 0 ? _b : "medium",
25
- inputProps: {
26
- id,
27
- "aria-invalid": hasError,
28
- "aria-describedby": cl(props["aria-describedby"], {
31
+ readOnly,
32
+ inputProps: Object.assign(Object.assign({ id }, ariaInvalid), { "aria-describedby": cl(props["aria-describedby"], {
29
33
  [inputDescriptionId]: !!(props === null || props === void 0 ? void 0 : props.description) && typeof (props === null || props === void 0 ? void 0 : props.description) === "string",
30
34
  [errorId]: showErrorMsg,
31
35
  [(_c = fieldset === null || fieldset === void 0 ? void 0 : fieldset.errorId) !== null && _c !== void 0 ? _c : ""]: hasError && !!(fieldset === null || fieldset === void 0 ? void 0 : fieldset.error),
32
- }) || undefined,
33
- disabled,
34
- },
36
+ }) || undefined, disabled }),
35
37
  };
36
38
  };
37
39
  //# sourceMappingURL=useFormField.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useFormField.js","sourceRoot":"","sources":["../../src/form/useFormField.ts"],"names":[],"mappings":"AAAA,OAAc,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,MAAM,MAAM,CAAC;AACtB,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AA8BjC;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAqB,EAAE,MAAc,EAAE,EAAE;;IACpE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC;IAEpD,MAAM,QAAQ,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC;IAE7C,MAAM,KAAK,GAAG,KAAK,EAAE,CAAC;IAEtB,MAAM,EAAE,GAAG,MAAA,KAAK,CAAC,EAAE,mCAAI,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC;IAC5C,MAAM,OAAO,GAAG,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,GAAG,MAAM,UAAU,KAAK,EAAE,CAAC;IAC1D,MAAM,kBAAkB,GAAG,GAAG,MAAM,gBAAgB,KAAK,EAAE,CAAC;IAE5D,MAAM,QAAQ,GAAG,CAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,QAAQ,KAAI,KAAK,CAAC,QAAQ,CAAC;IACtD,MAAM,QAAQ,GAAY,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,KAAK,KAAI,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,KAAK,CAAA,CAAC,CAAC;IACpE,MAAM,YAAY,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,SAAS,CAAC;IAExE,OAAO;QACL,YAAY;QACZ,QAAQ;QACR,OAAO;QACP,kBAAkB;QAClB,IAAI,EAAE,MAAA,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,IAAI,mCAAI,QAAQ;QACxC,UAAU,EAAE;YACV,EAAE;YACF,cAAc,EAAE,QAAQ;YACxB,kBAAkB,EAChB,EAAE,CAAC,KAAK,CAAC,kBAAkB,CAAC,EAAE;gBAC5B,CAAC,kBAAkB,CAAC,EAClB,CAAC,CAAC,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,WAAW,CAAA,IAAI,OAAO,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,WAAW,CAAA,KAAK,QAAQ;gBAChE,CAAC,OAAO,CAAC,EAAE,YAAY;gBACvB,CAAC,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,OAAO,mCAAI,EAAE,CAAC,EAAE,QAAQ,IAAI,CAAC,CAAC,CAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,KAAK,CAAA;aACzD,CAAC,IAAI,SAAS;YACjB,QAAQ;SACT;KACF,CAAC;AACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"useFormField.js","sourceRoot":"","sources":["../../src/form/useFormField.ts"],"names":[],"mappings":"AAAA,OAAc,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,MAAM,MAAM,CAAC;AACtB,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAkCjC;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAqB,EAAE,MAAc,EAAE,EAAE;;IACpE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC;IAEpD,MAAM,QAAQ,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC;IAE7C,MAAM,KAAK,GAAG,KAAK,EAAE,CAAC;IAEtB,MAAM,EAAE,GAAG,MAAA,KAAK,CAAC,EAAE,mCAAI,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC;IAC5C,MAAM,OAAO,GAAG,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,GAAG,MAAM,UAAU,KAAK,EAAE,CAAC;IAC1D,MAAM,kBAAkB,GAAG,GAAG,MAAM,gBAAgB,KAAK,EAAE,CAAC;IAE5D,MAAM,QAAQ,GAAG,CAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,QAAQ,KAAI,KAAK,CAAC,QAAQ,CAAC;IACtD,MAAM,QAAQ,GACZ,CAAC,CAAC,CAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,QAAQ,KAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC;IAErE,MAAM,QAAQ,GACZ,CAAC,QAAQ,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,KAAK,KAAI,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,KAAK,CAAA,CAAC,CAAC;IACzD,MAAM,YAAY,GAChB,CAAC,QAAQ,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,SAAS,CAAC;IAElE,MAAM,WAAW,qBAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAE,CAAC;IAEtE,IAAI,CAAC,KAAa,aAAb,KAAK,uBAAL,KAAK,CAAU,QAAQ,KAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE;QACrE,OAAO,CAAC,IAAI,CACV,yFAAyF,CAC1F,CAAC;QACF,OAAO,CAAC,IAAI,CACV,gGAAgG,CACjG,CAAC;KACH;IAED,OAAO;QACL,YAAY;QACZ,QAAQ;QACR,OAAO;QACP,kBAAkB;QAClB,IAAI,EAAE,MAAA,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,IAAI,mCAAI,QAAQ;QACxC,QAAQ;QACR,UAAU,gCACR,EAAE,IACC,WAAW,KACd,kBAAkB,EAChB,EAAE,CAAC,KAAK,CAAC,kBAAkB,CAAC,EAAE;gBAC5B,CAAC,kBAAkB,CAAC,EAClB,CAAC,CAAC,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,WAAW,CAAA,IAAI,OAAO,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,WAAW,CAAA,KAAK,QAAQ;gBAChE,CAAC,OAAO,CAAC,EAAE,YAAY;gBACvB,CAAC,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,OAAO,mCAAI,EAAE,CAAC,EAAE,QAAQ,IAAI,CAAC,CAAC,CAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,KAAK,CAAA;aACzD,CAAC,IAAI,SAAS,EAEjB,QAAQ,GACT;KACF,CAAC;AACJ,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@navikt/ds-react",
3
- "version": "4.5.0",
3
+ "version": "4.6.0",
4
4
  "description": "Aksel react-components for NAV designsystem",
5
5
  "author": "Aksel | NAV designsystem team",
6
6
  "license": "MIT",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@floating-ui/react": "0.24.1",
41
- "@navikt/aksel-icons": "^4.5.0",
41
+ "@navikt/aksel-icons": "^4.6.0",
42
42
  "@radix-ui/react-tabs": "1.0.0",
43
43
  "@radix-ui/react-toggle-group": "1.0.0",
44
44
  "clsx": "^1.2.1",
@@ -4,6 +4,7 @@ import React, { forwardRef, InputHTMLAttributes } from "react";
4
4
  import { BodyShort, ErrorMessage, Label, omit } from "..";
5
5
  import { FormFieldProps, useFormField } from "../form/useFormField";
6
6
  import { useDateInputContext } from "./context";
7
+ import { ReadOnlyIcon } from "../form/ReadOnlyIcon";
7
8
 
8
9
  export interface DateInputProps
9
10
  extends FormFieldProps,
@@ -57,6 +58,7 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
57
58
  errorId,
58
59
  showErrorMsg,
59
60
  hasError,
61
+ readOnly,
60
62
  } = useFormField(props, conditionalVariables.prefix);
61
63
 
62
64
  return (
@@ -71,6 +73,8 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
71
73
  "navds-date__field--error": hasError,
72
74
  "navds-form-field--disabled": !!inputProps.disabled,
73
75
  "navds-text-field--disabled": !!inputProps.disabled,
76
+ "navds-text-field--readonly": readOnly,
77
+ "navds-date__field--readonly": readOnly,
74
78
  }
75
79
  )}
76
80
  >
@@ -81,6 +85,7 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
81
85
  "navds-sr-only": hideLabel,
82
86
  })}
83
87
  >
88
+ <ReadOnlyIcon readOnly={readOnly} />
84
89
  {label}
85
90
  </Label>
86
91
  {!!description && (
@@ -102,6 +107,7 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
102
107
  {...inputProps}
103
108
  autoComplete="off"
104
109
  aria-controls={ariaId}
110
+ readOnly={readOnly}
105
111
  className={cl(
106
112
  "navds-date__field-input",
107
113
  "navds-text-field__input",
@@ -111,8 +117,8 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
111
117
  size={14}
112
118
  />
113
119
  <button
114
- disabled={inputProps.disabled}
115
- tabIndex={open ? -1 : 0}
120
+ disabled={inputProps.disabled || readOnly}
121
+ tabIndex={readOnly ? -1 : open ? -1 : 0}
116
122
  onClick={() => onOpen()}
117
123
  type="button"
118
124
  className="navds-date__field-button"
@@ -341,3 +341,25 @@ export const Size = () => {
341
341
  </div>
342
342
  );
343
343
  };
344
+
345
+ export const Readonly = () => {
346
+ const { datepickerProps, inputProps } = useDatepicker({
347
+ fromDate: new Date("Aug 23 2019"),
348
+ toDate: new Date("Feb 23 2024"),
349
+ onDateChange: console.log,
350
+ });
351
+
352
+ return (
353
+ <div style={{ display: "flex", gap: "1rem" }}>
354
+ <DatePicker {...datepickerProps} dropdownCaption>
355
+ <DatePicker.Input
356
+ size="medium"
357
+ {...inputProps}
358
+ value="01.02.2021"
359
+ label="Velg dato"
360
+ readOnly
361
+ />
362
+ </DatePicker>
363
+ </div>
364
+ );
365
+ };
@@ -204,6 +204,9 @@ export const useDatepicker = (
204
204
  };
205
205
 
206
206
  const handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {
207
+ if (e.target.readOnly) {
208
+ return;
209
+ }
207
210
  !open && openOnFocus && handleOpen(true);
208
211
  let day = parseDate(
209
212
  e.target.value,
@@ -201,6 +201,9 @@ export const useMonthpicker = (
201
201
  };
202
202
 
203
203
  const handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {
204
+ if (e.target.readOnly) {
205
+ return;
206
+ }
204
207
  !open && openOnFocus && handleOpen(true);
205
208
  let day = parseDate(
206
209
  e.target.value,
@@ -321,6 +321,9 @@ export const useRangeDatepicker = (
321
321
  };
322
322
 
323
323
  const handleFocus = (e, src: RangeT) => {
324
+ if (e.target.readOnly) {
325
+ return;
326
+ }
324
327
  !open && openOnFocus && setOpen(true);
325
328
  let day = parseDate(
326
329
  e.target.value,
@@ -6,7 +6,7 @@ import { useFormField } from "./useFormField";
6
6
  export interface ConfirmationPanelProps
7
7
  extends Omit<
8
8
  CheckboxProps,
9
- "children" | "indeterminate" | "hideLabel" | "error"
9
+ "children" | "indeterminate" | "hideLabel" | "error" | "readOnly"
10
10
  > {
11
11
  /**
12
12
  * Additional information on panel
@@ -3,6 +3,7 @@ import React, { FieldsetHTMLAttributes, forwardRef, useContext } from "react";
3
3
  import { BodyShort, ErrorMessage, Label, omit } from "../..";
4
4
  import { FormFieldProps } from "../useFormField";
5
5
  import { useFieldset } from "./useFieldset";
6
+ import { ReadOnlyIcon } from "../ReadOnlyIcon";
6
7
 
7
8
  export type FieldsetContextProps = {
8
9
  /**
@@ -21,6 +22,10 @@ export type FieldsetContextProps = {
21
22
  * Sets fieldset and all form-children to disabled
22
23
  */
23
24
  disabled: boolean;
25
+ /**
26
+ * Read only-state
27
+ */
28
+ readOnly?: boolean;
24
29
  };
25
30
 
26
31
  export const FieldsetContext = React.createContext<FieldsetContextProps | null>(
@@ -47,6 +52,7 @@ export interface FieldsetProps
47
52
  * @default true
48
53
  */
49
54
  errorPropagation?: boolean;
55
+ nativeReadOnly?: boolean;
50
56
  }
51
57
 
52
58
  export const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(
@@ -57,6 +63,7 @@ export const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(
57
63
  showErrorMsg,
58
64
  hasError,
59
65
  size,
66
+ readOnly,
60
67
  inputDescriptionId,
61
68
  } = useFieldset(props);
62
69
 
@@ -69,6 +76,7 @@ export const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(
69
76
  legend,
70
77
  description,
71
78
  hideLegend,
79
+ nativeReadOnly = true,
72
80
  ...rest
73
81
  } = props;
74
82
 
@@ -82,17 +90,21 @@ export const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(
82
90
  }),
83
91
  size,
84
92
  disabled: props.disabled ?? false,
93
+ readOnly,
85
94
  }}
86
95
  >
87
96
  <fieldset
88
- {...omit(rest, ["errorId", "error", "size"])}
97
+ {...omit(rest, ["errorId", "error", "size", "readOnly"])}
89
98
  {...inputProps}
90
99
  ref={ref}
91
100
  className={cl(
92
101
  className,
93
102
  "navds-fieldset",
94
103
  `navds-fieldset--${size}`,
95
- { "navds-fieldset--error": hasError }
104
+ {
105
+ "navds-fieldset--error": hasError,
106
+ "navds-fieldset--readonly": readOnly,
107
+ }
96
108
  )}
97
109
  >
98
110
  <Label
@@ -102,6 +114,7 @@ export const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(
102
114
  "navds-sr-only": !!hideLegend,
103
115
  })}
104
116
  >
117
+ <ReadOnlyIcon readOnly={readOnly} nativeReadOnly={nativeReadOnly} />
105
118
  {legend}
106
119
  </Label>
107
120
  {!!description && (
@@ -0,0 +1,20 @@
1
+ import { PadlockLockedFillIcon } from "@navikt/aksel-icons";
2
+ import React from "react";
3
+
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
+ };
@@ -4,6 +4,7 @@ import React, { forwardRef, SelectHTMLAttributes } from "react";
4
4
  import { ChevronDownIcon } from "@navikt/aksel-icons";
5
5
  import { BodyShort, ErrorMessage, Label, omit } from "..";
6
6
  import { FormFieldProps, useFormField } from "./useFormField";
7
+ import { ReadOnlyIcon } from "./ReadOnlyIcon";
7
8
 
8
9
  export interface SelectProps
9
10
  extends FormFieldProps,
@@ -55,7 +56,8 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
55
56
  hasError,
56
57
  size,
57
58
  inputDescriptionId,
58
- } = useFormField(props, "textField");
59
+ readOnly,
60
+ } = useFormField(props, "select");
59
61
 
60
62
  const {
61
63
  children,
@@ -68,6 +70,27 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
68
70
  ...rest
69
71
  } = props;
70
72
 
73
+ const readOnlyEventHandlers = {
74
+ onMouseDown: (evt) => {
75
+ // NOTE: does not prevent click
76
+ if (readOnly) {
77
+ evt.preventDefault();
78
+ // focus on the element as per readonly input behavior
79
+ evt.target.focus();
80
+ }
81
+ },
82
+ onKeyDown: (evt) => {
83
+ if (
84
+ readOnly &&
85
+ ["ArrowDown", "ArrowUp", "ArrowRight", "ArrowLeft", " "].includes(
86
+ evt.key
87
+ )
88
+ ) {
89
+ evt.preventDefault();
90
+ }
91
+ },
92
+ };
93
+
71
94
  return (
72
95
  <div
73
96
  className={cl(
@@ -77,6 +100,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
77
100
  {
78
101
  "navds-form-field--disabled": !!inputProps.disabled,
79
102
  "navds-select--error": hasError,
103
+ "navds-select--readonly": readOnly,
80
104
  }
81
105
  )}
82
106
  >
@@ -87,6 +111,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
87
111
  "navds-sr-only": hideLabel,
88
112
  })}
89
113
  >
114
+ <ReadOnlyIcon readOnly={readOnly} nativeReadOnly={false} />
90
115
  {label}
91
116
  </Label>
92
117
  {!!description && (
@@ -103,8 +128,9 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
103
128
  )}
104
129
  <div className="navds-select__container" style={style}>
105
130
  <select
106
- {...omit(rest, ["error", "errorId", "size"])}
131
+ {...omit(rest, ["error", "errorId", "size", "readOnly"])}
107
132
  {...inputProps}
133
+ {...readOnlyEventHandlers}
108
134
  ref={ref}
109
135
  className={cl(
110
136
  "navds-select__input",
@@ -7,6 +7,7 @@ import React, {
7
7
  } from "react";
8
8
  import { BodyShort, Loader, omit } from "..";
9
9
  import { FormFieldProps, useFormField } from "./useFormField";
10
+ import { ReadOnlyIcon } from "./ReadOnlyIcon";
10
11
 
11
12
  const SelectedIcon = () => (
12
13
  <svg
@@ -63,12 +64,12 @@ export interface SwitchProps
63
64
  *
64
65
  * @example
65
66
  * ```jsx
66
- * <Switch>Slå notifikasjoner</Switch>
67
+ * <Switch>Varsle med SMS</Switch>
67
68
  * ```
68
69
  */
69
70
  export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
70
71
  (props, ref) => {
71
- const { inputProps, size } = useFormField(props, "switch");
72
+ const { inputProps, size, readOnly } = useFormField(props, "switch");
72
73
 
73
74
  const {
74
75
  children,
@@ -90,11 +91,6 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
90
91
  checkedProp !== undefined && setChecked(checkedProp);
91
92
  }, [checkedProp]);
92
93
 
93
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
94
- setChecked(e.target.checked);
95
- props.onChange && props.onChange(e);
96
- };
97
-
98
94
  return (
99
95
  <div
100
96
  className={cl(
@@ -105,18 +101,32 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
105
101
  {
106
102
  "navds-switch--loading": loading,
107
103
  "navds-switch--disabled": inputProps.disabled ?? loading,
104
+ "navds-switch--readonly": readOnly,
108
105
  }
109
106
  )}
110
107
  >
111
108
  <input
112
- {...omit(rest, ["size"])}
109
+ {...omit(rest, ["size", "readOnly"])}
113
110
  {...omit(inputProps, ["aria-invalid", "aria-describedby"])}
114
111
  disabled={inputProps.disabled ?? loading}
115
112
  checked={checkedProp}
116
113
  defaultChecked={defaultChecked}
117
114
  ref={ref}
118
115
  type="checkbox"
119
- onChange={(e) => handleChange(e)}
116
+ onChange={(e) => {
117
+ if (readOnly) {
118
+ return;
119
+ }
120
+ setChecked(e.target.checked);
121
+ props.onChange && props.onChange(e);
122
+ }}
123
+ onClick={(e) => {
124
+ if (readOnly) {
125
+ e.preventDefault();
126
+ return;
127
+ }
128
+ props?.onClick?.(e);
129
+ }}
120
130
  className={cl(className, "navds-switch__input")}
121
131
  />
122
132
  <span className="navds-switch__track">
@@ -136,6 +146,7 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
136
146
  })}
137
147
  >
138
148
  <BodyShort as="div" size={size} className="navds-switch__label">
149
+ <ReadOnlyIcon readOnly={readOnly} nativeReadOnly={false} />
139
150
  {children}
140
151
  </BodyShort>
141
152
  {description && (
@@ -1,6 +1,7 @@
1
1
  import cl from "clsx";
2
2
  import React, { forwardRef, InputHTMLAttributes } from "react";
3
3
  import { BodyShort, ErrorMessage, Label, omit } from "..";
4
+ import { ReadOnlyIcon } from "./ReadOnlyIcon";
4
5
  import { FormFieldProps, useFormField } from "./useFormField";
5
6
 
6
7
  export interface TextFieldProps
@@ -62,6 +63,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
62
63
  htmlSize,
63
64
  hideLabel = false,
64
65
  type = "text",
66
+ readOnly,
65
67
  ...rest
66
68
  } = props;
67
69
 
@@ -75,6 +77,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
75
77
  "navds-text-field--error": hasError,
76
78
  "navds-text-field--disabled": !!inputProps.disabled,
77
79
  "navds-form-field--disabled": !!inputProps.disabled,
80
+ "navds-text-field--readonly": readOnly,
78
81
  }
79
82
  )}
80
83
  >
@@ -85,6 +88,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
85
88
  "navds-sr-only": hideLabel,
86
89
  })}
87
90
  >
91
+ <ReadOnlyIcon readOnly={readOnly} />
88
92
  {label}
89
93
  </Label>
90
94
 
@@ -105,6 +109,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
105
109
  {...inputProps}
106
110
  ref={ref}
107
111
  type={type}
112
+ readOnly={readOnly}
108
113
  className={cl(
109
114
  "navds-text-field__input",
110
115
  "navds-body-short",
@@ -3,6 +3,7 @@ import React, { forwardRef, useState } from "react";
3
3
  import { BodyShort, ErrorMessage, Label, omit, useId } from "..";
4
4
  import TextareaAutosize from "../util/TextareaAutoSize";
5
5
  import { FormFieldProps, useFormField } from "./useFormField";
6
+ import { ReadOnlyIcon } from "./ReadOnlyIcon";
6
7
 
7
8
  /**
8
9
  * TODO: Mulighet for lokalisering av sr-only/counter text
@@ -85,6 +86,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
85
86
  hideLabel = false,
86
87
  resize,
87
88
  i18n,
89
+ readOnly,
88
90
  ...rest
89
91
  } = props;
90
92
 
@@ -115,6 +117,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
115
117
  `navds-form-field--${size}`,
116
118
  {
117
119
  "navds-form-field--disabled": !!inputProps.disabled,
120
+ "navds-textarea--readonly": readOnly,
118
121
  "navds-textarea--error": hasError,
119
122
  "navds-textarea--resize": resize,
120
123
  }
@@ -127,6 +130,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
127
130
  "navds-sr-only": hideLabel,
128
131
  })}
129
132
  >
133
+ <ReadOnlyIcon readOnly={readOnly} />
130
134
  {label}
131
135
  </Label>
132
136
  {!!description && (
@@ -152,6 +156,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
152
156
  }
153
157
  minRows={getMinRows()}
154
158
  ref={ref}
159
+ readOnly={readOnly}
155
160
  className={cl(
156
161
  "navds-textarea__input",
157
162
  "navds-body-short",
@@ -4,6 +4,7 @@ import { BodyShort } from "../../typography";
4
4
  import { omit } from "../../util";
5
5
  import { FormFieldProps } from "../useFormField";
6
6
  import useCheckbox from "./useCheckbox";
7
+ import { ReadOnlyIcon } from "../ReadOnlyIcon";
7
8
 
8
9
  export interface CheckboxProps
9
10
  extends FormFieldProps,
@@ -42,7 +43,7 @@ export interface CheckboxProps
42
43
 
43
44
  export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
44
45
  (props, ref) => {
45
- const { inputProps, hasError, size } = useCheckbox(props);
46
+ const { inputProps, hasError, size, readOnly, nested } = useCheckbox(props);
46
47
 
47
48
  return (
48
49
  <div
@@ -53,6 +54,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
53
54
  {
54
55
  "navds-checkbox--error": hasError,
55
56
  "navds-checkbox--disabled": inputProps.disabled,
57
+ "navds-checkbox--readonly": readOnly,
56
58
  }
57
59
  )}
58
60
  >
@@ -65,6 +67,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
65
67
  "hideLabel",
66
68
  "indeterminate",
67
69
  "errorId",
70
+ "readOnly",
68
71
  ])}
69
72
  {...inputProps}
70
73
  type="checkbox"
@@ -89,6 +92,9 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
89
92
  })}
90
93
  >
91
94
  <BodyShort as="span" size={size}>
95
+ {!nested && (
96
+ <ReadOnlyIcon readOnly={readOnly} nativeReadOnly={false} />
97
+ )}
92
98
  {props.children}
93
99
  </BodyShort>
94
100
  {props.description && (
@@ -81,6 +81,7 @@ export const CheckboxGroup = forwardRef<
81
81
  "navds-checkbox-group",
82
82
  `navds-checkbox-group--${rest.size ?? fieldset?.size ?? "medium"}`
83
83
  )}
84
+ nativeReadOnly={false}
84
85
  >
85
86
  <CheckboxGroupContext.Provider
86
87
  value={{
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable react-hooks/rules-of-hooks */
2
+ import { Meta } from "@storybook/react";
2
3
  import React, { useState } from "react";
3
4
  import { Checkbox, CheckboxGroup } from "../../index";
4
- import { Meta } from "@storybook/react";
5
5
 
6
6
  export default {
7
7
  title: "ds-react/Checkbox",
@@ -190,3 +190,37 @@ export const Indeterminate = () => {
190
190
  </>
191
191
  );
192
192
  };
193
+
194
+ export const Readonly = () => (
195
+ <div className="colgap">
196
+ <CheckboxGroup
197
+ legend="Hvilken frukt liker du?"
198
+ defaultValue={["banan"]}
199
+ readOnly
200
+ >
201
+ <Checkbox value="banan">Banan</Checkbox>
202
+ <Checkbox value="eple">Eple</Checkbox>
203
+ <Checkbox value="druer" indeterminate>
204
+ Druer
205
+ </Checkbox>
206
+ </CheckboxGroup>
207
+ <CheckboxGroup
208
+ legend="Hvilken frukt liker du?"
209
+ error="feilmelding"
210
+ defaultValue={["Eple"]}
211
+ readOnly
212
+ >
213
+ <Checkbox value="eple" description="Epler kommer i 4 varianter">
214
+ Eple
215
+ </Checkbox>
216
+ <Checkbox value="banan">Banan</Checkbox>
217
+ </CheckboxGroup>
218
+ <hr />
219
+ <Checkbox value="tekst1" readOnly>
220
+ Eple single
221
+ </Checkbox>
222
+ <Checkbox value="tekst1" checked readOnly>
223
+ Banan single
224
+ </Checkbox>
225
+ </div>
226
+ );
@@ -10,7 +10,7 @@ import { omit } from "../..";
10
10
  const useCheckbox = ({ children, ...props }: CheckboxProps) => {
11
11
  const checkboxGroup = useContext(CheckboxGroupContext);
12
12
 
13
- const { inputProps, ...rest } = useFormField(
13
+ const { inputProps, readOnly, ...rest } = useFormField(
14
14
  omit(props, ["description"]),
15
15
  "checkbox"
16
16
  );
@@ -30,6 +30,8 @@ const useCheckbox = ({ children, ...props }: CheckboxProps) => {
30
30
 
31
31
  return {
32
32
  ...rest,
33
+ readOnly,
34
+ nested: !!checkboxGroup,
33
35
  inputProps: {
34
36
  ...inputProps,
35
37
  checked: checkboxGroup?.value
@@ -39,9 +41,19 @@ const useCheckbox = ({ children, ...props }: CheckboxProps) => {
39
41
  ? checkboxGroup.defaultValue.includes(props.value)
40
42
  : props.defaultChecked,
41
43
  onChange: (e) => {
44
+ if (readOnly) {
45
+ return;
46
+ }
42
47
  props.onChange && props.onChange(e);
43
48
  checkboxGroup && checkboxGroup.toggleValue(props.value);
44
49
  },
50
+ onClick: (e) => {
51
+ if (readOnly) {
52
+ e.preventDefault();
53
+ return;
54
+ }
55
+ props?.onClick?.(e);
56
+ },
45
57
  },
46
58
  };
47
59
  };