Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ Refactor eventhandling i datepicker-hooks #1907

Merged
merged 9 commits into from
Mar 31, 2023
Merged
5 changes: 5 additions & 0 deletions .changeset/clever-candles-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@navikt/ds-react": patch
---

:recycle: Refactor event-handling i datepicker-hooks
2 changes: 1 addition & 1 deletion @navikt/core/react/src/date/DateInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import cl from "clsx";
import React, { forwardRef, InputHTMLAttributes } from "react";
import { BodyShort, Button, ErrorMessage, Label, omit } from "..";
import { FormFieldProps, useFormField } from "../form/useFormField";
import { useDateInputContext } from "./hooks";
import { useDateInputContext } from "./context";

export interface DateInputProps
extends FormFieldProps,
Expand Down
5 changes: 5 additions & 0 deletions @navikt/core/react/src/date/context/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { useDateInputContext, DateContext } from "./useDateInputContext";
export {
useSharedMonthContext,
SharedMonthProvider,
} from "./useSharedMonthContext";
6 changes: 3 additions & 3 deletions @navikt/core/react/src/date/datepicker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import {
} from "react-day-picker";
import { omit, Popover, useId } from "../..";
import { DateInputType, DatePickerInput } from "../DateInput";
import { DateContext } from "../hooks";
import { DateContext } from "../context";
import { getLocaleFromString, labels } from "../utils";
import { Caption, DropdownCaption } from "./caption";
import DatePickerStandalone, {
DatePickerStandaloneType,
} from "./DatePickerStandalone";
import { DayButton } from "./DayButton";
import { Head } from "./Head";
import { TableHead } from "./TableHead";

export type ConditionalModeProps =
| {
Expand Down Expand Up @@ -221,7 +221,7 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
components={{
Caption: dropdownCaption ? DropdownCaption : Caption,
Day: DayButton,
Head: Head,
Head: TableHead,
}}
className={cl("navds-date", className)}
classNames={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { omit } from "../..";
import { getLocaleFromString, labels } from "../utils";
import { Caption, DropdownCaption } from "./caption";
import { ConditionalModeProps, DatePickerDefaultProps } from "./DatePicker";
import { Head } from "./Head";
import { TableHead } from "./TableHead";

interface DatePickerStandaloneDefaultProps
extends Omit<
Expand Down Expand Up @@ -100,7 +100,7 @@ export const DatePickerStandalone: DatePickerStandaloneType = forwardRef<
selected={selected ?? selectedDates}
components={{
Caption: dropdownCaption ? DropdownCaption : Caption,
Head: Head,
Head: TableHead,
}}
className="navds-date"
classNames={{ vhidden: "navds-sr-only" }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import { HeadRow, useDayPicker } from "react-day-picker";

/** Render the table head. */
export function Head(): JSX.Element {
export function TableHead(): JSX.Element {
const { classNames, styles, components } = useDayPicker();
const HeadRowComponent = components?.HeadRow ?? HeadRow;
return (
Expand Down
42 changes: 21 additions & 21 deletions @navikt/core/react/src/date/datepicker/datepicker.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,6 @@ const disabledDays = [
export default {
title: "ds-react/Datepicker",
component: DatePicker,
argTypes: {
size: {
control: {
type: "radio",
options: ["medium", "small"],
},
},
locale: {
control: {
type: "radio",
options: ["nb", "nn", "en"],
},
},
mode: {
defaultValue: "single",
control: {
type: "radio",
options: ["single", "multiple", "range"],
},
},
},
};

export const Default = {
Expand Down Expand Up @@ -123,6 +102,27 @@ export const Default = {
inputfield: true,
standalone: false,
openOnFocus: true,
mode: "single",
},
argTypes: {
size: {
control: {
type: "radio",
options: ["medium", "small"],
},
},
locale: {
control: {
type: "radio",
options: ["nb", "nn", "en"],
},
},
mode: {
control: {
type: "radio",
options: ["single", "multiple", "range"],
},
},
},
};

Expand Down
5 changes: 0 additions & 5 deletions @navikt/core/react/src/date/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,3 @@ export { useRangeDatepicker as UNSAFE_useRangeDatepicker } from "./useRangeDatep
export type { RangeValidationT } from "./useRangeDatepicker";
export { useMonthpicker as UNSAFE_useMonthpicker } from "./useMonthPicker";
export type { MonthValidationT } from "./useMonthPicker";
export { useDateInputContext, DateContext } from "./useDateInputContext";
export {
useSharedMonthContext,
SharedMonthProvider,
} from "./useSharedMonthContext";
70 changes: 15 additions & 55 deletions @navikt/core/react/src/date/hooks/useDatepicker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import differenceInCalendarDays from "date-fns/differenceInCalendarDays";
import isWeekend from "date-fns/isWeekend";
import React, { useCallback, useEffect, useRef, useState } from "react";
import React, { useRef, useState } from "react";
import { DayClickEventHandler, isMatch } from "react-day-picker";
import { DateInputProps } from "../DateInput";
import { DatePickerProps } from "../datepicker/DatePicker";
Expand All @@ -10,6 +10,8 @@ import {
isValidDate,
parseDate,
} from "../utils";
import { useEscape } from "./useEscape";
import { useOutsideClickHandler } from "./useOutsideClickHandler";

export interface UseDatepickerOptions
extends Pick<
Expand Down Expand Up @@ -134,7 +136,7 @@ export const useDatepicker = (
const locale = getLocaleFromString(_locale);

const inputRef = useRef<HTMLInputElement>(null);
const daypickerRef = useRef<HTMLDivElement>(null);
const [daypickerRef, setDaypickerRef] = useState<HTMLDivElement>();

const [defaultSelected, setDefaultSelected] = useState(_defaultSelected);

Expand All @@ -148,45 +150,21 @@ export const useDatepicker = (
: "";
const [inputValue, setInputValue] = useState(defaultInputValue);

useOutsideClickHandler(open, setOpen, [
daypickerRef,
inputRef.current,
inputRef.current?.nextSibling,
]);

useEscape(open, setOpen, inputRef);

const updateDate = (date?: Date) => {
onDateChange?.(date);
setSelectedDay(date);
};

const updateValidation = (val: Partial<DateValidationT> = {}) => {
const msg = getValidationMessage(val);
onValidate?.(msg);
};

const handleFocusIn = useCallback(
(e) => {
/* Workaround for shadow-dom users (open) */
const composed = e.composedPath?.()?.[0];
if (!e?.target || !e?.target?.nodeType || !composed) {
return;
}

![
daypickerRef.current,
inputRef.current,
inputRef.current?.nextSibling,
].some(
(element) => element?.contains(e.target) || element?.contains(composed)
) &&
open &&
setOpen(false);
},
[open]
);

useEffect(() => {
window.addEventListener("focusin", handleFocusIn);
window.addEventListener("pointerdown", handleFocusIn);
return () => {
window?.removeEventListener?.("focusin", handleFocusIn);
window?.removeEventListener?.("pointerdown", handleFocusIn);
};
}, [handleFocusIn]);
const updateValidation = (val: Partial<DateValidationT> = {}) =>
onValidate?.(getValidationMessage(val));

const reset = () => {
updateDate(defaultSelected);
Expand Down Expand Up @@ -300,24 +278,6 @@ export const useDatepicker = (
setMonth(defaultMonth ?? day);
};

const handleClose = useCallback(() => {
setOpen(false);
inputRef.current && inputRef.current.focus();
}, []);

const escape = useCallback(
(e) => open && e.key === "Escape" && handleClose(),
[handleClose, open]
);

useEffect(() => {
window.addEventListener("keydown", escape, false);

return () => {
window.removeEventListener("keydown", escape, false);
};
}, [escape]);

const datepickerProps = {
month,
onMonthChange: (month) => setMonth(month),
Expand All @@ -331,7 +291,7 @@ export const useDatepicker = (
onOpenToggle: () => setOpen((x) => !x),
disabled,
disableWeekends,
ref: daypickerRef,
ref: setDaypickerRef,
};

const inputProps = {
Expand Down
25 changes: 25 additions & 0 deletions @navikt/core/react/src/date/hooks/useEscape.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { useCallback, useEffect } from "react";

export const useEscape = (
open: boolean,
setOpen: React.Dispatch<React.SetStateAction<boolean>>,
focusRef: any
) => {
const handleClose = useCallback(() => {
setOpen(false);
focusRef?.current && focusRef.current.focus();
}, [focusRef, setOpen]);

const escape = useCallback(
(e) => open && e.key === "Escape" && handleClose(),
[handleClose, open]
);

useEffect(() => {
window.addEventListener("keydown", escape, false);

return () => {
window.removeEventListener("keydown", escape, false);
};
}, [escape]);
};
69 changes: 15 additions & 54 deletions @navikt/core/react/src/date/hooks/useMonthPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import React, { useRef, useState } from "react";
import { DateInputProps } from "../DateInput";
import { MonthPickerProps } from "../monthpicker/MonthPicker";
import {
Expand All @@ -8,6 +8,8 @@ import {
isValidDate,
parseDate,
} from "../utils";
import { useEscape } from "./useEscape";
import { useOutsideClickHandler } from "./useOutsideClickHandler";

export interface UseMonthPickerOptions
extends Pick<
Expand Down Expand Up @@ -118,7 +120,7 @@ export const useMonthpicker = (
const locale = getLocaleFromString(_locale);

const inputRef = useRef<HTMLInputElement>(null);
const monthpickerRef = useRef<HTMLDivElement>(null);
const [monthpickerRef, setMonthpickerRef] = useState<HTMLDivElement>();

// Initialize states
const [year, setYear] = useState(defaultSelected ?? defaultYear ?? today);
Expand All @@ -131,44 +133,21 @@ export const useMonthpicker = (

const [inputValue, setInputValue] = useState(defaultInputValue);

useOutsideClickHandler(open, setOpen, [
monthpickerRef,
inputRef.current,
inputRef.current?.nextSibling,
]);

useEscape(open, setOpen, inputRef);

const updateMonth = (date?: Date) => {
onMonthChange?.(date);
setSelectedMonth(date);
};

const updateValidation = (val: Partial<MonthValidationT> = {}) => {
const msg = getValidationMessage(val);
onValidate?.(msg);
};

const handleFocusIn = useCallback(
(e) => {
/* Workaround for shadow-dom users (open) */
const composed = e.composedPath?.()?.[0];
if (!e?.target || !e?.target?.nodeType || !composed) {
return;
}
![
monthpickerRef.current,
inputRef.current,
inputRef.current?.nextSibling,
].some(
(element) => element?.contains(e.target) || element?.contains(composed)
) &&
open &&
setOpen(false);
},
[open]
);

useEffect(() => {
window.addEventListener("focusin", handleFocusIn);
window.addEventListener("pointerdown", handleFocusIn);
return () => {
window?.removeEventListener?.("focusin", handleFocusIn);
window?.removeEventListener?.("pointerdown", handleFocusIn);
};
}, [handleFocusIn]);
const updateValidation = (val: Partial<MonthValidationT> = {}) =>
onValidate?.(getValidationMessage(val));

const reset = () => {
updateMonth(defaultSelected);
Expand Down Expand Up @@ -294,24 +273,6 @@ export const useMonthpicker = (
setYear(month);
};

const handleClose = useCallback(() => {
setOpen(false);
inputRef.current && inputRef.current.focus();
}, []);

const escape = useCallback(
(e) => open && e.key === "Escape" && handleClose(),
[handleClose, open]
);

useEffect(() => {
window.addEventListener("keydown", escape, false);

return () => {
window.removeEventListener("keydown", escape, false);
};
}, [escape]);

const monthpickerProps = {
year,
onYearChange: (y?: Date) => setYear(y ?? today),
Expand All @@ -323,7 +284,7 @@ export const useMonthpicker = (
open,
onOpenToggle: () => setOpen((x) => !x),
disabled,
ref: monthpickerRef,
ref: setMonthpickerRef,
};

const inputProps = {
Expand Down
Loading