From 588b485934e27db2f6bb12417c9b32aeb9c6a696 Mon Sep 17 00:00:00 2001 From: Taylor Noj Date: Mon, 24 Feb 2025 20:23:00 -0500 Subject: [PATCH 1/5] first pass at allowing one Datepicker to be open at a time --- .../components/src/DatePicker/DatePicker.tsx | 30 +++++++++++++++++++ .../components/src/InputDate/InputDate.tsx | 2 ++ .../src/InputDate/InputDate.types.ts | 1 + 3 files changed, 33 insertions(+) diff --git a/packages/components/src/DatePicker/DatePicker.tsx b/packages/components/src/DatePicker/DatePicker.tsx index eac94e48e9..cbd6562193 100644 --- a/packages/components/src/DatePicker/DatePicker.tsx +++ b/packages/components/src/DatePicker/DatePicker.tsx @@ -1,3 +1,4 @@ +/* eslint-disable max-statements */ import React, { ReactElement, useEffect, useRef, useState } from "react"; import classnames from "classnames"; import ReactDatePicker from "react-datepicker"; @@ -13,6 +14,8 @@ import { useFocusOnSelectedDate } from "./useFocusOnSelectedDate"; import { useAtlantisContext } from "../AtlantisContext"; interface BaseDatePickerProps { + /** Unique identifier for the datepicker */ + readonly id?: string; /** * The maximum selectable date. */ @@ -83,6 +86,9 @@ interface DatePickerInlineProps extends BaseDatePickerProps { type DatePickerProps = XOR; +const datePickerEventBus = new EventTarget(); +const DATEPICKER_OPEN_EVENT = "datepicker-open"; + /*eslint max-statements: ["error", 13]*/ export function DatePicker({ onChange, @@ -97,6 +103,7 @@ export function DatePicker({ maxDate, minDate, highlightDates, + id, }: DatePickerProps) { const { ref, focusOnSelectedDate } = useFocusOnSelectedDate(); const [open, setOpen] = useState(false); @@ -122,6 +129,26 @@ export function DatePicker({ useEffect(focusOnSelectedDate, [open]); } + useEffect(() => { + const handleOtherPickerOpen = (event: Event) => { + if (event instanceof CustomEvent && event.detail.id !== id) { + pickerRef.current?.setOpen(false); + } + }; + + datePickerEventBus.addEventListener( + DATEPICKER_OPEN_EVENT, + handleOtherPickerOpen, + ); + + return () => { + datePickerEventBus.removeEventListener( + DATEPICKER_OPEN_EVENT, + handleOtherPickerOpen, + ); + }; + }, [id]); + return (
(null); + const uniqueId = inputProps.id || `datepicker-${Math.random()}`; return ( { /** * A Date object value From c0e1452155c0b39209b31c183a752c06f795dc0e Mon Sep 17 00:00:00 2001 From: Taylor Noj Date: Mon, 24 Feb 2025 23:25:53 -0500 Subject: [PATCH 2/5] use useId instead to generate Datepicker ids --- packages/components/src/InputDate/InputDate.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/components/src/InputDate/InputDate.tsx b/packages/components/src/InputDate/InputDate.tsx index f39b166a7b..1d90f5323f 100644 --- a/packages/components/src/InputDate/InputDate.tsx +++ b/packages/components/src/InputDate/InputDate.tsx @@ -1,12 +1,13 @@ import omit from "lodash/omit"; -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useId, useRef, useState } from "react"; import { InputDateProps } from "./InputDate.types"; import { FieldActionsRef, FormField, Suffix } from "../FormField"; import { DatePicker } from "../DatePicker"; export function InputDate(inputProps: InputDateProps) { const formFieldActionsRef = useRef(null); - const uniqueId = inputProps.id || `datepicker-${Math.random()}`; + const generatedId = useId(); + const uniqueId = inputProps.id || `datepicker-${generatedId}`; return ( Date: Tue, 25 Feb 2025 15:20:58 -0500 Subject: [PATCH 3/5] use direct ref approach instead of events --- .../components/src/DatePicker/DatePicker.tsx | 75 ++++++++----------- .../components/src/InputDate/InputDate.tsx | 5 +- .../src/InputDate/InputDate.types.ts | 1 - 3 files changed, 32 insertions(+), 49 deletions(-) diff --git a/packages/components/src/DatePicker/DatePicker.tsx b/packages/components/src/DatePicker/DatePicker.tsx index cbd6562193..1e75006c9f 100644 --- a/packages/components/src/DatePicker/DatePicker.tsx +++ b/packages/components/src/DatePicker/DatePicker.tsx @@ -14,8 +14,6 @@ import { useFocusOnSelectedDate } from "./useFocusOnSelectedDate"; import { useAtlantisContext } from "../AtlantisContext"; interface BaseDatePickerProps { - /** Unique identifier for the datepicker */ - readonly id?: string; /** * The maximum selectable date. */ @@ -86,8 +84,7 @@ interface DatePickerInlineProps extends BaseDatePickerProps { type DatePickerProps = XOR; -const datePickerEventBus = new EventTarget(); -const DATEPICKER_OPEN_EVENT = "datepicker-open"; +const openDatePickerRef = { current: null as ReactDatePicker | null }; /*eslint max-statements: ["error", 13]*/ export function DatePicker({ @@ -103,7 +100,6 @@ export function DatePicker({ maxDate, minDate, highlightDates, - id, }: DatePickerProps) { const { ref, focusOnSelectedDate } = useFocusOnSelectedDate(); const [open, setOpen] = useState(false); @@ -122,33 +118,13 @@ export function DatePicker({ const datePickerClassNames = classnames(styles.datePicker, { [styles.inline]: inline, }); - const { pickerRef } = useEscapeKeyToCloseDatePicker(open, ref); + const { pickerRef } = useEscapeKeyToCloseDatePicker(open); if (smartAutofocus) { useRefocusOnActivator(open); useEffect(focusOnSelectedDate, [open]); } - useEffect(() => { - const handleOtherPickerOpen = (event: Event) => { - if (event instanceof CustomEvent && event.detail.id !== id) { - pickerRef.current?.setOpen(false); - } - }; - - datePickerEventBus.addEventListener( - DATEPICKER_OPEN_EVENT, - handleOtherPickerOpen, - ); - - return () => { - datePickerEventBus.removeEventListener( - DATEPICKER_OPEN_EVENT, - handleOtherPickerOpen, - ); - }; - }, [id]); - return (
, -): { pickerRef: React.RefObject } { +function useEscapeKeyToCloseDatePicker(open: boolean): { + pickerRef: React.RefObject; +} { const pickerRef = useRef(null); - const escFunction = (event: KeyboardEvent) => { - if (event.key === "Escape" && open) { - // Close the picker ourselves and prevent propagation so that ESC presses with the picker open - // do not close parent elements that may also be listening for ESC presses such as Modals - pickerRef.current?.setOpen(false); - event.stopPropagation(); - } - }; useEffect(() => { - ref.current?.addEventListener("keydown", escFunction); + const escFunction = (event: KeyboardEvent) => { + if (event.key === "Escape" && open) { + // Close the picker ourselves and prevent propagation so that ESC presses with the picker open + // do not close parent elements that may also be listening for ESC presses such as Modals + pickerRef.current?.setOpen(false); + event.stopPropagation(); + } + }; + + document.addEventListener("keydown", escFunction, true); return () => { - ref.current?.removeEventListener("keydown", escFunction); + document.removeEventListener("keydown", escFunction, true); }; - }, [open, ref, pickerRef]); + }, [open]); return { pickerRef, diff --git a/packages/components/src/InputDate/InputDate.tsx b/packages/components/src/InputDate/InputDate.tsx index 1d90f5323f..892ec972ff 100644 --- a/packages/components/src/InputDate/InputDate.tsx +++ b/packages/components/src/InputDate/InputDate.tsx @@ -1,17 +1,14 @@ import omit from "lodash/omit"; -import React, { useEffect, useId, useRef, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { InputDateProps } from "./InputDate.types"; import { FieldActionsRef, FormField, Suffix } from "../FormField"; import { DatePicker } from "../DatePicker"; export function InputDate(inputProps: InputDateProps) { const formFieldActionsRef = useRef(null); - const generatedId = useId(); - const uniqueId = inputProps.id || `datepicker-${generatedId}`; return ( { /** * A Date object value From d812a93dcaad5bd83e2df04b001abcd5905bba9a Mon Sep 17 00:00:00 2001 From: Taylor Noj Date: Tue, 25 Feb 2025 15:29:32 -0500 Subject: [PATCH 4/5] remove maxlines for file --- packages/components/src/DatePicker/DatePicker.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/src/DatePicker/DatePicker.tsx b/packages/components/src/DatePicker/DatePicker.tsx index 1e75006c9f..b9e99ed461 100644 --- a/packages/components/src/DatePicker/DatePicker.tsx +++ b/packages/components/src/DatePicker/DatePicker.tsx @@ -1,4 +1,3 @@ -/* eslint-disable max-statements */ import React, { ReactElement, useEffect, useRef, useState } from "react"; import classnames from "classnames"; import ReactDatePicker from "react-datepicker"; From 8da1e03ae4925bbddb26d9254035c466685e9f5d Mon Sep 17 00:00:00 2001 From: Taylor Noj Date: Tue, 25 Feb 2025 17:18:17 -0500 Subject: [PATCH 5/5] remove comments and add a DateRange story in InputGroup --- docs/components/InputGroup/Web.stories.tsx | 29 ++++++++++++++++++- .../components/src/DatePicker/DatePicker.tsx | 3 -- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/components/InputGroup/Web.stories.tsx b/docs/components/InputGroup/Web.stories.tsx index ee93f59263..263a61016c 100644 --- a/docs/components/InputGroup/Web.stories.tsx +++ b/docs/components/InputGroup/Web.stories.tsx @@ -1,8 +1,9 @@ -import React from "react"; +import React, { useState } from "react"; import { ComponentMeta, ComponentStory } from "@storybook/react"; import { InputGroup } from "@jobber/components/InputGroup"; import { InputTime } from "@jobber/components/InputTime"; import { InputText } from "@jobber/components/InputText"; +import { InputDate } from "@jobber/components/InputDate"; export default { title: "Components/Forms and Inputs/InputGroup/Web", @@ -49,6 +50,27 @@ const NestedTemplate: ComponentStory = args => { ); }; +const DateRangeTemplate: ComponentStory = args => { + const [startDate, setStartDate] = useState(new Date("2024-01-01")); + const [endDate, setEndDate] = useState(new Date("2024-12-31")); + + return ( + + + + + ); +}; + export const Basic = BasicTemplate.bind({}); Basic.args = { flowDirection: "vertical", @@ -58,3 +80,8 @@ export const Nested = NestedTemplate.bind({}); Nested.args = { flowDirection: "vertical", }; + +export const DateRange = DateRangeTemplate.bind({}); +DateRange.args = { + flowDirection: "horizontal", +}; diff --git a/packages/components/src/DatePicker/DatePicker.tsx b/packages/components/src/DatePicker/DatePicker.tsx index b9e99ed461..6e11742020 100644 --- a/packages/components/src/DatePicker/DatePicker.tsx +++ b/packages/components/src/DatePicker/DatePicker.tsx @@ -172,7 +172,6 @@ export function DatePicker({ } function handleCalendarOpen() { - // If there's already an open picker that's different from this one, close it if ( openDatePickerRef.current && openDatePickerRef.current !== pickerRef.current @@ -180,13 +179,11 @@ export function DatePicker({ openDatePickerRef.current.setOpen(false); } - // Set this picker as the currently open one openDatePickerRef.current = pickerRef.current; setOpen(true); } function handleCalendarClose() { - // Clear the ref if this picker is the one that's closing if (openDatePickerRef.current === pickerRef.current) { openDatePickerRef.current = null; }