@codecademy/gamut 68.1.3-alpha.bcf87d.0 → 68.1.3-alpha.e93e89.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,9 @@
1
- /// <reference types="react" />
1
+ import * as React from 'react';
2
2
  /**
3
3
  * Outer wrapper for the calendar (header + body + footer).
4
4
  * Used by DatePickerCalendar to group the calendar content.
5
+ * Renders a CheckerDense pattern shadow at offset left 8, top 8.
5
6
  */
6
- export declare const Calendar: import("@emotion/styled").StyledComponent<{
7
- theme?: import("@emotion/react").Theme | undefined;
8
- as?: import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> | undefined;
9
- } & {
10
- theme?: import("@emotion/react").Theme | undefined;
11
- }, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {}>;
7
+ export declare const Calendar: React.FC<{
8
+ children: React.ReactNode;
9
+ }>;
@@ -1,15 +1,28 @@
1
- import _styled from "@emotion/styled/base";
2
- import { css } from '@codecademy/gamut-styles';
1
+ import { CheckerDense } from '@codecademy/gamut-patterns';
2
+ import * as React from 'react';
3
+ import { Box } from '../../Box';
4
+
3
5
  /**
4
6
  * Outer wrapper for the calendar (header + body + footer).
5
7
  * Used by DatePickerCalendar to group the calendar content.
8
+ * Renders a CheckerDense pattern shadow at offset left 8, top 8.
6
9
  */
7
- export const Calendar = /*#__PURE__*/_styled("div", {
8
- target: "eq0g1i20",
9
- label: "Calendar"
10
- })(css({
11
- backgroundColor: 'background',
12
- borderRadius: 'lg',
13
- boxShadow: '0 4px 16px rgba(0, 0, 0, 0.12)',
14
- width: 'max-content'
15
- }), process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9EYXRlUGlja2VyL0NhbGVuZGFyL0NhbGVuZGFyLnRzeCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFPd0IiLCJmaWxlIjoiLi4vLi4vLi4vc3JjL0RhdGVQaWNrZXIvQ2FsZW5kYXIvQ2FsZW5kYXIudHN4Iiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzIH0gZnJvbSAnQGNvZGVjYWRlbXkvZ2FtdXQtc3R5bGVzJztcbmltcG9ydCBzdHlsZWQgZnJvbSAnQGVtb3Rpb24vc3R5bGVkJztcblxuLyoqXG4gKiBPdXRlciB3cmFwcGVyIGZvciB0aGUgY2FsZW5kYXIgKGhlYWRlciArIGJvZHkgKyBmb290ZXIpLlxuICogVXNlZCBieSBEYXRlUGlja2VyQ2FsZW5kYXIgdG8gZ3JvdXAgdGhlIGNhbGVuZGFyIGNvbnRlbnQuXG4gKi9cbmV4cG9ydCBjb25zdCBDYWxlbmRhciA9IHN0eWxlZC5kaXYoXG4gIGNzcyh7XG4gICAgYmFja2dyb3VuZENvbG9yOiAnYmFja2dyb3VuZCcsXG4gICAgYm9yZGVyUmFkaXVzOiAnbGcnLFxuICAgIGJveFNoYWRvdzogJzAgNHB4IDE2cHggcmdiYSgwLCAwLCAwLCAwLjEyKScsXG4gICAgd2lkdGg6ICdtYXgtY29udGVudCcsXG4gIH0pXG4pO1xuIl19 */");
10
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
+ export const Calendar = ({
12
+ children
13
+ }) => /*#__PURE__*/_jsxs(Box, {
14
+ position: "relative",
15
+ width: "max-content",
16
+ children: [/*#__PURE__*/_jsx(CheckerDense, {
17
+ left: 8,
18
+ position: "absolute",
19
+ top: 8
20
+ }), /*#__PURE__*/_jsx(Box, {
21
+ bg: "background",
22
+ border: 1,
23
+ borderRadius: "sm",
24
+ position: "relative",
25
+ zIndex: 1,
26
+ children: children
27
+ })]
28
+ });
@@ -1,7 +1,8 @@
1
1
  import * as React from 'react';
2
2
  import { FlexBox } from '../../Box';
3
3
  import { TextButton } from '../../Button';
4
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ import { getRelativeTodayLabel } from './utils/format';
5
+
5
6
  // function formatQuickActionLabel(action: QuickAction): string {
6
7
  // const { num, timePeriod } = action;
7
8
  // const period =
@@ -22,23 +23,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
22
23
  // : 'years';
23
24
  // return `${num} ${period}`;
24
25
  // }
25
-
26
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
26
27
  export const CalendarFooter = ({
27
28
  onClearDate,
28
29
  onTodayClick,
29
- onSelectedDateChange,
30
- onCurrentMonthYearChange
30
+ locale,
31
+ clearText,
32
+ disabled,
33
+ showClearButton
31
34
  }) => {
32
- const handleClearDate = () => {
33
- onSelectedDateChange(null);
34
- onClearDate?.();
35
- };
36
- const handleTodayClick = () => {
37
- const today = new Date();
38
- onSelectedDateChange(today);
39
- onCurrentMonthYearChange(new Date(today.getFullYear(), today.getMonth(), 1));
40
- onTodayClick?.();
41
- };
42
35
  // const actions = quickActions.slice(0, 3);
43
36
 
44
37
  return /*#__PURE__*/_jsxs(FlexBox, {
@@ -46,14 +39,15 @@ export const CalendarFooter = ({
46
39
  borderTop: 1,
47
40
  justifyContent: "space-between",
48
41
  p: 12,
49
- children: [/*#__PURE__*/_jsx(TextButton, {
50
- onClick: handleClearDate,
51
- children: "Clear"
42
+ children: [showClearButton && /*#__PURE__*/_jsx(TextButton, {
43
+ disabled: disabled,
44
+ onClick: () => onClearDate?.(),
45
+ children: clearText
52
46
  }), /*#__PURE__*/_jsx(FlexBox, {
53
47
  gap: 32,
54
48
  children: /*#__PURE__*/_jsx(TextButton, {
55
- onClick: handleTodayClick,
56
- children: "Today"
49
+ onClick: () => onTodayClick?.(),
50
+ children: getRelativeTodayLabel(locale)
57
51
  })
58
52
  })]
59
53
  });
@@ -3,21 +3,25 @@ import * as React from 'react';
3
3
  import { FlexBox } from '../../Box';
4
4
  import { IconButton } from '../../Button';
5
5
  import { Text } from '../../Typography';
6
- import { formatMonthYear } from './utils/format';
6
+ import { formatMonthYear, getRelativeMonthLabels } from './utils/format';
7
7
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
8
  export const CalendarHeader = ({
9
9
  currentMonthYear,
10
10
  onCurrentMonthYearChange,
11
11
  secondMonthYear,
12
- onPreviousMonthClick,
12
+ onLastMonthClick,
13
13
  onNextMonthClick,
14
14
  locale,
15
15
  headingId
16
16
  }) => {
17
- const handlePreviousMonth = () => {
18
- const previousMonth = new Date(currentMonthYear.getFullYear(), currentMonthYear.getMonth() - 1, 1);
19
- onCurrentMonthYearChange?.(previousMonth);
20
- onPreviousMonthClick?.();
17
+ const {
18
+ nextMonth,
19
+ lastMonth
20
+ } = getRelativeMonthLabels(locale);
21
+ const handleLastMonth = () => {
22
+ const lastMonth = new Date(currentMonthYear.getFullYear(), currentMonthYear.getMonth() - 1, 1);
23
+ onCurrentMonthYearChange?.(lastMonth);
24
+ onLastMonthClick?.();
21
25
  };
22
26
  const handleNextMonth = () => {
23
27
  const nextMonth = new Date(currentMonthYear.getFullYear(), currentMonthYear.getMonth() + 1, 1);
@@ -29,11 +33,11 @@ export const CalendarHeader = ({
29
33
  justifyContent: "space-between",
30
34
  pb: 16,
31
35
  children: [/*#__PURE__*/_jsx(IconButton, {
32
- "aria-label": "Previous month",
36
+ "aria-label": lastMonth,
33
37
  icon: MiniChevronLeftIcon,
34
38
  size: "small",
35
- tip: "Previous month",
36
- onClick: handlePreviousMonth
39
+ tip: lastMonth,
40
+ onClick: handleLastMonth
37
41
  }), /*#__PURE__*/_jsx(Text, {
38
42
  "aria-live": "polite",
39
43
  as: "h2",
@@ -53,10 +57,10 @@ export const CalendarHeader = ({
53
57
  id: headingId,
54
58
  children: formatMonthYear(secondMonthYear, locale)
55
59
  }), /*#__PURE__*/_jsx(IconButton, {
56
- "aria-label": "Next month",
60
+ "aria-label": nextMonth,
57
61
  icon: MiniChevronRightIcon,
58
62
  size: "small",
59
- tip: "Next month",
63
+ tip: nextMonth,
60
64
  onClick: handleNextMonth
61
65
  })]
62
66
  });
@@ -9,7 +9,7 @@ export interface CalendarHeaderProps {
9
9
  /** Currently displayed second month and year (used for heading and prev/next range) */
10
10
  secondMonthYear?: Date;
11
11
  /** Called after navigating to previous month; use for click tracking. */
12
- onPreviousMonthClick?: () => void;
12
+ onLastMonthClick?: () => void;
13
13
  /** Called after navigating to next month; use for click tracking. */
14
14
  onNextMonthClick?: () => void;
15
15
  /** Locale for month/year formatting (e.g. 'en-US') */
@@ -49,11 +49,12 @@ export interface QuickAction {
49
49
  onClick: () => void;
50
50
  }
51
51
  export interface CalendarFooterProps {
52
+ disabled?: boolean;
53
+ showClearButton?: boolean;
54
+ locale?: string;
55
+ clearText: string;
52
56
  onClearDate?: () => void;
53
57
  onTodayClick?: () => void;
54
- /** Called when the user navigates to a different month. Pass the new date (e.g. setVisibleDate) so the calendar updates. */
55
- onSelectedDateChange: (newDate: Date | null) => void;
56
- onCurrentMonthYearChange: (newDate: Date) => void;
57
58
  /** Max 3 quick actions (e.g. "7 days", "1 month") */
58
59
  quickActions?: QuickAction[];
59
60
  }
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Date formatting for the calendar using Intl.DateTimeFormat.
3
3
  */
4
+ /**
5
+ * Capitalize the first character of a string; rest unchanged (e.g. "next month" → "Next month").
6
+ */
7
+ export declare const capitalizeFirst: (str: string) => string;
4
8
  /**
5
9
  * Format month and year for the calendar header (e.g. "February 2026").
6
10
  */
@@ -15,6 +19,24 @@ export declare const getWeekdayLabels: (locale?: string, weekStartsOn?: 0 | 1) =
15
19
  * Same order as getWeekdayLabels.
16
20
  */
17
21
  export declare const getWeekdayFullNames: (locale?: string, weekStartsOn?: 0 | 1) => string[];
22
+ /**
23
+ * Get localized "next month" and "previous month" labels for calendar nav.
24
+ * Uses Intl.RelativeTimeFormat with numeric: "auto" (e.g. "next month", "last month").
25
+ */
26
+ export declare const getRelativeMonthLabels: (locale?: string) => {
27
+ nextMonth: string;
28
+ lastMonth: string;
29
+ };
30
+ /**
31
+ * Get localized "today" label (e.g. "today").
32
+ */
33
+ export declare const getRelativeTodayLabel: (locale?: string) => string;
34
+ /**
35
+ * Get the locale's short date format pattern (e.g. "MM/DD/YYYY" for en-US,
36
+ * "DD/MM/YYYY" for en-GB). Uses Intl.DateTimeFormat formatToParts to infer
37
+ * order and separators. Useful for parsing or building locale-aware inputs.
38
+ */
39
+ export declare const getDateFormatPattern: (locale?: string) => string;
18
40
  /**
19
41
  * Format a date for display in the date picker input (e.g. "2/15/2026").
20
42
  */
@@ -2,11 +2,16 @@
2
2
  * Date formatting for the calendar using Intl.DateTimeFormat.
3
3
  */
4
4
 
5
+ /**
6
+ * Capitalize the first character of a string; rest unchanged (e.g. "next month" → "Next month").
7
+ */
8
+ export const capitalizeFirst = str => str.length === 0 ? str : str[0].toUpperCase() + str.slice(1);
9
+
5
10
  /**
6
11
  * Format month and year for the calendar header (e.g. "February 2026").
7
12
  */
8
13
  export const formatMonthYear = (date, locale) => {
9
- return new Intl.DateTimeFormat(locale ?? 'en-US', {
14
+ return new Intl.DateTimeFormat(locale, {
10
15
  month: 'long',
11
16
  year: 'numeric'
12
17
  }).format(date);
@@ -17,7 +22,7 @@ export const formatMonthYear = (date, locale) => {
17
22
  * Order depends on weekStartsOn: 0 = Sunday first, 1 = Monday first.
18
23
  */
19
24
  export const getWeekdayLabels = (locale, weekStartsOn = 0) => {
20
- const formatter = new Intl.DateTimeFormat(locale ?? 'en-US', {
25
+ const formatter = new Intl.DateTimeFormat(locale, {
21
26
  weekday: 'short'
22
27
  });
23
28
  // Jan 7, 2024 is a Sunday; add 0..6 days to get Sun..Sat
@@ -40,7 +45,7 @@ export const getWeekdayLabels = (locale, weekStartsOn = 0) => {
40
45
  * Same order as getWeekdayLabels.
41
46
  */
42
47
  export const getWeekdayFullNames = (locale, weekStartsOn = 0) => {
43
- const formatter = new Intl.DateTimeFormat(locale ?? 'en-US', {
48
+ const formatter = new Intl.DateTimeFormat(locale, {
44
49
  weekday: 'long'
45
50
  });
46
51
  const sunday = new Date(2024, 0, 7);
@@ -57,11 +62,60 @@ export const getWeekdayFullNames = (locale, weekStartsOn = 0) => {
57
62
  return names;
58
63
  };
59
64
 
65
+ /**
66
+ * Get localized "next month" and "previous month" labels for calendar nav.
67
+ * Uses Intl.RelativeTimeFormat with numeric: "auto" (e.g. "next month", "last month").
68
+ */
69
+ export const getRelativeMonthLabels = locale => {
70
+ const rtf = new Intl.RelativeTimeFormat(locale, {
71
+ numeric: 'auto'
72
+ });
73
+ return {
74
+ nextMonth: capitalizeFirst(rtf.format(1, 'month')),
75
+ lastMonth: capitalizeFirst(rtf.format(-1, 'month'))
76
+ };
77
+ };
78
+
79
+ /**
80
+ * Get localized "today" label (e.g. "today").
81
+ */
82
+ export const getRelativeTodayLabel = locale => {
83
+ const rtf = new Intl.RelativeTimeFormat(locale, {
84
+ numeric: 'auto'
85
+ });
86
+ return capitalizeFirst(rtf.format(0, 'day'));
87
+ };
88
+
89
+ /**
90
+ * Get the locale's short date format pattern (e.g. "MM/DD/YYYY" for en-US,
91
+ * "DD/MM/YYYY" for en-GB). Uses Intl.DateTimeFormat formatToParts to infer
92
+ * order and separators. Useful for parsing or building locale-aware inputs.
93
+ */
94
+ export const getDateFormatPattern = locale => {
95
+ const parts = new Intl.DateTimeFormat(locale, {
96
+ year: 'numeric',
97
+ month: '2-digit',
98
+ day: '2-digit'
99
+ }).formatToParts(new Date(2025, 0, 15));
100
+ return parts.map(p => {
101
+ switch (p.type) {
102
+ case 'day':
103
+ return 'DD';
104
+ case 'month':
105
+ return 'MM';
106
+ case 'year':
107
+ return 'YYYY';
108
+ default:
109
+ return p.value;
110
+ }
111
+ }).join('');
112
+ };
113
+
60
114
  /**
61
115
  * Format a date for display in the date picker input (e.g. "2/15/2026").
62
116
  */
63
117
  export const formatDateForInput = (date, locale) => {
64
- return new Intl.DateTimeFormat(locale ?? 'en-US', {
118
+ return new Intl.DateTimeFormat(locale, {
65
119
  month: 'numeric',
66
120
  day: 'numeric',
67
121
  year: 'numeric'
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import type { DatePickerProps } from './types';
2
+ import { type DatePickerProps } from './types';
3
3
  /**
4
4
  * DatePicker: single-date or range. Holds shared state and provides it via context.
5
5
  * Single: selectedDate, setSelectedDate. Range: startDate, endDate, setStartDate, setEndDate.
@@ -1,9 +1,11 @@
1
- import { useCallback, useId, useMemo, useRef, useState } from 'react';
1
+ import { MiniArrowRightIcon } from '@codecademy/gamut-icons';
2
+ import { useCallback, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
3
  import { FlexBox } from '../Box';
3
4
  import { PopoverContainer } from '../PopoverContainer';
4
5
  import { DatePickerCalendar } from './DatePickerCalendar';
5
6
  import { DatePickerProvider } from './DatePickerContext';
6
7
  import { DatePickerInput } from './DatePickerInput';
8
+ import { DEFAULT_DATE_PICKER_TRANSLATIONS } from './translations';
7
9
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
8
10
  function isRangeProps(props) {
9
11
  return props.mode === 'range';
@@ -16,17 +18,30 @@ function isRangeProps(props) {
16
18
  */
17
19
  export const DatePicker = props => {
18
20
  const {
19
- locale = 'en-US',
21
+ locale,
20
22
  disabledDates = [],
21
23
  placeholder,
22
24
  mode,
23
- children
25
+ children,
26
+ translations: translationsProp
24
27
  } = props;
25
28
  const [isCalendarOpen, setIsCalendarOpen] = useState(false);
26
29
  const [activeRangePart, setActiveRangePart] = useState(null);
27
30
  const inputRef = useRef(null);
28
31
  const dialogId = useId();
29
32
  const calendarDialogId = `datepicker-dialog-${dialogId.replace(/:/g, '')}`;
33
+ const popoverOffset = 4;
34
+
35
+ // Align popover left edge with input left edge. PopoverContainer's "bottom-right"
36
+ // sets popover left = target left + (target width + offset + x), so we pass
37
+ // x = -(target width + offset) to get popover left = target left.
38
+ const [popoverX, setPopoverX] = useState(0);
39
+ useLayoutEffect(() => {
40
+ if (isCalendarOpen && inputRef.current) {
41
+ const width = inputRef.current.offsetWidth;
42
+ setPopoverX(-(width + popoverOffset));
43
+ }
44
+ }, [isCalendarOpen, popoverOffset]);
30
45
  const openCalendar = useCallback(() => setIsCalendarOpen(true), []);
31
46
  const closeCalendar = useCallback(() => {
32
47
  setIsCalendarOpen(false);
@@ -44,6 +59,10 @@ export const DatePicker = props => {
44
59
  }
45
60
  }, [props]);
46
61
  const contextValue = useMemo(() => {
62
+ const translations = {
63
+ ...DEFAULT_DATE_PICKER_TRANSLATIONS,
64
+ ...translationsProp
65
+ };
47
66
  const base = {
48
67
  startOrSelectedDate,
49
68
  setSelection,
@@ -52,7 +71,8 @@ export const DatePicker = props => {
52
71
  closeCalendar,
53
72
  locale,
54
73
  disabledDates,
55
- calendarDialogId
74
+ calendarDialogId,
75
+ translations
56
76
  };
57
77
  return mode === 'range' ? {
58
78
  ...base,
@@ -64,7 +84,7 @@ export const DatePicker = props => {
64
84
  ...base,
65
85
  mode: 'single'
66
86
  };
67
- }, [mode, startOrSelectedDate, endDate, setSelection, activeRangePart, setActiveRangePart, isCalendarOpen, openCalendar, closeCalendar, locale, disabledDates, calendarDialogId]);
87
+ }, [mode, startOrSelectedDate, endDate, setSelection, activeRangePart, setActiveRangePart, isCalendarOpen, openCalendar, closeCalendar, locale, disabledDates, calendarDialogId, translationsProp]);
68
88
 
69
89
  // what is this doing
70
90
  // useEffect(() => {
@@ -84,7 +104,9 @@ export const DatePicker = props => {
84
104
  placeholder: placeholder,
85
105
  rangePart: "start",
86
106
  ref: inputRef
87
- }), /*#__PURE__*/_jsx(DatePickerInput, {
107
+ }), /*#__PURE__*/_jsx(MiniArrowRightIcon, {
108
+ alignSelf: "center"
109
+ }), ' ', /*#__PURE__*/_jsx(DatePickerInput, {
88
110
  label: props.endLabel,
89
111
  placeholder: placeholder,
90
112
  rangePart: "end"
@@ -96,22 +118,20 @@ export const DatePicker = props => {
96
118
  ref: inputRef
97
119
  })
98
120
  }), /*#__PURE__*/_jsx(PopoverContainer, {
99
- alignment: "bottom-left",
100
- allowPageInteraction: true
101
- // look into if we can kill this and mess with where we are focusing instead
102
- ,
121
+ alignment: "bottom-right",
122
+ allowPageInteraction: true,
103
123
  focusOnProps: {
104
124
  autoFocus: false,
105
125
  focusLock: false
106
126
  },
107
- invertAxis: "x",
108
127
  isOpen: isCalendarOpen,
109
- offset: 10,
128
+ offset: popoverOffset,
110
129
  targetRef: inputRef,
111
- onRequestClose: closeCalendar // without this we cant type in the input but there has to be a better way
112
- ,
130
+ x: popoverX,
131
+ y: 0,
132
+ onRequestClose: closeCalendar,
113
133
  children: /*#__PURE__*/_jsx("div", {
114
- "aria-label": "Choose date",
134
+ "aria-label": contextValue.translations.calendarDialogAriaLabel,
115
135
  id: calendarDialogId,
116
136
  role: "dialog",
117
137
  children: /*#__PURE__*/_jsx(DatePickerCalendar, {
@@ -27,7 +27,8 @@ export const DatePickerCalendar = ({
27
27
  disabledDates,
28
28
  locale,
29
29
  closeCalendar,
30
- isCalendarOpen
30
+ isCalendarOpen,
31
+ translations
31
32
  } = context;
32
33
  const isRange = mode === 'range';
33
34
  const endDate = isRange ? context.endDate : undefined;
@@ -118,9 +119,11 @@ export const DatePickerCalendar = ({
118
119
  })]
119
120
  })]
120
121
  }), /*#__PURE__*/_jsx(CalendarFooter, {
122
+ clearText: translations.clearText,
123
+ disabled: startOrSelectedDate === null && endDate === null,
124
+ locale: locale,
125
+ showClearButton: isRange,
121
126
  onClearDate: handleClearDate,
122
- onCurrentMonthYearChange: setVisibleDate,
123
- onSelectedDateChange: date => date === null ? handleClearDate() : handleTodayClick(),
124
127
  onTodayClick: handleTodayClick
125
128
  })]
126
129
  });
@@ -1,7 +1,8 @@
1
- import { CalendarIcon } from '@codecademy/gamut-icons';
1
+ import { MiniCalendarIcon } from '@codecademy/gamut-icons';
2
2
  import { forwardRef, useEffect, useId, useRef, useState } from 'react';
3
+ import { FormGroup } from '../Form/elements/FormGroup';
3
4
  import { Input } from '../Form/inputs/Input';
4
- import { formatDateForInput, parseDateFromInput } from './Calendar/utils/format';
5
+ import { formatDateForInput, getDateFormatPattern, parseDateFromInput } from './Calendar/utils/format';
5
6
  import { useDatePicker } from './DatePickerContext';
6
7
 
7
8
  /**
@@ -43,7 +44,8 @@ export const DatePickerInput = /*#__PURE__*/forwardRef(({
43
44
  openCalendar,
44
45
  locale,
45
46
  isCalendarOpen,
46
- calendarDialogId
47
+ calendarDialogId,
48
+ translations
47
49
  } = context;
48
50
  const isRange = mode === 'range';
49
51
  const inputID = useId();
@@ -97,30 +99,38 @@ export const DatePickerInput = /*#__PURE__*/forwardRef(({
97
99
  const handleOpenCalendar = () => {
98
100
  openCalendar();
99
101
  };
100
- const defaultLabel = isRange && rangePart === 'end' ? 'End date' : isRange ? 'Start date' : 'Date';
101
- return /*#__PURE__*/_jsx(Input, {
102
- ...rest,
103
- "aria-autocomplete": "none",
104
- "aria-controls": calendarDialogId,
105
- "aria-expanded": isCalendarOpen,
106
- "aria-haspopup": "dialog",
107
- icon: CalendarIcon // add mini calendar icon and update
102
+ const defaultLabel = !isRange ? translations.dateLabel : rangePart === 'end' ? translations.endDateLabel : translations.startDateLabel;
103
+ return /*#__PURE__*/_jsx(FormGroup, {
104
+ htmlFor: inputId,
105
+ isSoloField: true // should probaly be based on a prop
108
106
  ,
109
- id: inputId,
110
- label: label ?? defaultLabel // this isnt actually adding a label
111
- ,
112
- placeholder: placeholder ?? 'MM/DD/YYYY',
113
- ref: ref,
114
- role: "combobox",
115
- type: "text",
116
- value: inputValue,
117
- onBlur: handleBlur,
118
- onChange: handleChange,
119
- onClick: handleOpenCalendar,
120
- onFocus: () => {
121
- isInputFocusedRef.current = true;
122
- if (isRange && rangePart) context.setActiveRangePart(rangePart);
123
- },
124
- onKeyDown: handleKeyDown
107
+ label: label ?? defaultLabel,
108
+ pb: 0,
109
+ spacing: "tight",
110
+ width: "170px",
111
+ children: /*#__PURE__*/_jsx(Input, {
112
+ ...rest,
113
+ "aria-autocomplete": "none",
114
+ "aria-controls": calendarDialogId,
115
+ "aria-expanded": isCalendarOpen,
116
+ "aria-haspopup": "dialog",
117
+ icon: () => /*#__PURE__*/_jsx(MiniCalendarIcon, {
118
+ size: 16
119
+ }),
120
+ id: inputId,
121
+ placeholder: placeholder ?? getDateFormatPattern(locale),
122
+ ref: ref,
123
+ role: "combobox",
124
+ type: "text",
125
+ value: inputValue,
126
+ onBlur: handleBlur,
127
+ onChange: handleChange,
128
+ onClick: handleOpenCalendar,
129
+ onFocus: () => {
130
+ isInputFocusedRef.current = true;
131
+ if (isRange && rangePart) context.setActiveRangePart(rangePart);
132
+ },
133
+ onKeyDown: handleKeyDown
134
+ })
125
135
  });
126
136
  });
@@ -0,0 +1,3 @@
1
+ import { DatePickerTranslations } from './types';
2
+ /** Default UI strings; pass translations prop to override. */
3
+ export declare const DEFAULT_DATE_PICKER_TRANSLATIONS: Required<DatePickerTranslations>;
@@ -0,0 +1,8 @@
1
+ /** Default UI strings; pass translations prop to override. */
2
+ export const DEFAULT_DATE_PICKER_TRANSLATIONS = {
3
+ clearText: 'Clear',
4
+ dateLabel: 'Date',
5
+ startDateLabel: 'Start date',
6
+ endDateLabel: 'End date',
7
+ calendarDialogAriaLabel: 'Choose date'
8
+ };
@@ -17,6 +17,8 @@ export interface DatePickerBaseProps {
17
17
  children?: React.ReactNode;
18
18
  /** Placeholder for the input. */
19
19
  placeholder?: string;
20
+ /** Override UI strings (e.g. clear button). Merged with defaults. */
21
+ translations?: DatePickerTranslations;
20
22
  }
21
23
  /** Props for the DatePicker (single-date mode). */
22
24
  export interface DatePickerSingleProps extends DatePickerBaseProps {
@@ -48,14 +50,29 @@ export interface DatePickerRangeProps extends DatePickerBaseProps {
48
50
  export type DatePickerProps = DatePickerSingleProps | DatePickerRangeProps;
49
51
  /** Which range input is active (focused); null = calendar drives both (selection mode). */
50
52
  export type ActiveRangePart = 'start' | 'end' | null;
53
+ /** Optional translations for DatePicker UI strings. Pass to override defaults. */
54
+ export interface DatePickerTranslations {
55
+ /** Label for the clear date button (default: "Clear"). */
56
+ clearText?: string;
57
+ /** Default label for the date input in single mode (default: "Date"). */
58
+ dateLabel?: string;
59
+ /** Default label for the start date input in range mode (default: "Start date"). */
60
+ startDateLabel?: string;
61
+ /** Default label for the end date input in range mode (default: "End date"). */
62
+ endDateLabel?: string;
63
+ /** aria-label for the calendar dialog (default: "Choose date"). */
64
+ calendarDialogAriaLabel?: string;
65
+ }
51
66
  /** Shared state provided by DatePicker via context. */
52
67
  export interface DatePickerBaseContextValue {
53
68
  isCalendarOpen: boolean;
54
69
  openCalendar: () => void;
55
70
  closeCalendar: () => void;
56
- locale: string;
71
+ locale?: string;
57
72
  disabledDates: Date[];
58
73
  calendarDialogId: string;
74
+ /** UI string overrides (e.g. clear button). */
75
+ translations: Required<DatePickerTranslations>;
59
76
  /** Start date (range) or selected date (single). */
60
77
  startOrSelectedDate: Date | null;
61
78
  /** Set selection. Single: (date). Range: (start, end). */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@codecademy/gamut",
3
3
  "description": "Styleguide & Component library for Codecademy",
4
- "version": "68.1.3-alpha.bcf87d.0",
4
+ "version": "68.1.3-alpha.e93e89.0",
5
5
  "author": "Codecademy Engineering <dev@codecademy.com>",
6
6
  "dependencies": {
7
7
  "@codecademy/gamut-icons": "9.57.0",
@@ -59,5 +59,5 @@
59
59
  "dist/**/[A-Z]**/[A-Z]*.js",
60
60
  "dist/**/[A-Z]**/index.js"
61
61
  ],
62
- "gitHead": "8ba3e2e2d76f322da2031e91d94fe9989651db9d"
62
+ "gitHead": "597c47b90e8bb7ebae7c00c214c0ef0887bcb8a5"
63
63
  }