Skip to content

Commit

Permalink
♻️ Refactor eventhandling i datepicker-hooks (#1907)
Browse files Browse the repository at this point in the history
  • Loading branch information
KenAJoh authored Mar 31, 2023
1 parent b5c8a94 commit 63fdacf
Show file tree
Hide file tree
Showing 20 changed files with 151 additions and 203 deletions.
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

0 comments on commit 63fdacf

Please sign in to comment.