@codecademy/gamut

68.2.268.2.3-alpha.794432.0
dist/DatePicker/Calendar/CalendarBody.js
+dist/DatePicker/Calendar/CalendarBody.jsNew file
+143
Index: package/dist/DatePicker/Calendar/CalendarBody.js
===================================================================
--- package/dist/DatePicker/Calendar/CalendarBody.js
+++ package/dist/DatePicker/Calendar/CalendarBody.js
@@ -0,0 +1,143 @@
+import { useCallback, useEffect, useMemo, useRef } from 'react';
+import * as React from 'react';
+import { useIsoFirstWeekday, useResolvedLocale } from '../utils/locale';
+import { getDatesWithRow, getMonthGrid, isDateDisabled, isDateInRange, isSameDay } from './utils/dateGrid';
+import { CalendarTable, DateCell, TableHeader } from './utils/elements';
+import { formatDateForAriaLabel, getWeekdayNames } from './utils/format';
+import { keyHandler } from './utils/keyHandler';
+import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
+export const CalendarBody = ({
+  displayDate,
+  selectedDate,
+  endDate = null,
+  disabledDates = [],
+  onDateSelect,
+  locale,
+  weekStartsOn,
+  labelledById,
+  focusedDate,
+  onFocusedDateChange,
+  onDisplayDateChange,
+  onEscapeKeyPress,
+  hasAdjacentMonthRight,
+  hasAdjacentMonthLeft,
+  focusGridSync
+}) => {
+  const resolvedLocale = useResolvedLocale(locale);
+  const firstWeekday = useIsoFirstWeekday(resolvedLocale, weekStartsOn);
+  const year = displayDate.getFullYear();
+  const month = displayDate.getMonth();
+  const weeks = getMonthGrid(year, month, firstWeekday);
+  const weekdayLabels = getWeekdayNames('short', resolvedLocale, firstWeekday);
+  const weekdayFullNames = getWeekdayNames('long', resolvedLocale, firstWeekday);
+  const buttonRefs = useRef(new Map());
+  const tableRef = useRef(null);
+  const datesWithRow = useMemo(() => getDatesWithRow(weeks), [weeks]);
+  const focusTarget = focusedDate ?? selectedDate;
+  const isToday = useCallback(date => date !== null && isSameDay(date, new Date()), []);
+  const focusButton = useCallback(date => {
+    if (date === null) return false;
+    const key = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
+    const el = buttonRefs.current.get(key);
+    if (!el) return false;
+    el.focus();
+    return true;
+  }, []);
+  useEffect(() => {
+    // Keep the roving tabindex / focused day aligned with `focusTarget` when it makes sense for a11y.
+    if (focusTarget === null) return;
+
+    // Standalone calendar (e.g. Storybook): always move DOM focus to the active day.
+    if (!focusGridSync) {
+      focusButton(focusTarget);
+      return;
+    }
+    const inGrid = tableRef.current?.contains(document.activeElement);
+    const requested = focusGridSync.gridFocusRequested;
+
+    // Focus is already in this grid (keyboard nav): update which day is focused as `focusTarget` changes.
+    if (inGrid) {
+      focusButton(focusTarget);
+      return;
+    }
+
+    // DatePicker opened via keyboard / ArrowDown: parent asked to move focus into the grid once.
+    if (requested) {
+      const success = focusButton(focusTarget);
+      if (success) {
+        focusGridSync.onGridFocusRequestHandled();
+      }
+    }
+    // If !inGrid && !requested (e.g. calendar opened with the mouse): leave focus on the input — do not call focusButton.
+  }, [focusTarget, focusButton, focusGridSync]);
+  const handleKeyDown = useCallback((e, date) => keyHandler({
+    e,
+    date,
+    onFocusedDateChange,
+    datesWithRow,
+    month,
+    year,
+    disabledDates,
+    onDateSelect,
+    onEscapeKeyPress,
+    onDisplayDateChange,
+    hasAdjacentMonthRight,
+    hasAdjacentMonthLeft
+  }), [onFocusedDateChange, datesWithRow, month, year, disabledDates, onDateSelect, onEscapeKeyPress, onDisplayDateChange, hasAdjacentMonthLeft, hasAdjacentMonthRight]);
+  const setButtonRef = useCallback((date, el) => {
+    const k = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
+    if (el) buttonRefs.current.set(k, el);else buttonRefs.current.delete(k);
+  }, []);
+  return /*#__PURE__*/_jsxs(CalendarTable, {
+    "aria-labelledby": labelledById,
+    ref: tableRef,
+    role: "grid",
+    children: [/*#__PURE__*/_jsx("thead", {
+      children: /*#__PURE__*/_jsx("tr", {
+        children: weekdayLabels.map((label, i) => /*#__PURE__*/_jsx(TableHeader, {
+          abbr: weekdayFullNames[i],
+          scope: "col",
+          children: label
+        }, label))
+      })
+    }), /*#__PURE__*/_jsx("tbody", {
+      children: weeks.map((week, rowIndex) => /*#__PURE__*/_jsx("tr", {
+        children: week.map((date, colIndex) => {
+          if (date === null) {
+            return (
+              /*#__PURE__*/
+              // eslint-disable-next-line jsx-a11y/control-has-associated-label
+              _jsx("td", {
+                role: "gridcell"
+              }, `empty-${rowIndex}-${colIndex}`)
+            );
+          }
+          const selected = isSameDay(date, selectedDate) || isSameDay(date, endDate);
+          const range = !!selectedDate && !!endDate;
+          const inRange = range && isDateInRange(date, selectedDate, endDate);
+          const disabled = isDateDisabled(date, disabledDates);
+          const today = isToday(date);
+          // this is making the selected date a differnet color bc it is focused, look into further
+          const isFocused = focusTarget !== null && isSameDay(date, focusTarget);
+          return /*#__PURE__*/_jsx(DateCell, {
+            "aria-label": formatDateForAriaLabel(date, resolvedLocale),
+            "aria-selected": selected,
+            isDisabled: disabled,
+            isInRange: inRange,
+            isRangeEnd: range && isSameDay(date, endDate),
+            isRangeStart: range && isSameDay(date, selectedDate),
+            isSelected: selected,
+            isToday: today,
+            ref: el => setButtonRef(date, el),
+            role: "gridcell",
+            tabIndex: isFocused ? 0 : -1,
+            onClick: () => onDateSelect(date),
+            onFocus: () => onFocusedDateChange?.(date),
+            onKeyDown: e => handleKeyDown(e, date),
+            children: date.getDate()
+          }, date.getTime());
+        })
+      }, week.join('-')))
+    })]
+  });
+};
\ No newline at end of file