diff --git a/src/shared/components/ui/Calendar/Calendar.tsx b/src/shared/components/ui/Calendar/Calendar.tsx index 7c3bc2d..9aa0672 100644 --- a/src/shared/components/ui/Calendar/Calendar.tsx +++ b/src/shared/components/ui/Calendar/Calendar.tsx @@ -8,7 +8,7 @@ import { tv } from "tailwind-variants"; import { useFieldController } from "~/hooks"; import { WithHookForm, WithoutHookForm } from "~/shared"; -import { currentTimezone } from "~/utils"; +import { currentTimezone, findFirstEnabledDate } from "~/utils"; import { CalendarHeader } from "./CalendarHeader"; import { CalendarTable } from "./CalendarTable"; @@ -50,13 +50,19 @@ export const Calendar = ({ ); }} onFocusChange={controller?.field.onBlur} - // TODO: ref={field.ref} Need to pass this down to CalendarTable to focus on error {...props} aria-labelledby={label} className={classNames.container} > - + { + const firstEnabledDate = findFirstEnabledDate(el); + if (firstEnabledDate) { + return controller?.field.ref(firstEnabledDate); + } + }} + /> {errorMessage && ( diff --git a/src/shared/components/ui/Calendar/CalendarTable.tsx b/src/shared/components/ui/Calendar/CalendarTable.tsx index ca43fc5..f5af7b3 100644 --- a/src/shared/components/ui/Calendar/CalendarTable.tsx +++ b/src/shared/components/ui/Calendar/CalendarTable.tsx @@ -1,3 +1,4 @@ +import { forwardRef } from "react"; import { CalendarCell, CalendarGrid, @@ -16,7 +17,7 @@ const calendarTable = tv({ }); const calendarCell = tv({ - base: "mx-auto my-0.5 flex aspect-square w-8 items-center justify-center rounded-lg text-sm font-semibold text-gray-600 sm:w-11", + base: "mx-auto my-0.5 flex aspect-square w-8 items-center justify-center rounded-lg text-sm font-semibold text-gray-600 focus:outline-none focus:ring-2 focus:ring-[#005FCC] sm:w-11", variants: { state: { default: "", @@ -35,33 +36,42 @@ const calendarCell = tv({ const { root, dayOfWeek } = calendarTable(); -export const CalendarTable = (props: CalendarGridProps) => { - return ( - - - {(day) => ( - {day} - )} - - - {(date) => ( - - calendarCell({ - state: isSelected - ? "selected" - : isDisabled - ? "disabled" - : isUnavailable - ? "unavailable" - : "default", - hoverable: !isUnavailable && !isDisabled && !isSelected, - }) - } - date={date} - /> - )} - - - ); -}; +export const CalendarTable = forwardRef( + (props, ref) => { + return ( + + + {(day) => ( + + {day} + + )} + + + {(date) => ( + + calendarCell({ + state: isSelected + ? "selected" + : isDisabled + ? "disabled" + : isUnavailable + ? "unavailable" + : "default", + hoverable: !isUnavailable && !isDisabled && !isSelected, + }) + } + date={date} + /> + )} + + + ); + }, +); diff --git a/src/shared/components/ui/DateField/DateField.tsx b/src/shared/components/ui/DateField/DateField.tsx index d937078..c4c456c 100644 --- a/src/shared/components/ui/DateField/DateField.tsx +++ b/src/shared/components/ui/DateField/DateField.tsx @@ -14,7 +14,7 @@ import { tv } from "tailwind-variants"; import { useFieldController } from "~/hooks"; import { WithHookForm, WithoutHookForm } from "~/shared"; -import { currentTimezone } from "~/utils"; +import { currentTimezone, isFirstChild } from "~/utils"; const classNames = tv({ slots: { @@ -62,7 +62,11 @@ export const DateField = ({ {(segment) => ( - + isFirstChild(el) && controller?.field.ref(el)} + className={classNames.dateSegment} + segment={segment} + /> )} {errorMessage && ( diff --git a/src/shared/components/ui/DatePicker/DatePicker.tsx b/src/shared/components/ui/DatePicker/DatePicker.tsx index bb95866..32aa793 100644 --- a/src/shared/components/ui/DatePicker/DatePicker.tsx +++ b/src/shared/components/ui/DatePicker/DatePicker.tsx @@ -19,7 +19,7 @@ import { tv } from "tailwind-variants"; import { useFieldController } from "~/hooks"; import { WithHookForm, WithoutHookForm } from "~/shared"; -import { currentTimezone } from "~/utils"; +import { currentTimezone, isFirstChild } from "~/utils"; import { CalendarHeader } from "../Calendar/CalendarHeader"; import { CalendarTable } from "../Calendar/CalendarTable"; @@ -73,12 +73,14 @@ export const DatePicker = ({ {(segment) => ( - + isFirstChild(el) && controller?.field.ref(el)} + className={classNames.dateSegment} + segment={segment} + /> )} - + {errorMessage && ( diff --git a/src/shared/components/ui/DateRangePicker/DateRangePicker.tsx b/src/shared/components/ui/DateRangePicker/DateRangePicker.tsx index 92c866b..7fd4d53 100644 --- a/src/shared/components/ui/DateRangePicker/DateRangePicker.tsx +++ b/src/shared/components/ui/DateRangePicker/DateRangePicker.tsx @@ -19,6 +19,7 @@ import { tv } from "tailwind-variants"; import { currentTimezone, + isFirstChild, useFieldController, WithHookForm, WithoutHookForm, @@ -77,7 +78,11 @@ export const DateRangePicker = ({ {(segment) => ( - + isFirstChild(el) && controller?.field.ref(el)} + className={classNames.dateSegment} + segment={segment} + /> )} diff --git a/src/shared/components/ui/RangeCalendar/RangeCalendar.tsx b/src/shared/components/ui/RangeCalendar/RangeCalendar.tsx index ccaeebd..3ce581c 100644 --- a/src/shared/components/ui/RangeCalendar/RangeCalendar.tsx +++ b/src/shared/components/ui/RangeCalendar/RangeCalendar.tsx @@ -12,7 +12,7 @@ import { tv } from "tailwind-variants"; import { useFieldController } from "~/hooks"; import { WithHookForm, WithoutHookForm } from "~/shared"; -import { currentTimezone } from "~/utils"; +import { currentTimezone, findFirstEnabledDate } from "~/utils"; import { CalendarHeader } from "../Calendar/CalendarHeader"; import { CalendarTable } from "../Calendar/CalendarTable"; @@ -65,7 +65,14 @@ export const RangeCalendar = ({ >
- + { + const firstEnabledDate = findFirstEnabledDate(el); + if (firstEnabledDate) { + return controller?.field.ref(firstEnabledDate); + } + }} + />
diff --git a/src/shared/components/ui/TimeField/TimeField.tsx b/src/shared/components/ui/TimeField/TimeField.tsx index 3d7059b..cbe25b9 100644 --- a/src/shared/components/ui/TimeField/TimeField.tsx +++ b/src/shared/components/ui/TimeField/TimeField.tsx @@ -12,7 +12,12 @@ import { import { FieldValues } from "react-hook-form"; import { tv } from "tailwind-variants"; -import { useFieldController, WithHookForm, WithoutHookForm } from "~/shared"; +import { + isFirstChild, + useFieldController, + WithHookForm, + WithoutHookForm, +} from "~/shared"; const classNames = tv({ slots: { @@ -52,13 +57,16 @@ export const TimeField = ({ controller?.field.onChange(newTime.toString()); }} onBlur={controller?.field.onBlur} - ref={controller?.field.ref} {...props} > {(segment) => ( - + isFirstChild(el) && controller?.field.ref(el)} + className={classNames.dateSegment} + segment={segment} + /> )} {errorMessage && ( diff --git a/src/shared/utils/findFirstEnabledDate.ts b/src/shared/utils/findFirstEnabledDate.ts new file mode 100644 index 0000000..9141dbc --- /dev/null +++ b/src/shared/utils/findFirstEnabledDate.ts @@ -0,0 +1,8 @@ +export const findFirstEnabledDate = ( + table: HTMLTableElement | null, +): Element | null => { + return ( + table?.querySelector("td:not([aria-disabled])") + ?.children[0] ?? null + ); +}; diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts index f296343..c549f2b 100644 --- a/src/shared/utils/index.ts +++ b/src/shared/utils/index.ts @@ -1,6 +1,8 @@ export * from "./asyncTimeout"; export * from "./dateWithoutTimezone"; export * from "./forwardRef"; +export * from "./findFirstEnabledDate"; export * from "./isDateISOString"; +export * from "./isFirstChild"; export * from "./tw"; export * from "./validations"; diff --git a/src/shared/utils/isFirstChild.ts b/src/shared/utils/isFirstChild.ts new file mode 100644 index 0000000..0b58831 --- /dev/null +++ b/src/shared/utils/isFirstChild.ts @@ -0,0 +1,6 @@ +export const isFirstChild = (el: Element | null) => { + if (!el) return false; + return ( + el.parentElement && Array.from(el.parentElement.children).indexOf(el) === 0 + ); +};