@navikt/ds-react 5.8.0 → 5.9.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 (118) hide show
  1. package/_docs.json +1794 -1749
  2. package/cjs/date/context/useDateInputContext.js +1 -5
  3. package/cjs/date/datepicker/DatePicker.js +26 -25
  4. package/cjs/date/hooks/useDatepicker.js +9 -17
  5. package/cjs/date/hooks/useMonthPicker.js +9 -17
  6. package/cjs/date/hooks/useRangeDatepicker.js +9 -20
  7. package/cjs/date/monthpicker/MonthPicker.js +11 -6
  8. package/cjs/date/{DateInput.js → parts/DateInput.js} +14 -10
  9. package/cjs/date/parts/DateWrapper.js +55 -0
  10. package/cjs/date/utils/labels.js +77 -1
  11. package/cjs/form/combobox/Combobox.js +2 -2
  12. package/cjs/form/combobox/ComboboxProvider.js +1 -2
  13. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +15 -14
  14. package/cjs/form/combobox/FilteredOptions/filtered-options-util.js +24 -0
  15. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +23 -106
  16. package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js +55 -0
  17. package/cjs/form/combobox/Input/Input.js +22 -13
  18. package/cjs/form/combobox/customOptionsContext.js +2 -3
  19. package/cjs/modal/Modal.js +4 -1
  20. package/cjs/popover/Popover.js +5 -7
  21. package/cjs/util/useMedia.js +30 -0
  22. package/esm/date/context/useDateInputContext.d.ts +6 -2
  23. package/esm/date/context/useDateInputContext.js +1 -5
  24. package/esm/date/context/useDateInputContext.js.map +1 -1
  25. package/esm/date/datepicker/DatePicker.d.ts +1 -1
  26. package/esm/date/datepicker/DatePicker.js +28 -27
  27. package/esm/date/datepicker/DatePicker.js.map +1 -1
  28. package/esm/date/datepicker/types.d.ts +0 -5
  29. package/esm/date/hooks/useDatepicker.d.ts +8 -5
  30. package/esm/date/hooks/useDatepicker.js +10 -18
  31. package/esm/date/hooks/useDatepicker.js.map +1 -1
  32. package/esm/date/hooks/useMonthPicker.d.ts +7 -4
  33. package/esm/date/hooks/useMonthPicker.js +10 -18
  34. package/esm/date/hooks/useMonthPicker.js.map +1 -1
  35. package/esm/date/hooks/useRangeDatepicker.d.ts +9 -3
  36. package/esm/date/hooks/useRangeDatepicker.js +10 -21
  37. package/esm/date/hooks/useRangeDatepicker.js.map +1 -1
  38. package/esm/date/index.d.ts +1 -1
  39. package/esm/date/index.js.map +1 -1
  40. package/esm/date/monthpicker/MonthPicker.d.ts +1 -1
  41. package/esm/date/monthpicker/MonthPicker.js +13 -8
  42. package/esm/date/monthpicker/MonthPicker.js.map +1 -1
  43. package/esm/date/monthpicker/types.d.ts +0 -5
  44. package/esm/date/{DateInput.d.ts → parts/DateInput.d.ts} +5 -1
  45. package/esm/date/{DateInput.js → parts/DateInput.js} +15 -11
  46. package/esm/date/parts/DateInput.js.map +1 -0
  47. package/esm/date/parts/DateWrapper.d.ts +15 -0
  48. package/esm/date/parts/DateWrapper.js +26 -0
  49. package/esm/date/parts/DateWrapper.js.map +1 -0
  50. package/esm/date/utils/labels.d.ts +2 -0
  51. package/esm/date/utils/labels.js +74 -0
  52. package/esm/date/utils/labels.js.map +1 -1
  53. package/esm/form/combobox/Combobox.js +2 -2
  54. package/esm/form/combobox/Combobox.js.map +1 -1
  55. package/esm/form/combobox/ComboboxProvider.js +1 -2
  56. package/esm/form/combobox/ComboboxProvider.js.map +1 -1
  57. package/esm/form/combobox/FilteredOptions/FilteredOptions.js +15 -14
  58. package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  59. package/esm/form/combobox/FilteredOptions/filtered-options-util.d.ts +12 -0
  60. package/esm/form/combobox/FilteredOptions/filtered-options-util.js +23 -0
  61. package/esm/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -0
  62. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.d.ts +10 -13
  63. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +24 -107
  64. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  65. package/esm/form/combobox/FilteredOptions/useVirtualFocus.d.ts +15 -0
  66. package/esm/form/combobox/FilteredOptions/useVirtualFocus.js +54 -0
  67. package/esm/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -0
  68. package/esm/form/combobox/Input/Input.js +22 -13
  69. package/esm/form/combobox/Input/Input.js.map +1 -1
  70. package/esm/form/combobox/customOptionsContext.d.ts +4 -1
  71. package/esm/form/combobox/customOptionsContext.js +2 -3
  72. package/esm/form/combobox/customOptionsContext.js.map +1 -1
  73. package/esm/modal/Modal.js +4 -1
  74. package/esm/modal/Modal.js.map +1 -1
  75. package/esm/popover/Popover.d.ts +0 -5
  76. package/esm/popover/Popover.js +5 -7
  77. package/esm/popover/Popover.js.map +1 -1
  78. package/esm/util/useMedia.d.ts +8 -0
  79. package/esm/util/useMedia.js +27 -0
  80. package/esm/util/useMedia.js.map +1 -0
  81. package/package.json +3 -3
  82. package/src/date/context/useDateInputContext.tsx +5 -5
  83. package/src/date/datepicker/DatePicker.tsx +58 -65
  84. package/src/date/datepicker/datepicker.stories.tsx +37 -46
  85. package/src/date/datepicker/types.ts +0 -5
  86. package/src/date/hooks/useDatepicker.tsx +20 -25
  87. package/src/date/hooks/useMonthPicker.tsx +18 -24
  88. package/src/date/hooks/useRangeDatepicker.tsx +27 -30
  89. package/src/date/index.ts +1 -1
  90. package/src/date/monthpicker/MonthPicker.tsx +39 -43
  91. package/src/date/monthpicker/types.ts +0 -5
  92. package/src/date/{DateInput.tsx → parts/DateInput.tsx} +23 -12
  93. package/src/date/parts/DateWrapper.tsx +80 -0
  94. package/src/date/utils/labels.ts +83 -0
  95. package/src/form/combobox/Combobox.tsx +2 -2
  96. package/src/form/combobox/ComboboxProvider.tsx +1 -2
  97. package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +28 -16
  98. package/src/form/combobox/FilteredOptions/filtered-options-util.ts +38 -0
  99. package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +70 -140
  100. package/src/form/combobox/FilteredOptions/useVirtualFocus.ts +87 -0
  101. package/src/form/combobox/Input/Input.tsx +22 -18
  102. package/src/form/combobox/customOptionsContext.tsx +10 -5
  103. package/src/guide-panel/guidepanel.stories.tsx +2 -2
  104. package/src/modal/Modal.tsx +4 -1
  105. package/src/popover/Popover.tsx +4 -12
  106. package/src/util/__tests__/useMedia.test.tsx +19 -0
  107. package/src/util/useMedia.ts +38 -0
  108. package/cjs/date/hooks/useEscape.js +0 -23
  109. package/cjs/date/hooks/useOutsideClickHandler.js +0 -26
  110. package/esm/date/DateInput.js.map +0 -1
  111. package/esm/date/hooks/useEscape.d.ts +0 -2
  112. package/esm/date/hooks/useEscape.js +0 -20
  113. package/esm/date/hooks/useEscape.js.map +0 -1
  114. package/esm/date/hooks/useOutsideClickHandler.d.ts +0 -1
  115. package/esm/date/hooks/useOutsideClickHandler.js +0 -23
  116. package/esm/date/hooks/useOutsideClickHandler.js.map +0 -1
  117. package/src/date/hooks/useEscape.tsx +0 -30
  118. package/src/date/hooks/useOutsideClickHandler.tsx +0 -34
@@ -1,10 +1,10 @@
1
1
  import differenceInCalendarDays from "date-fns/differenceInCalendarDays";
2
2
  import checkIsBefore from "date-fns/isBefore";
3
3
  import isWeekend from "date-fns/isWeekend";
4
- import React, { useRef, useState } from "react";
4
+ import React, { useState } from "react";
5
5
  import { DateRange, isMatch } from "react-day-picker";
6
- import { DateInputProps } from "../DateInput";
7
6
  import { DatePickerProps } from "../datepicker/DatePicker";
7
+ import { DateInputProps } from "../parts/DateInput";
8
8
  import {
9
9
  formatDateForInput,
10
10
  getLocaleFromString,
@@ -12,8 +12,6 @@ import {
12
12
  parseDate,
13
13
  } from "../utils";
14
14
  import { DateValidationT, UseDatepickerOptions } from "./useDatepicker";
15
- import { useEscape } from "./useEscape";
16
- import { useOutsideClickHandler } from "./useOutsideClickHandler";
17
15
 
18
16
  export type RangeValidationT = {
19
17
  from: DateValidationT;
@@ -74,14 +72,28 @@ interface UseRangeDatepickerValue {
74
72
  fromInputProps: Pick<
75
73
  DateInputProps,
76
74
  "onChange" | "onFocus" | "onBlur" | "value"
77
- > & { ref: React.RefObject<HTMLInputElement> };
75
+ > & {
76
+ /**
77
+ * @private
78
+ */
79
+ setAnchorRef: React.Dispatch<
80
+ React.SetStateAction<HTMLButtonElement | null>
81
+ >;
82
+ };
78
83
  /**
79
84
  * Use: <DatePicker.Input label="to" {...toInputProps}/>
80
85
  */
81
86
  toInputProps: Pick<
82
87
  DateInputProps,
83
88
  "onChange" | "onFocus" | "onBlur" | "value"
84
- > & { ref: React.RefObject<HTMLInputElement> };
89
+ > & {
90
+ /**
91
+ * @private
92
+ */
93
+ setAnchorRef?: React.Dispatch<
94
+ React.SetStateAction<HTMLButtonElement | null>
95
+ >;
96
+ };
85
97
  /**
86
98
  * Resets all states (callback)
87
99
  */
@@ -218,14 +230,11 @@ export const useRangeDatepicker = (
218
230
  onValidate,
219
231
  defaultMonth,
220
232
  allowTwoDigitYear = true,
221
- openOnFocus = true,
222
233
  } = opt;
223
234
 
224
- const locale = getLocaleFromString(_locale);
235
+ const [anchorRef, setAnchorRef] = useState<HTMLButtonElement | null>(null);
225
236
 
226
- const inputRefTo = useRef<HTMLInputElement>(null);
227
- const inputRefFrom = useRef<HTMLInputElement>(null);
228
- const [daypickerRef, setDaypickerRef] = useState<HTMLDivElement>();
237
+ const locale = getLocaleFromString(_locale);
229
238
 
230
239
  const [defaultSelected, setDefaultSelected] = useState(_defaultSelected);
231
240
 
@@ -255,20 +264,6 @@ export const useRangeDatepicker = (
255
264
 
256
265
  const [open, setOpen] = useState(false);
257
266
 
258
- useOutsideClickHandler(open, setOpen, [
259
- daypickerRef,
260
- inputRefTo.current,
261
- inputRefFrom.current,
262
- inputRefTo.current?.nextSibling,
263
- inputRefFrom.current?.nextSibling,
264
- ]);
265
-
266
- useEscape(
267
- open,
268
- setOpen,
269
- selectedRange?.from && !selectedRange?.to ? inputRefTo : inputRefFrom
270
- );
271
-
272
267
  const updateRange = (range?: DateRange) => {
273
268
  onRangeChange?.(range);
274
269
  setSelectedRange(range);
@@ -324,7 +319,6 @@ export const useRangeDatepicker = (
324
319
  if (e.target.readOnly) {
325
320
  return;
326
321
  }
327
- !open && openOnFocus && setOpen(true);
328
322
  const day = parseDate(
329
323
  e.target.value,
330
324
  today,
@@ -378,6 +372,7 @@ export const useRangeDatepicker = (
378
372
  const handleSelect = (range) => {
379
373
  if (range?.from && range?.to) {
380
374
  setOpen(false);
375
+ anchorRef?.focus();
381
376
  }
382
377
  const prevToRange =
383
378
  !selectedRange?.from && selectedRange?.to ? selectedRange?.to : range?.to;
@@ -545,10 +540,12 @@ export const useRangeDatepicker = (
545
540
  mode: "range" as const,
546
541
  open,
547
542
  onOpenToggle: () => setOpen((x) => !x),
543
+ onClose: () => {
544
+ setOpen(false);
545
+ anchorRef?.focus();
546
+ },
548
547
  disabled,
549
548
  disableWeekends,
550
- bubbleEscape: true,
551
- ref: setDaypickerRef,
552
549
  };
553
550
 
554
551
  const fromInputProps = {
@@ -556,7 +553,7 @@ export const useRangeDatepicker = (
556
553
  onFocus: (e) => handleFocus(e, RANGE.FROM),
557
554
  onBlur: (e) => handleBlur(e, RANGE.FROM),
558
555
  value: fromInputValue,
559
- ref: inputRefFrom,
556
+ setAnchorRef,
560
557
  };
561
558
 
562
559
  const toInputProps = {
@@ -564,7 +561,7 @@ export const useRangeDatepicker = (
564
561
  onFocus: (e) => handleFocus(e, RANGE.TO),
565
562
  onBlur: (e) => handleBlur(e, RANGE.TO),
566
563
  value: toInputValue,
567
- ref: inputRefTo,
564
+ setAnchorRef,
568
565
  };
569
566
 
570
567
  return {
package/src/date/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- export { type DateInputProps } from "./DateInput";
2
1
  export {
3
2
  default as DatePicker,
4
3
  type DatePickerProps,
@@ -15,3 +14,4 @@ export {
15
14
  export { default as MonthPicker } from "./monthpicker/MonthPicker";
16
15
  export { type MonthPickerStandaloneProps } from "./monthpicker/MonthPickerStandalone";
17
16
  export { type MonthPickerProps } from "./monthpicker/types";
17
+ export { type DateInputProps } from "./parts/DateInput";
@@ -1,10 +1,10 @@
1
1
  import cl from "clsx";
2
- import React, { forwardRef, useRef, useState } from "react";
2
+ import React, { forwardRef, useMemo, useRef, useState } from "react";
3
3
  import { RootProvider } from "react-day-picker";
4
- import { Popover } from "../../popover";
5
- import { useId } from "../../util";
6
- import { MonthPickerInput } from "../DateInput";
4
+ import { mergeRefs, useId } from "../../util";
7
5
  import { DateContext, SharedMonthProvider } from "../context";
6
+ import { MonthPickerInput } from "../parts/DateInput";
7
+ import { DateWrapper } from "../parts/DateWrapper";
8
8
  import { getLocaleFromString } from "../utils";
9
9
  import MonthCaption from "./MonthCaption";
10
10
  import MonthPickerStandalone from "./MonthPickerStandalone";
@@ -74,7 +74,6 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
74
74
  year,
75
75
  onYearChange,
76
76
  strategy = "absolute",
77
- bubbleEscape = false,
78
77
  },
79
78
  ref
80
79
  ) => {
@@ -82,6 +81,7 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
82
81
  const [open, setOpen] = useState(_open ?? false);
83
82
 
84
83
  const wrapperRef = useRef<HTMLDivElement | null>(null);
84
+ const mergedRef = useMemo(() => mergeRefs([wrapperRef, ref]), [ref]);
85
85
 
86
86
  const [selectedMonth, setSelectedMonth] = useState<Date | undefined>(
87
87
  defaultSelected
@@ -107,51 +107,47 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
107
107
  onOpenToggle?.();
108
108
  },
109
109
  ariaId,
110
+ defined: true,
110
111
  }}
111
112
  >
112
113
  <div
113
- ref={wrapperRef}
114
+ ref={mergedRef}
114
115
  className={cl("navds-date__wrapper", wrapperClassName)}
115
116
  >
116
117
  {children}
117
- {(_open ?? open) && (
118
- <Popover
119
- arrow={false}
120
- anchorEl={wrapperRef.current}
121
- open={_open ?? open}
122
- onClose={() => onClose?.() ?? setOpen(false)}
123
- placement="bottom-start"
124
- role="dialog"
125
- ref={ref}
126
- id={ariaId}
127
- className="navds-date navds-date__popover"
128
- strategy={strategy}
129
- bubbleEscape={bubbleEscape}
130
- flip={false}
118
+ <DateWrapper
119
+ open={_open ?? open}
120
+ anchor={wrapperRef.current}
121
+ onClose={() => onClose?.() ?? setOpen(false)}
122
+ locale={locale}
123
+ variant="month"
124
+ popoverProps={{
125
+ id: ariaId,
126
+ strategy,
127
+ }}
128
+ >
129
+ <RootProvider
130
+ locale={getLocaleFromString(locale)}
131
+ selected={selected ?? selectedMonth}
132
+ toDate={toDate}
133
+ fromDate={fromDate}
134
+ month={selected ?? selectedMonth}
131
135
  >
132
- <RootProvider
133
- locale={getLocaleFromString(locale)}
134
- selected={selected ?? selectedMonth}
135
- toDate={toDate}
136
- fromDate={fromDate}
137
- month={selected ?? selectedMonth}
138
- >
139
- <div className={cl("rdp-month", className)}>
140
- <SharedMonthProvider
141
- dropdownCaption={dropdownCaption}
142
- disabled={disabled}
143
- selected={selected ?? selectedMonth}
144
- onSelect={handleSelect}
145
- year={year}
146
- onYearChange={onYearChange}
147
- >
148
- <MonthCaption />
149
- <MonthSelector />
150
- </SharedMonthProvider>
151
- </div>
152
- </RootProvider>
153
- </Popover>
154
- )}
136
+ <div className={cl("rdp-month", className)}>
137
+ <SharedMonthProvider
138
+ dropdownCaption={dropdownCaption}
139
+ disabled={disabled}
140
+ selected={selected ?? selectedMonth}
141
+ onSelect={handleSelect}
142
+ year={year}
143
+ onYearChange={onYearChange}
144
+ >
145
+ <MonthCaption />
146
+ <MonthSelector />
147
+ </SharedMonthProvider>
148
+ </div>
149
+ </RootProvider>
150
+ </DateWrapper>
155
151
  </div>
156
152
  </DateContext.Provider>
157
153
  );
@@ -75,9 +75,4 @@ export interface MonthPickerProps extends React.HTMLAttributes<HTMLDivElement> {
75
75
  * @default "absolute"
76
76
  */
77
77
  strategy?: "absolute" | "fixed";
78
- /**
79
- * Bubbles Escape keydown-event up trough DOM-tree. This is set to false by default to prevent closing components like Modal on Escape
80
- * @default false
81
- */
82
- bubbleEscape?: boolean;
83
78
  }
@@ -1,11 +1,11 @@
1
1
  import { CalendarIcon } from "@navikt/aksel-icons";
2
2
  import cl from "clsx";
3
- import React, { forwardRef, InputHTMLAttributes } from "react";
4
- import { FormFieldProps, useFormField } from "../form/useFormField";
5
- import { useDateInputContext } from "./context";
6
- import { ReadOnlyIcon } from "../form/ReadOnlyIcon";
7
- import { BodyShort, ErrorMessage, Label } from "../typography";
8
- import { omit } from "../util";
3
+ import React, { forwardRef, InputHTMLAttributes, useRef } from "react";
4
+ import { ReadOnlyIcon } from "../../form/ReadOnlyIcon";
5
+ import { FormFieldProps, useFormField } from "../../form/useFormField";
6
+ import { BodyShort, ErrorMessage, Label } from "../../typography";
7
+ import { omit } from "../../util";
8
+ import { useDateInputContext } from "../context";
9
9
 
10
10
  export interface DateInputProps
11
11
  extends FormFieldProps,
@@ -28,6 +28,10 @@ export interface DateInputProps
28
28
  * @private
29
29
  */
30
30
  variant?: "datepicker" | "monthpicker";
31
+ /**
32
+ * @private
33
+ */
34
+ setAnchorRef?: React.Dispatch<React.SetStateAction<HTMLButtonElement | null>>;
31
35
  }
32
36
 
33
37
  const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
@@ -37,9 +41,12 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
37
41
  label,
38
42
  description,
39
43
  variant = "datepicker",
44
+ setAnchorRef,
40
45
  ...rest
41
46
  } = props;
42
47
 
48
+ const buttonRef = useRef<HTMLButtonElement>(null);
49
+
43
50
  const isDatepickerVariant = variant === "datepicker";
44
51
 
45
52
  const conditionalVariables = {
@@ -50,7 +57,7 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
50
57
  },
51
58
  };
52
59
 
53
- const { onOpen, ariaId, open } = useDateInputContext();
60
+ const context = useDateInputContext();
54
61
 
55
62
  const {
56
63
  inputProps,
@@ -108,7 +115,7 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
108
115
  {...omit(rest, ["error", "errorId", "size"])}
109
116
  {...inputProps}
110
117
  autoComplete="off"
111
- aria-controls={open ? ariaId : undefined}
118
+ aria-controls={context?.open ? context.ariaId : undefined}
112
119
  readOnly={readOnly}
113
120
  className={cl(
114
121
  "navds-date__field-input",
@@ -116,19 +123,23 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
116
123
  "navds-body-short",
117
124
  `navds-body-short--${size}`
118
125
  )}
119
- size={isDatepickerVariant ? 10 : 14}
126
+ size={isDatepickerVariant ? 11 : 14}
120
127
  />
121
128
  <button
122
129
  disabled={inputProps.disabled || readOnly}
123
- tabIndex={readOnly ? -1 : open ? -1 : 0}
124
- onClick={() => onOpen()}
130
+ tabIndex={readOnly ? -1 : context?.open ? -1 : 0}
131
+ onClick={() => {
132
+ context?.onOpen();
133
+ setAnchorRef?.(buttonRef.current);
134
+ }}
125
135
  type="button"
126
136
  className="navds-date__field-button"
137
+ ref={buttonRef}
127
138
  >
128
139
  <CalendarIcon
129
140
  pointerEvents="none"
130
141
  title={
131
- open
142
+ context?.open
132
143
  ? conditionalVariables.iconTitle.close
133
144
  : conditionalVariables.iconTitle.open
134
145
  }
@@ -0,0 +1,80 @@
1
+ import cl from "clsx";
2
+ import React, { useContext, useRef } from "react";
3
+ import { Button } from "../../button";
4
+ import { Modal } from "../../modal";
5
+ import { ModalContext } from "../../modal/ModalContext";
6
+ import { Popover } from "../../popover";
7
+ import { useMedia } from "../../util/useMedia";
8
+ import { modalCloseButtonLabel, modalLabel } from "../utils/labels";
9
+
10
+ type DateWrapperProps = {
11
+ open: boolean;
12
+ children: React.ReactNode;
13
+ onClose: () => void;
14
+ anchor: HTMLDivElement | null;
15
+ locale: "nb" | "nn" | "en";
16
+ variant: "single" | "multiple" | "range" | "month";
17
+ popoverProps: {
18
+ id?: string;
19
+ strategy?: "absolute" | "fixed";
20
+ };
21
+ };
22
+
23
+ export const DateWrapper = ({
24
+ open,
25
+ children,
26
+ onClose,
27
+ anchor,
28
+ locale,
29
+ variant,
30
+ popoverProps,
31
+ }: DateWrapperProps) => {
32
+ const modalRef = useRef<HTMLDialogElement>(null);
33
+ const isInModal = useContext(ModalContext) !== null;
34
+ const hideModal =
35
+ useMedia("screen and (min-width: 768px)", true) && !isInModal;
36
+
37
+ if (hideModal) {
38
+ return (
39
+ <Popover
40
+ arrow={false}
41
+ anchorEl={anchor}
42
+ open={open}
43
+ onClose={onClose}
44
+ placement="bottom-start"
45
+ role="dialog"
46
+ className={cl("navds-date__popover", {
47
+ "navds-date": variant === "month",
48
+ })}
49
+ flip={false}
50
+ {...popoverProps}
51
+ >
52
+ {children}
53
+ </Popover>
54
+ );
55
+ }
56
+ return (
57
+ <Modal
58
+ ref={modalRef}
59
+ open={open}
60
+ onClose={onClose}
61
+ aria-label={modalLabel(locale, variant)}
62
+ className={cl("navds-date__modal", {
63
+ "navds-date__nested-modal": isInModal,
64
+ "navds-date": variant === "month",
65
+ })}
66
+ closeOnBackdropClick
67
+ >
68
+ <div className="navds-date__modal-body">
69
+ {children}
70
+ <Button
71
+ variant="tertiary"
72
+ onClick={() => modalRef?.current?.close()}
73
+ size="small"
74
+ >
75
+ {modalCloseButtonLabel(locale)}
76
+ </Button>
77
+ </div>
78
+ </Modal>
79
+ );
80
+ };
@@ -133,3 +133,86 @@ export const labelWeek = (localeCode?: string): string => {
133
133
  return `Uke:`;
134
134
  }
135
135
  };
136
+
137
+ const modalLabelSingle = (localeCode?: string): string => {
138
+ switch (localeCode) {
139
+ case "nb":
140
+ return `Velg dato`;
141
+ case "nn":
142
+ return `Vel dato`;
143
+ case "en-GB":
144
+ return `Pick a date`;
145
+ default:
146
+ return `Velg dato`;
147
+ }
148
+ };
149
+
150
+ const modalLabelMultiple = (localeCode?: string): string => {
151
+ switch (localeCode) {
152
+ case "nb":
153
+ return `Velg datoer`;
154
+ case "nn":
155
+ return `Vel datoar`;
156
+ case "en-GB":
157
+ return `Pick dates`;
158
+ default:
159
+ return `Velg datoer`;
160
+ }
161
+ };
162
+
163
+ const modalLabelRanged = (localeCode?: string): string => {
164
+ switch (localeCode) {
165
+ case "nb":
166
+ return `Velg start- og sluttdato`;
167
+ case "nn":
168
+ return `Vel start- og sluttdato`;
169
+ case "en-GB":
170
+ return `Pick a start and end date`;
171
+ default:
172
+ return `Velg start- og sluttdato`;
173
+ }
174
+ };
175
+
176
+ const modalLabelMonth = (localeCode?: string): string => {
177
+ switch (localeCode) {
178
+ case "nb":
179
+ return `Velg måned`;
180
+ case "nn":
181
+ return `Vel månad`;
182
+ case "en-GB":
183
+ return `Pick a month`;
184
+ default:
185
+ return `Velg måned`;
186
+ }
187
+ };
188
+
189
+ export const modalLabel = (
190
+ localeCode: string,
191
+ variant: "single" | "multiple" | "range" | "month"
192
+ ) => {
193
+ switch (variant) {
194
+ case "single":
195
+ return modalLabelSingle(localeCode);
196
+ case "multiple":
197
+ return modalLabelMultiple(localeCode);
198
+ case "range":
199
+ return modalLabelRanged(localeCode);
200
+ case "month":
201
+ return modalLabelMonth(localeCode);
202
+ default:
203
+ return modalLabelSingle(localeCode);
204
+ }
205
+ };
206
+
207
+ export const modalCloseButtonLabel = (localeCode?: string): string => {
208
+ switch (localeCode) {
209
+ case "nb":
210
+ return `Lukk`;
211
+ case "nn":
212
+ return `Lukk`;
213
+ case "en-GB":
214
+ return `Close`;
215
+ default:
216
+ return `Lukk`;
217
+ }
218
+ };
@@ -33,7 +33,7 @@ export const Combobox = forwardRef<
33
33
 
34
34
  const toggleListButtonRef = useRef<HTMLButtonElement>(null);
35
35
 
36
- const { currentOption, toggleIsListOpen } = useFilteredOptionsContext();
36
+ const { activeDecendantId, toggleIsListOpen } = useFilteredOptionsContext();
37
37
  const { selectedOptions } = useSelectedOptionsContext();
38
38
 
39
39
  const {
@@ -92,7 +92,7 @@ export const Combobox = forwardRef<
92
92
  "navds-combobox__wrapper-inner navds-text-field__input",
93
93
  {
94
94
  "navds-combobox__wrapper-inner--virtually-unfocused":
95
- currentOption !== null,
95
+ activeDecendantId !== null,
96
96
  }
97
97
  )}
98
98
  onClick={focusInput}
@@ -65,7 +65,7 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
65
65
  size,
66
66
  }}
67
67
  >
68
- <CustomOptionsProvider>
68
+ <CustomOptionsProvider value={{ isMultiSelect }}>
69
69
  <SelectedOptionsProvider
70
70
  value={{
71
71
  allowNewValues,
@@ -81,7 +81,6 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
81
81
  filteredOptions,
82
82
  isListOpen,
83
83
  isLoading,
84
- isMultiSelect,
85
84
  options,
86
85
  }}
87
86
  >
@@ -6,6 +6,7 @@ import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsCon
6
6
  import { useInputContext } from "../Input/inputContext";
7
7
  import { Loader } from "../../../loader";
8
8
  import { BodyShort, Label } from "../../../typography";
9
+ import filteredOptionsUtil from "./filtered-options-util";
9
10
 
10
11
  const FilteredOptions = () => {
11
12
  const {
@@ -18,25 +19,25 @@ const FilteredOptions = () => {
18
19
  isLoading,
19
20
  isListOpen,
20
21
  filteredOptions,
21
- filteredOptionsIndex,
22
- filteredOptionsRef,
22
+ setFilteredOptionsRef,
23
23
  isMouseLastUsedInputDevice,
24
24
  setIsMouseLastUsedInputDevice,
25
25
  isValueNew,
26
- setFilteredOptionsIndex,
27
26
  toggleIsListOpen,
27
+ activeDecendantId,
28
+ virtualFocus,
28
29
  } = useFilteredOptionsContext();
29
30
  const { isMultiSelect, selectedOptions, toggleOption } =
30
31
  useSelectedOptionsContext();
31
32
 
32
33
  return (
33
34
  <ul
34
- ref={filteredOptionsRef}
35
+ ref={setFilteredOptionsRef}
35
36
  className={cl("navds-combobox__list", {
36
37
  "navds-combobox__list--closed": !isListOpen,
37
38
  "navds-combobox__list--with-hover": isMouseLastUsedInputDevice,
38
39
  })}
39
- id={`${id}-filtered-options`}
40
+ id={filteredOptionsUtil.getFilteredOptionsId(id)}
40
41
  role="listbox"
41
42
  tabIndex={-1}
42
43
  >
@@ -45,7 +46,8 @@ const FilteredOptions = () => {
45
46
  className="navds-combobox__list-item--loading"
46
47
  role="option"
47
48
  aria-selected={false}
48
- id={`${id}-is-loading`}
49
+ id={filteredOptionsUtil.getIsLoadingId(id)}
50
+ data-no-focus="true"
49
51
  >
50
52
  <Loader aria-label="Søker..." />
51
53
  </li>
@@ -54,8 +56,12 @@ const FilteredOptions = () => {
54
56
  <li
55
57
  tabIndex={-1}
56
58
  onMouseMove={() => {
57
- if (filteredOptionsIndex !== -1) {
58
- setFilteredOptionsIndex(-1);
59
+ if (
60
+ activeDecendantId !== filteredOptionsUtil.getAddNewOptionId(id)
61
+ ) {
62
+ virtualFocus.moveFocusToElement(
63
+ filteredOptionsUtil.getAddNewOptionId(id)
64
+ );
59
65
  setIsMouseLastUsedInputDevice(true);
60
66
  }
61
67
  }}
@@ -64,10 +70,10 @@ const FilteredOptions = () => {
64
70
  if (!isMultiSelect && !selectedOptions.includes(value))
65
71
  toggleIsListOpen(false);
66
72
  }}
67
- id={`${id}-combobox-new-option`}
73
+ id={filteredOptionsUtil.getAddNewOptionId(id)}
68
74
  className={cl("navds-combobox__list-item__new-option", {
69
75
  "navds-combobox__list-item__new-option--focus":
70
- filteredOptionsIndex === -1,
76
+ activeDecendantId === filteredOptionsUtil.getAddNewOptionId(id),
71
77
  })}
72
78
  role="option"
73
79
  aria-selected={false}
@@ -86,24 +92,30 @@ const FilteredOptions = () => {
86
92
  className="navds-combobox__list-item__no-options"
87
93
  role="option"
88
94
  aria-selected={false}
89
- id={`${id}-no-hits`}
95
+ id={filteredOptionsUtil.getNoHitsId(id)}
96
+ data-no-focus="true"
90
97
  >
91
98
  Ingen søketreff
92
99
  </li>
93
100
  )}
94
- {filteredOptions.map((option, index) => (
101
+ {filteredOptions.map((option) => (
95
102
  <li
96
103
  className={cl("navds-combobox__list-item", {
97
- "navds-combobox__list-item--focus": index === filteredOptionsIndex,
104
+ "navds-combobox__list-item--focus":
105
+ activeDecendantId === filteredOptionsUtil.getOptionId(id, option),
98
106
  "navds-combobox__list-item--selected":
99
107
  selectedOptions.includes(option),
100
108
  })}
101
- id={`${id}-option-${option.replace(" ", "-")}`}
109
+ id={filteredOptionsUtil.getOptionId(id, option)}
102
110
  key={option}
103
111
  tabIndex={-1}
104
112
  onMouseMove={() => {
105
- if (filteredOptionsIndex !== index) {
106
- setFilteredOptionsIndex(index);
113
+ if (
114
+ activeDecendantId !== filteredOptionsUtil.getOptionId(id, option)
115
+ ) {
116
+ virtualFocus.moveFocusToElement(
117
+ filteredOptionsUtil.getOptionId(id, option)
118
+ );
107
119
  setIsMouseLastUsedInputDevice(true);
108
120
  }
109
121
  }}