@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.
- package/_docs.json +1794 -1749
- package/cjs/date/context/useDateInputContext.js +1 -5
- package/cjs/date/datepicker/DatePicker.js +26 -25
- package/cjs/date/hooks/useDatepicker.js +9 -17
- package/cjs/date/hooks/useMonthPicker.js +9 -17
- package/cjs/date/hooks/useRangeDatepicker.js +9 -20
- package/cjs/date/monthpicker/MonthPicker.js +11 -6
- package/cjs/date/{DateInput.js → parts/DateInput.js} +14 -10
- package/cjs/date/parts/DateWrapper.js +55 -0
- package/cjs/date/utils/labels.js +77 -1
- package/cjs/form/combobox/Combobox.js +2 -2
- package/cjs/form/combobox/ComboboxProvider.js +1 -2
- package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +15 -14
- package/cjs/form/combobox/FilteredOptions/filtered-options-util.js +24 -0
- package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +23 -106
- package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js +55 -0
- package/cjs/form/combobox/Input/Input.js +22 -13
- package/cjs/form/combobox/customOptionsContext.js +2 -3
- package/cjs/modal/Modal.js +4 -1
- package/cjs/popover/Popover.js +5 -7
- package/cjs/util/useMedia.js +30 -0
- package/esm/date/context/useDateInputContext.d.ts +6 -2
- package/esm/date/context/useDateInputContext.js +1 -5
- package/esm/date/context/useDateInputContext.js.map +1 -1
- package/esm/date/datepicker/DatePicker.d.ts +1 -1
- package/esm/date/datepicker/DatePicker.js +28 -27
- package/esm/date/datepicker/DatePicker.js.map +1 -1
- package/esm/date/datepicker/types.d.ts +0 -5
- package/esm/date/hooks/useDatepicker.d.ts +8 -5
- package/esm/date/hooks/useDatepicker.js +10 -18
- package/esm/date/hooks/useDatepicker.js.map +1 -1
- package/esm/date/hooks/useMonthPicker.d.ts +7 -4
- package/esm/date/hooks/useMonthPicker.js +10 -18
- package/esm/date/hooks/useMonthPicker.js.map +1 -1
- package/esm/date/hooks/useRangeDatepicker.d.ts +9 -3
- package/esm/date/hooks/useRangeDatepicker.js +10 -21
- package/esm/date/hooks/useRangeDatepicker.js.map +1 -1
- package/esm/date/index.d.ts +1 -1
- package/esm/date/index.js.map +1 -1
- package/esm/date/monthpicker/MonthPicker.d.ts +1 -1
- package/esm/date/monthpicker/MonthPicker.js +13 -8
- package/esm/date/monthpicker/MonthPicker.js.map +1 -1
- package/esm/date/monthpicker/types.d.ts +0 -5
- package/esm/date/{DateInput.d.ts → parts/DateInput.d.ts} +5 -1
- package/esm/date/{DateInput.js → parts/DateInput.js} +15 -11
- package/esm/date/parts/DateInput.js.map +1 -0
- package/esm/date/parts/DateWrapper.d.ts +15 -0
- package/esm/date/parts/DateWrapper.js +26 -0
- package/esm/date/parts/DateWrapper.js.map +1 -0
- package/esm/date/utils/labels.d.ts +2 -0
- package/esm/date/utils/labels.js +74 -0
- package/esm/date/utils/labels.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/ComboboxProvider.js +1 -2
- package/esm/form/combobox/ComboboxProvider.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/FilteredOptions.js +15 -14
- package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/filtered-options-util.d.ts +12 -0
- package/esm/form/combobox/FilteredOptions/filtered-options-util.js +23 -0
- package/esm/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -0
- package/esm/form/combobox/FilteredOptions/filteredOptionsContext.d.ts +10 -13
- package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +24 -107
- package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/useVirtualFocus.d.ts +15 -0
- package/esm/form/combobox/FilteredOptions/useVirtualFocus.js +54 -0
- package/esm/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -0
- package/esm/form/combobox/Input/Input.js +22 -13
- package/esm/form/combobox/Input/Input.js.map +1 -1
- package/esm/form/combobox/customOptionsContext.d.ts +4 -1
- package/esm/form/combobox/customOptionsContext.js +2 -3
- package/esm/form/combobox/customOptionsContext.js.map +1 -1
- package/esm/modal/Modal.js +4 -1
- package/esm/modal/Modal.js.map +1 -1
- package/esm/popover/Popover.d.ts +0 -5
- package/esm/popover/Popover.js +5 -7
- package/esm/popover/Popover.js.map +1 -1
- package/esm/util/useMedia.d.ts +8 -0
- package/esm/util/useMedia.js +27 -0
- package/esm/util/useMedia.js.map +1 -0
- package/package.json +3 -3
- package/src/date/context/useDateInputContext.tsx +5 -5
- package/src/date/datepicker/DatePicker.tsx +58 -65
- package/src/date/datepicker/datepicker.stories.tsx +37 -46
- package/src/date/datepicker/types.ts +0 -5
- package/src/date/hooks/useDatepicker.tsx +20 -25
- package/src/date/hooks/useMonthPicker.tsx +18 -24
- package/src/date/hooks/useRangeDatepicker.tsx +27 -30
- package/src/date/index.ts +1 -1
- package/src/date/monthpicker/MonthPicker.tsx +39 -43
- package/src/date/monthpicker/types.ts +0 -5
- package/src/date/{DateInput.tsx → parts/DateInput.tsx} +23 -12
- package/src/date/parts/DateWrapper.tsx +80 -0
- package/src/date/utils/labels.ts +83 -0
- package/src/form/combobox/Combobox.tsx +2 -2
- package/src/form/combobox/ComboboxProvider.tsx +1 -2
- package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +28 -16
- package/src/form/combobox/FilteredOptions/filtered-options-util.ts +38 -0
- package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +70 -140
- package/src/form/combobox/FilteredOptions/useVirtualFocus.ts +87 -0
- package/src/form/combobox/Input/Input.tsx +22 -18
- package/src/form/combobox/customOptionsContext.tsx +10 -5
- package/src/guide-panel/guidepanel.stories.tsx +2 -2
- package/src/modal/Modal.tsx +4 -1
- package/src/popover/Popover.tsx +4 -12
- package/src/util/__tests__/useMedia.test.tsx +19 -0
- package/src/util/useMedia.ts +38 -0
- package/cjs/date/hooks/useEscape.js +0 -23
- package/cjs/date/hooks/useOutsideClickHandler.js +0 -26
- package/esm/date/DateInput.js.map +0 -1
- package/esm/date/hooks/useEscape.d.ts +0 -2
- package/esm/date/hooks/useEscape.js +0 -20
- package/esm/date/hooks/useEscape.js.map +0 -1
- package/esm/date/hooks/useOutsideClickHandler.d.ts +0 -1
- package/esm/date/hooks/useOutsideClickHandler.js +0 -23
- package/esm/date/hooks/useOutsideClickHandler.js.map +0 -1
- package/src/date/hooks/useEscape.tsx +0 -30
- 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, {
|
|
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
|
-
> & {
|
|
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
|
-
> & {
|
|
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
|
|
235
|
+
const [anchorRef, setAnchorRef] = useState<HTMLButtonElement | null>(null);
|
|
225
236
|
|
|
226
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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={
|
|
114
|
+
ref={mergedRef}
|
|
114
115
|
className={cl("navds-date__wrapper", wrapperClassName)}
|
|
115
116
|
>
|
|
116
117
|
{children}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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 {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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
|
|
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 ?
|
|
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={() =>
|
|
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
|
+
};
|
package/src/date/utils/labels.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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={
|
|
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={
|
|
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={
|
|
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 (
|
|
58
|
-
|
|
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={
|
|
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
|
-
|
|
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={
|
|
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
|
|
101
|
+
{filteredOptions.map((option) => (
|
|
95
102
|
<li
|
|
96
103
|
className={cl("navds-combobox__list-item", {
|
|
97
|
-
"navds-combobox__list-item--focus":
|
|
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={
|
|
109
|
+
id={filteredOptionsUtil.getOptionId(id, option)}
|
|
102
110
|
key={option}
|
|
103
111
|
tabIndex={-1}
|
|
104
112
|
onMouseMove={() => {
|
|
105
|
-
if (
|
|
106
|
-
|
|
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
|
}}
|