@codecademy/gamut

68.2.268.2.3-alpha.794432.0
dist/DatePicker/DatePicker.js
+dist/DatePicker/DatePicker.jsNew file
+155
Index: package/dist/DatePicker/DatePicker.js
===================================================================
--- package/dist/DatePicker/DatePicker.js
+++ package/dist/DatePicker/DatePicker.js
@@ -0,0 +1,155 @@
+import { MiniArrowRightIcon } from '@codecademy/gamut-icons';
+import { useCallback, useId, useMemo, useRef, useState } from 'react';
+import { Box, FlexBox } from '../Box';
+import { PopoverContainer } from '../PopoverContainer';
+import { DatePickerCalendar } from './DatePickerCalendar';
+import { DatePickerProvider } from './DatePickerContext';
+import { DatePickerInput } from './DatePickerInput';
+import { isRangeProps } from './utils/dateSelect';
+import { useResolvedLocale } from './utils/locale';
+import { DEFAULT_DATE_PICKER_TRANSLATIONS } from './utils/translations';
+
+/**
+ * DatePicker: single-date or range. Holds shared state and provides it via context.
+ * Single: selectedDate, setSelectedDate. Range: startDate, endDate, setStartDate, setEndDate.
+ * With no children, renders default layout (input + calendar popover).
+ */
+import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
+export const DatePicker = props => {
+  const {
+    locale,
+    disabledDates = [],
+    placeholder,
+    mode,
+    children,
+    translations: translationsProp,
+    inputSize
+  } = props;
+  const [isCalendarOpen, setIsCalendarOpen] = useState(false);
+  const [focusGridSignal, setFocusGridSignal] = useState(false);
+  const [gridFocusRequested, setGridFocusRequested] = useState(false);
+  const [activeRangePart, setActiveRangePart] = useState(null);
+  const inputRef = useRef(null);
+  const dialogId = useId();
+  const calendarDialogId = `datepicker-dialog-${dialogId.replace(/:/g, '')}`;
+  const clearGridFocusRequest = useCallback(() => {
+    setGridFocusRequested(false);
+  }, []);
+  const resolvedLocale = useResolvedLocale(locale);
+  const openCalendar = useCallback(options => {
+    const moveFocus = options?.moveFocusIntoCalendar ?? false;
+    setIsCalendarOpen(true);
+    if (moveFocus) {
+      setGridFocusRequested(true);
+      setFocusGridSignal(signal => !signal);
+    } else {
+      setGridFocusRequested(false);
+    }
+  }, []);
+  const focusCalendarGrid = useCallback(() => {
+    setGridFocusRequested(true);
+    setFocusGridSignal(signal => !signal);
+  }, []);
+  const closeCalendar = useCallback(() => {
+    setIsCalendarOpen(false);
+    setActiveRangePart(null);
+    setGridFocusRequested(false);
+    inputRef.current?.focus();
+  }, []);
+  const startOrSelectedDate = isRangeProps(props) ? props.startDate : props.selectedDate;
+  const endDate = isRangeProps(props) ? props.endDate : null;
+  const setSelection = useCallback((start, end) => {
+    if (isRangeProps(props)) {
+      props.setStartDate(start);
+      props.setEndDate(end ?? null);
+    } else {
+      props.setSelectedDate(start);
+    }
+  }, [props]);
+  const contextValue = useMemo(() => {
+    const translations = {
+      ...DEFAULT_DATE_PICKER_TRANSLATIONS,
+      ...translationsProp
+    };
+    const base = {
+      startOrSelectedDate,
+      setSelection,
+      isCalendarOpen,
+      openCalendar,
+      focusCalendarGrid,
+      focusGridSignal,
+      gridFocusRequested,
+      clearGridFocusRequest,
+      closeCalendar,
+      locale: resolvedLocale,
+      disabledDates,
+      calendarDialogId,
+      translations
+    };
+    return mode === 'range' ? {
+      ...base,
+      mode: 'range',
+      endDate,
+      activeRangePart,
+      setActiveRangePart
+    } : {
+      ...base,
+      mode: 'single'
+    };
+  }, [mode, startOrSelectedDate, endDate, setSelection, activeRangePart, setActiveRangePart, isCalendarOpen, openCalendar, focusCalendarGrid, focusGridSignal, gridFocusRequested, clearGridFocusRequest, closeCalendar, resolvedLocale, disabledDates, calendarDialogId, translationsProp]);
+  const content = children !== undefined ? children : /*#__PURE__*/_jsxs(_Fragment, {
+    children: [/*#__PURE__*/_jsx(FlexBox, {
+      gap: inputSize === 'small' ? 4 : 8,
+      width: "fit-content",
+      children: mode === 'range' ? /*#__PURE__*/_jsxs(_Fragment, {
+        children: [/*#__PURE__*/_jsx(DatePickerInput, {
+          label: props.startLabel,
+          placeholder: placeholder,
+          rangePart: "start",
+          ref: inputRef,
+          size: inputSize
+        }), /*#__PURE__*/_jsx(Box, {
+          alignSelf: "center",
+          mt: 32,
+          children: /*#__PURE__*/_jsx(MiniArrowRightIcon, {})
+        }), /*#__PURE__*/_jsx(DatePickerInput, {
+          label: props.endLabel,
+          placeholder: placeholder,
+          rangePart: "end",
+          size: inputSize
+          // does this need a ref?
+        })]
+      }) : /*#__PURE__*/_jsx(DatePickerInput, {
+        label: props.label,
+        placeholder: placeholder,
+        ref: inputRef,
+        size: inputSize
+      })
+    }), /*#__PURE__*/_jsx(PopoverContainer, {
+      alignment: "bottom-left",
+      allowPageInteraction: true,
+      focusOnProps: {
+        autoFocus: false,
+        focusLock: false
+      },
+      invertAxis: "x",
+      isOpen: isCalendarOpen,
+      targetRef: inputRef,
+      x: -20,
+      y: -16,
+      onRequestClose: closeCalendar,
+      children: /*#__PURE__*/_jsx("div", {
+        "aria-label": contextValue.translations.calendarDialogAriaLabel,
+        id: calendarDialogId,
+        role: "dialog",
+        children: /*#__PURE__*/_jsx(DatePickerCalendar, {
+          dialogId: calendarDialogId
+        })
+      })
+    })]
+  });
+  return /*#__PURE__*/_jsx(DatePickerProvider, {
+    value: contextValue,
+    children: content
+  });
+};
\ No newline at end of file