@codecademy/gamut

68.6.268.6.3-alpha.92d8ae.0
dist/DatePicker/DatePickerInput/index.js
~dist/DatePicker/DatePickerInput/index.jsModified
+64−191
Index: package/dist/DatePicker/DatePickerInput/index.js
===================================================================
--- package/dist/DatePicker/DatePickerInput/index.js
+++ package/dist/DatePicker/DatePickerInput/index.js
@@ -1,215 +1,88 @@
-import { MiniCalendarIcon } from '@codecademy/gamut-icons';
-import { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
-import { FlexBox } from '../../Box';
+import { forwardRef, useId } from 'react';
+import { Box, FlexBox } from '../../Box';
 import { FormGroup } from '../../Form/elements/FormGroup';
-import { isSameDay } from '../DatePickerCalendar/Calendar/utils/dateGrid';
-import { handleDateSelectRange } from '../DatePickerCalendar/utils/dateSelect';
+import { FormGroupLabel } from '../../Form/elements/FormGroupLabel';
+import { DATE_PICKER_FIELD_WIDTH } from '../constants';
 import { useDatePicker } from '../DatePickerContext';
-import { SegmentedShell } from './elements';
-import { DatePickerInputSegment } from './Segment';
-import { SegmentLiteral } from './Segment/elements';
-import { getDateSegmentsFromDate, normalizeSegmentValues, parseSegmentsToDate } from './Segment/utils';
-import { formatDateISO8601DateOnly, getDateFieldOrder, getDateFormatLayout } from './utils';
+import { createDatePickerFieldIds, createDatePickerShellId } from '../utils/fieldIds';
+import { DatePickerInputShell } from './DatePickerInputShell';
+import { DatePickerDescription } from './elements';
 import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
+export { DatePickerDescription } from './elements';
 export const DatePickerInput = /*#__PURE__*/forwardRef(({
   disabled,
   error,
   form,
   label,
   name,
   rangePart,
   size = 'base',
+  description,
   ...rest
 }, ref) => {
   const context = useDatePicker();
   if (context === null) {
     throw new Error('DatePickerInput must be used inside a DatePicker (it reads shared state from context).');
   }
   const {
-    mode,
-    openCalendar,
-    focusCalendar,
-    locale,
-    isCalendarOpen,
-    translations,
-    disableDate
+    translations
   } = context;
-  const isRange = mode === 'range';
-  const endDate = isRange ? context.endDate : null;
-  const date = isRange ? context.startDate : context.selectedDate;
-  const inputID = useId();
-  const inputId = `datepicker-input-${inputID.replace(/:/g, '')}`;
-  const {
-    layout,
-    fieldOrder
-  } = useMemo(() => {
-    const layout = getDateFormatLayout(locale);
-    return {
-      layout,
-      fieldOrder: getDateFieldOrder(layout)
-    };
-  }, [locale]);
-  const defaultLabel = !isRange ? translations.dateLabel : rangePart === 'end' ? translations.endDateLabel : translations.startDateLabel;
-  const boundDate = isRange && rangePart === 'end' ? endDate : date;
-  const segmentsFromBound = useMemo(() => getDateSegmentsFromDate(boundDate), [boundDate]);
-  const [segments, setSegments] = useState(segmentsFromBound);
-  const parsedForHidden = parseSegmentsToDate(segments);
-  const hiddenValue = parsedForHidden ? formatDateISO8601DateOnly(parsedForHidden) : '';
-  const isInputFocusedRef = useRef(false);
-  const containerRef = useRef(null);
-  const segmentElRefs = useRef({});
-  const assignSegmentRef = useCallback((field, el) => {
-    segmentElRefs.current[field] = el;
-  }, [segmentElRefs]);
-  const onSiblingSegmentFocus = useCallback(field => {
-    segmentElRefs.current[field]?.focus();
-  }, [segmentElRefs]);
-  const shellRef = useCallback(el => {
-    containerRef.current = el;
-    if (typeof ref === 'function') ref(el);else if (ref !== null) ref.current = el;
-  }, [ref]);
-  useEffect(() => {
-    if (!isInputFocusedRef.current) {
-      setSegments(segmentsFromBound);
-    }
-  }, [segmentsFromBound]);
-  const commitParsedDate = useCallback(parsed => {
-    if (!isRange) {
-      context.onSelection(parsed);
-    }
-    if (isRange && rangePart) {
-      handleDateSelectRange({
-        date: parsed,
-        activeRangePart: rangePart,
-        startDate: date,
-        endDate,
-        onRangeSelection: context.onRangeSelection,
-        disableDate
-      });
-    }
-  }, [isRange, rangePart, context, endDate, date, disableDate]);
-  const clearSelection = useCallback(() => {
-    if (!isRange) {
-      context.onSelection(null);
-    }
-    if (isRange && rangePart) {
-      if (rangePart === 'start') context.onRangeSelection(null, endDate);else context.onRangeSelection(date, null);
-    }
-  }, [isRange, rangePart, context, endDate, date]);
-  const onSegmentChange = useCallback(next => {
-    const parsed = parseSegmentsToDate(next);
-    if (parsed) commitParsedDate(parsed);else if (!next.month && !next.day && !next.year) clearSelection();
-  }, [clearSelection, commitParsedDate]);
-  const onContainerBlur = useCallback(e => {
-    if (containerRef.current?.contains(e.relatedTarget)) return;
-    isInputFocusedRef.current = false;
-    setSegments(prev => {
-      const normalized = normalizeSegmentValues(prev);
-      const parsed = parseSegmentsToDate(normalized);
-      if (parsed) {
-        const sameAsBound = isSameDay(parsed, boundDate);
-        if (isCalendarOpen && !sameAsBound) {
-          queueMicrotask(() => {
-            commitParsedDate(parsed);
-          });
-        }
-        return normalized;
-      }
-      if (!normalized.month && !normalized.day && !normalized.year) {
-        queueMicrotask(() => {
-          clearSelection();
-        });
-        return getDateSegmentsFromDate(null);
-      }
-      return segmentsFromBound;
+  const labelUid = useId();
+  const inputUid = useId();
+  const shellProps = {
+    disabled,
+    error,
+    form,
+    size,
+    ...rest
+  };
+  if (rangePart) {
+    const shellId = createDatePickerShellId(inputUid);
+    const defaultLabel = rangePart === 'end' ? translations.endDateLabel : translations.startDateLabel;
+    return /*#__PURE__*/_jsxs(FormGroup, {
+      alignItems: "flex-start",
+      id: shellId,
+      isSoloField: true,
+      label: label ?? defaultLabel,
+      mb: 0,
+      pb: 0,
+      spacing: "tight",
+      width: "fit-content",
+      children: [description ? /*#__PURE__*/_jsx(DatePickerDescription, {
+        "aria-live": "assertive",
+        children: description
+      }) : null, /*#__PURE__*/_jsx(DatePickerInputShell, {
+        ...shellProps,
+        labelledById: shellId,
+        name: name,
+        rangePart: rangePart,
+        ref: ref,
+        shellId: shellId
+      })]
     });
-  }, [containerRef, boundDate, segmentsFromBound, clearSelection, commitParsedDate, isCalendarOpen]);
-  const setActiveRangePartForField = useCallback(() => {
-    if (isRange && rangePart) context.setActiveRangePart(rangePart);
-  }, [isRange, rangePart, context]);
-  const onSegmentFocus = useCallback(() => {
-    isInputFocusedRef.current = true;
-    setActiveRangePartForField();
-  }, [isInputFocusedRef, setActiveRangePartForField]);
-  const onShellFocus = useCallback(() => {
-    setActiveRangePartForField();
-  }, [setActiveRangePartForField]);
-  const onShellClick = useCallback(() => {
-    if (disabled) return;
-    setActiveRangePartForField();
-    openCalendar();
-  }, [disabled, setActiveRangePartForField, openCalendar]);
-  const onSegmentAltArrowDown = useCallback(() => {
-    if (!isCalendarOpen) openCalendar();
-    focusCalendar();
-  }, [isCalendarOpen, openCalendar, focusCalendar]);
-  return /*#__PURE__*/_jsx(FormGroup, {
-    htmlFor: inputId,
-    isSoloField: true,
-    label: label ?? defaultLabel,
-    mb: 0,
-    pb: 0,
-    spacing: "tight",
+  }
+  const fieldIds = createDatePickerFieldIds(inputUid, labelUid);
+  return /*#__PURE__*/_jsxs(Box, {
     width: "fit-content",
-    children: /*#__PURE__*/_jsxs(SegmentedShell, {
-      id: inputId,
-      inputSize: size,
-      ref: shellRef,
-      role: "group",
-      variant: error ? 'error' : 'default',
-      width: "113px",
-      onBlur: onContainerBlur,
-      onClick: onShellClick,
-      onFocus: onShellFocus,
-      ...rest,
-      children: [/*#__PURE__*/_jsx(FlexBox, {
-        alignItems: "center",
-        justifyContent: "center",
-        children: layout.map((item, index) => {
-          if (item.kind === 'literal') {
-            return /*#__PURE__*/_jsx(SegmentLiteral, {
-              "aria-hidden": true
-              // eslint-disable-next-line react/no-array-index-key
-              ,
-              children: `${item.text}`
-            }, `literal-${item.text}-${index}`);
-          }
-          const idx = fieldOrder.indexOf(item.field);
-          const prevField = idx > 0 ? fieldOrder[idx - 1] : null;
-          const nextField = idx < fieldOrder.length - 1 ? fieldOrder[idx + 1] : null;
-          return /*#__PURE__*/_jsx(DatePickerInputSegment, {
-            applySegments: onSegmentChange,
-            assignSegmentRef: assignSegmentRef,
-            disabled: !!disabled,
-            error: !!error,
-            field: item.field,
-            nextField: nextField,
-            prevField: prevField,
-            segments: segments,
-            setSegments: setSegments,
-            onAltArrowDown: onSegmentAltArrowDown,
-            onFocus: onSegmentFocus,
-            onSiblingFocus: onSiblingSegmentFocus
-          }, item.field);
-        })
-      }), /*#__PURE__*/_jsx("input", {
-        "aria-hidden": true,
-        form: form,
+    children: [/*#__PURE__*/_jsx(Box, {
+      id: fieldIds.labelledById,
+      width: DATE_PICKER_FIELD_WIDTH,
+      children: /*#__PURE__*/_jsx(FormGroupLabel, {
+        htmlFor: fieldIds.shellId,
+        isSoloField: true,
+        children: label ?? translations.dateLabel
+      })
+    }), description ? /*#__PURE__*/_jsx(DatePickerDescription, {
+      "aria-live": "assertive",
+      children: description
+    }) : null, /*#__PURE__*/_jsx(FlexBox, {
+      ref: ref,
+      children: /*#__PURE__*/_jsx(DatePickerInputShell, {
+        ...shellProps,
+        labelledById: fieldIds.labelledById,
         name: name ?? 'datePickerInput',
-        tabIndex: -1,
-        type: "hidden",
-        value: hiddenValue
-      }), /*#__PURE__*/_jsx(FlexBox, {
-        alignItems: "center",
-        justifyContent: "center",
-        pl: 16,
-        pr: 8,
-        role: "presentation",
-        children: /*#__PURE__*/_jsx(MiniCalendarIcon, {
-          "aria-hidden": true,
-          size: 16
-        })
-      })]
-    })
+        shellId: fieldIds.shellId
+      })
+    })]
   });
 });
\ No newline at end of file