@codecademy/gamut 68.1.3-alpha.bcf87d.0 → 68.1.3-alpha.da9068.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.
@@ -2,66 +2,101 @@
2
2
  * Date formatting for the calendar using Intl.DateTimeFormat.
3
3
  */
4
4
 
5
+ import { isValidDate } from './validation';
6
+
7
+ /**
8
+ * Capitalize the first character of a string using the locale; rest unchanged (e.g. "next month" → "Next month").
9
+ */
10
+ export const capitalizeFirst = (str, locale) => str.length === 0 ? str : str[0].toLocaleUpperCase(locale) + str.slice(1);
11
+
5
12
  /**
6
13
  * Format month and year for the calendar header (e.g. "February 2026").
7
14
  */
8
15
  export const formatMonthYear = (date, locale) => {
9
- return new Intl.DateTimeFormat(locale ?? 'en-US', {
16
+ return new Intl.DateTimeFormat(locale, {
10
17
  month: 'long',
11
18
  year: 'numeric'
12
19
  }).format(date);
13
20
  };
14
21
 
15
22
  /**
16
- * Get short weekday labels for column headers (e.g. ["Su", "Mo", ...]).
23
+ * Get weekday names for column headers or abbr attributes.
17
24
  * Order depends on weekStartsOn: 0 = Sunday first, 1 = Monday first.
25
+ * @param format - 'short' for abbreviated (e.g. "Su", "Mo"), 'long' for full (e.g. "Sunday", "Monday")
18
26
  */
19
- export const getWeekdayLabels = (locale, weekStartsOn = 0) => {
20
- const formatter = new Intl.DateTimeFormat(locale ?? 'en-US', {
21
- weekday: 'short'
27
+ export const getWeekdayNames = (format, locale, weekStartsOn = 0) => {
28
+ const formatter = new Intl.DateTimeFormat(locale, {
29
+ weekday: format
22
30
  });
23
31
  // Jan 7, 2024 is a Sunday; add 0..6 days to get Sun..Sat
24
32
  const sunday = new Date(2024, 0, 7);
25
- const labels = Array.from({
33
+ const names = Array.from({
26
34
  length: 7
27
35
  }, (_, i) => {
28
- const d = new Date(sunday);
29
- d.setDate(sunday.getDate() + i);
30
- return formatter.format(d);
36
+ const date = new Date(sunday);
37
+ date.setDate(sunday.getDate() + i);
38
+ return formatter.format(date);
31
39
  });
32
40
  if (weekStartsOn === 1) {
33
- return [...labels.slice(1), labels[0]];
41
+ return [...names.slice(1), names[0]];
34
42
  }
35
- return labels;
43
+ return names;
36
44
  };
37
45
 
38
46
  /**
39
- * Get full weekday names for abbr attributes (e.g. "Sunday", "Monday").
40
- * Same order as getWeekdayLabels.
47
+ * Get localized "next month" and "previous month" labels for calendar nav.
48
+ * Uses Intl.RelativeTimeFormat with numeric: "auto" (e.g. "next month", "last month").
41
49
  */
42
- export const getWeekdayFullNames = (locale, weekStartsOn = 0) => {
43
- const formatter = new Intl.DateTimeFormat(locale ?? 'en-US', {
44
- weekday: 'long'
50
+ export const getRelativeMonthLabels = locale => {
51
+ const rtf = new Intl.RelativeTimeFormat(locale, {
52
+ numeric: 'auto'
45
53
  });
46
- const sunday = new Date(2024, 0, 7);
47
- const names = Array.from({
48
- length: 7
49
- }, (_, i) => {
50
- const d = new Date(sunday);
51
- d.setDate(sunday.getDate() + i);
52
- return formatter.format(d);
54
+ return {
55
+ nextMonth: capitalizeFirst(rtf.format(1, 'month'), locale),
56
+ lastMonth: capitalizeFirst(rtf.format(-1, 'month'), locale)
57
+ };
58
+ };
59
+
60
+ /**
61
+ * Get localized "today" label (e.g. "today").
62
+ */
63
+ export const getRelativeTodayLabel = locale => {
64
+ const rtf = new Intl.RelativeTimeFormat(locale, {
65
+ numeric: 'auto'
53
66
  });
54
- if (weekStartsOn === 1) {
55
- return [...names.slice(1), names[0]];
56
- }
57
- return names;
67
+ return capitalizeFirst(rtf.format(0, 'day'), locale);
68
+ };
69
+
70
+ /**
71
+ * Get the locale's short date format pattern (e.g. "MM/DD/YYYY" for en-US,
72
+ * "DD/MM/YYYY" for en-GB). Uses Intl.DateTimeFormat formatToParts to infer
73
+ * order and separators. Useful for parsing or building locale-aware inputs.
74
+ */
75
+ export const getDateFormatPattern = locale => {
76
+ const parts = new Intl.DateTimeFormat(locale, {
77
+ year: 'numeric',
78
+ month: '2-digit',
79
+ day: '2-digit'
80
+ }).formatToParts(new Date(2025, 0, 15));
81
+ return parts.map(part => {
82
+ switch (part.type) {
83
+ case 'day':
84
+ return 'DD';
85
+ case 'month':
86
+ return 'MM';
87
+ case 'year':
88
+ return 'YYYY';
89
+ default:
90
+ return part.value;
91
+ }
92
+ }).join('');
58
93
  };
59
94
 
60
95
  /**
61
96
  * Format a date for display in the date picker input (e.g. "2/15/2026").
62
97
  */
63
98
  export const formatDateForInput = (date, locale) => {
64
- return new Intl.DateTimeFormat(locale ?? 'en-US', {
99
+ return new Intl.DateTimeFormat(locale, {
65
100
  month: 'numeric',
66
101
  day: 'numeric',
67
102
  year: 'numeric'
@@ -79,52 +114,10 @@ export const parseDateFromInput = (value, locale) => {
79
114
  const trimmed = value.trim();
80
115
  if (!trimmed) return null;
81
116
  const parsed = new Date(trimmed);
82
- if (Number.isNaN(parsed.getTime())) return null;
117
+ if (!isValidDate(parsed)) return null;
83
118
  const formatted = formatDateForInput(parsed, locale);
84
119
  if (formatted === trimmed) return parsed;
85
120
  const parts = trimmed.split(/[/-]/);
86
121
  if (parts.length >= 3) return parsed;
87
122
  return null;
88
- };
89
- const RANGE_SEPARATOR = ' – ';
90
-
91
- /**
92
- * Format a date range for the input (e.g. "2/15/2026 – 2/20/2026").
93
- */
94
- export const formatDateRangeForInput = (startDate, endDate, locale) => {
95
- if (!startDate && !endDate) return '';
96
- if (!startDate) return formatDateForInput(endDate, locale);
97
- if (!endDate) return formatDateForInput(startDate, locale);
98
- return `${formatDateForInput(startDate, locale)}${RANGE_SEPARATOR}${formatDateForInput(endDate, locale)}`;
99
- };
100
-
101
- /**
102
- * Parse a range string (e.g. "2/15/2026 – 2/20/2026") into { startDate, endDate }.
103
- * Returns null if invalid. Single date is allowed and yields startDate = endDate.
104
- */
105
- export const parseDateRangeFromInput = (value, locale) => {
106
- const trimmed = value.trim();
107
- if (!trimmed) return null;
108
- const parts = trimmed.split(RANGE_SEPARATOR).map(s => s.trim());
109
- if (parts.length === 1) {
110
- const d = parseDateFromInput(parts[0], locale);
111
- if (!d) return null;
112
- return {
113
- startDate: d,
114
- endDate: new Date(d)
115
- };
116
- }
117
- if (parts.length === 2) {
118
- const start = parseDateFromInput(parts[0], locale);
119
- const end = parseDateFromInput(parts[1], locale);
120
- if (!start || !end) return null;
121
- return start.getTime() <= end.getTime() ? {
122
- startDate: start,
123
- endDate: end
124
- } : {
125
- startDate: end,
126
- endDate: start
127
- };
128
- }
129
- return null;
130
123
  };
@@ -10,4 +10,4 @@ export declare const getDatesWithRow: (weeks: (Date | null)[][]) => {
10
10
  export declare const keyHandler: (e: React.KeyboardEvent, date: Date, onFocusedDateChange: (date: Date | null) => void, datesWithRow: {
11
11
  date: Date;
12
12
  rowIndex: number;
13
- }[], month: number, year: number, disabledDates: Date[], onDateSelect: (date: Date) => void, onEscapeKeyPress?: () => void, onVisibleDateChange?: ((newDate: Date) => void) | undefined) => void;
13
+ }[], month: number, year: number, disabledDates: Date[], onDateSelect: (date: Date) => void, onEscapeKeyPress?: () => void, onVisibleDateChange?: ((newDate: Date) => void) | undefined, hasAdjacentMonthRight?: boolean, hasAdjacentMonthLeft?: boolean) => void;
@@ -21,14 +21,15 @@ export const getDatesWithRow = weeks => {
21
21
  });
22
22
  return result;
23
23
  };
24
- export const keyHandler = (e, date, onFocusedDateChange, datesWithRow, month, year, disabledDates, onDateSelect, onEscapeKeyPress, onVisibleDateChange) => {
25
- const key = date.getTime();
24
+ export const keyHandler = (e, date, onFocusedDateChange, datesWithRow, month, year, disabledDates, onDateSelect, onEscapeKeyPress, onVisibleDateChange, hasAdjacentMonthRight, hasAdjacentMonthLeft) => {
26
25
  const idx = datesWithRow.findIndex(({
27
- date: d
28
- }) => d.getTime() === key);
26
+ date: dateWithRow
27
+ }) => dateWithRow.getTime() === date.getTime());
29
28
  if (idx < 0) return;
30
29
  const currentRow = datesWithRow[idx].rowIndex;
31
30
  const day = date.getDate();
31
+ const hasRight = !!hasAdjacentMonthRight;
32
+ const hasLeft = !!hasAdjacentMonthLeft;
32
33
  let newDate = null;
33
34
  let newVisibleDate = null;
34
35
  switch (e.key) {
@@ -39,7 +40,9 @@ export const keyHandler = (e, date, onFocusedDateChange, datesWithRow, month, ye
39
40
  } else {
40
41
  const lastDayPrevMonth = new Date(year, month, 0);
41
42
  newDate = lastDayPrevMonth;
42
- newVisibleDate = new Date(year, month - 1, 1);
43
+ if (!hasLeft) {
44
+ newVisibleDate = new Date(year, month - 1, 1);
45
+ }
43
46
  }
44
47
  break;
45
48
  case 'ArrowRight':
@@ -48,7 +51,9 @@ export const keyHandler = (e, date, onFocusedDateChange, datesWithRow, month, ye
48
51
  newDate = datesWithRow[idx + 1].date;
49
52
  } else {
50
53
  newDate = new Date(year, month + 1, 1);
51
- newVisibleDate = new Date(year, month + 1, 1);
54
+ if (!hasRight) {
55
+ newVisibleDate = new Date(year, month + 1, 1);
56
+ }
52
57
  }
53
58
  break;
54
59
  case 'ArrowUp':
@@ -56,7 +61,9 @@ export const keyHandler = (e, date, onFocusedDateChange, datesWithRow, month, ye
56
61
  newDate = new Date(date);
57
62
  newDate.setDate(newDate.getDate() - 7);
58
63
  if (newDate.getMonth() !== month || newDate.getFullYear() !== year) {
59
- newVisibleDate = new Date(newDate.getFullYear(), newDate.getMonth(), 1);
64
+ if (!hasLeft) {
65
+ newVisibleDate = new Date(newDate.getFullYear(), newDate.getMonth(), 1);
66
+ }
60
67
  }
61
68
  break;
62
69
  case 'ArrowDown':
@@ -64,7 +71,9 @@ export const keyHandler = (e, date, onFocusedDateChange, datesWithRow, month, ye
64
71
  newDate = new Date(date);
65
72
  newDate.setDate(newDate.getDate() + 7);
66
73
  if (newDate.getMonth() !== month || newDate.getFullYear() !== year) {
67
- newVisibleDate = new Date(newDate.getFullYear(), newDate.getMonth(), 1);
74
+ if (!hasRight) {
75
+ newVisibleDate = new Date(newDate.getFullYear(), newDate.getMonth(), 1);
76
+ }
68
77
  }
69
78
  break;
70
79
  case 'Home':
@@ -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 { MiniArrowRightIcon } from '@codecademy/gamut-icons';
1
2
  import { useCallback, useId, useMemo, useRef, useState } from 'react';
2
- import { FlexBox } from '../Box';
3
+ import { Box, 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,11 +18,12 @@ 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);
@@ -44,6 +47,10 @@ export const DatePicker = props => {
44
47
  }
45
48
  }, [props]);
46
49
  const contextValue = useMemo(() => {
50
+ const translations = {
51
+ ...DEFAULT_DATE_PICKER_TRANSLATIONS,
52
+ ...translationsProp
53
+ };
47
54
  const base = {
48
55
  startOrSelectedDate,
49
56
  setSelection,
@@ -52,7 +59,8 @@ export const DatePicker = props => {
52
59
  closeCalendar,
53
60
  locale,
54
61
  disabledDates,
55
- calendarDialogId
62
+ calendarDialogId,
63
+ translations
56
64
  };
57
65
  return mode === 'range' ? {
58
66
  ...base,
@@ -64,26 +72,21 @@ export const DatePicker = props => {
64
72
  ...base,
65
73
  mode: 'single'
66
74
  };
67
- }, [mode, startOrSelectedDate, endDate, setSelection, activeRangePart, setActiveRangePart, isCalendarOpen, openCalendar, closeCalendar, locale, disabledDates, calendarDialogId]);
68
-
69
- // what is this doing
70
- // useEffect(() => {
71
- // if (!isCalendarOpen) return;
72
- // const id = setTimeout(() => inputRef.current?.focus(), 0);
73
- // return () => clearTimeout(id);
74
- // }, [isCalendarOpen]);
75
-
75
+ }, [mode, startOrSelectedDate, endDate, setSelection, activeRangePart, setActiveRangePart, isCalendarOpen, openCalendar, closeCalendar, locale, disabledDates, calendarDialogId, translationsProp]);
76
76
  const content = children !== undefined ? children : /*#__PURE__*/_jsxs(_Fragment, {
77
77
  children: [/*#__PURE__*/_jsx(FlexBox, {
78
78
  gap: 8,
79
79
  width: "fit-content",
80
- wrap: true,
81
80
  children: mode === 'range' ? /*#__PURE__*/_jsxs(_Fragment, {
82
81
  children: [/*#__PURE__*/_jsx(DatePickerInput, {
83
82
  label: props.startLabel,
84
83
  placeholder: placeholder,
85
84
  rangePart: "start",
86
85
  ref: inputRef
86
+ }), /*#__PURE__*/_jsx(Box, {
87
+ alignSelf: "center",
88
+ mt: 32,
89
+ children: /*#__PURE__*/_jsx(MiniArrowRightIcon, {})
87
90
  }), /*#__PURE__*/_jsx(DatePickerInput, {
88
91
  label: props.endLabel,
89
92
  placeholder: placeholder,
@@ -97,21 +100,19 @@ export const DatePicker = props => {
97
100
  })
98
101
  }), /*#__PURE__*/_jsx(PopoverContainer, {
99
102
  alignment: "bottom-left",
100
- allowPageInteraction: true
101
- // look into if we can kill this and mess with where we are focusing instead
102
- ,
103
+ allowPageInteraction: true,
103
104
  focusOnProps: {
104
105
  autoFocus: false,
105
106
  focusLock: false
106
107
  },
107
108
  invertAxis: "x",
108
109
  isOpen: isCalendarOpen,
109
- offset: 10,
110
110
  targetRef: inputRef,
111
- onRequestClose: closeCalendar // without this we cant type in the input but there has to be a better way
112
- ,
111
+ x: -20,
112
+ y: -16,
113
+ onRequestClose: closeCalendar,
113
114
  children: /*#__PURE__*/_jsx("div", {
114
- "aria-label": "Choose date",
115
+ "aria-label": contextValue.translations.calendarDialogAriaLabel,
115
116
  id: calendarDialogId,
116
117
  role: "dialog",
117
118
  children: /*#__PURE__*/_jsx(DatePickerCalendar, {
@@ -1,4 +1,6 @@
1
+ import { breakpoints } from '@codecademy/gamut-styles';
1
2
  import { useEffect, useId, useRef, useState } from 'react';
3
+ import { useMedia } from 'react-use';
2
4
  import { Box, FlexBox } from '../Box';
3
5
  import { Calendar, CalendarBody, CalendarFooter, CalendarHeader } from './Calendar';
4
6
  import { useDatePicker } from './DatePickerContext';
@@ -27,7 +29,8 @@ export const DatePickerCalendar = ({
27
29
  disabledDates,
28
30
  locale,
29
31
  closeCalendar,
30
- isCalendarOpen
32
+ isCalendarOpen,
33
+ translations
31
34
  } = context;
32
35
  const isRange = mode === 'range';
33
36
  const endDate = isRange ? context.endDate : undefined;
@@ -53,7 +56,7 @@ export const DatePickerCalendar = ({
53
56
  handleDateSelectSingle(date, startOrSelectedDate, setSelection);
54
57
  } else {
55
58
  context.setActiveRangePart(null);
56
- handleDateSelectRange(date, context.activeRangePart, startOrSelectedDate, context.endDate, setSelection);
59
+ handleDateSelectRange(date, context.activeRangePart, startOrSelectedDate, context.endDate, setSelection, disabledDates);
57
60
  }
58
61
  };
59
62
  const handleClearDate = () => {
@@ -69,6 +72,7 @@ export const DatePickerCalendar = ({
69
72
  const focusTarget = focusedDate ?? startOrSelectedDate ?? endDate ?? new Date();
70
73
  const addMonths = (date, n) => new Date(date.getFullYear(), date.getMonth() + n, 1);
71
74
  const secondMonthDate = addMonths(visibleDate, 1);
75
+ const isTwoMonthsVisible = useMedia(`(min-width: ${breakpoints.xs})`);
72
76
  return /*#__PURE__*/_jsxs(Calendar, {
73
77
  children: [/*#__PURE__*/_jsxs(Box, {
74
78
  p: 24,
@@ -83,6 +87,7 @@ export const DatePickerCalendar = ({
83
87
  disabledDates: disabledDates,
84
88
  endDate: endDate,
85
89
  focusedDate: focusTarget,
90
+ hasAdjacentMonthRight: isTwoMonthsVisible,
86
91
  labelledById: headingId,
87
92
  locale: locale,
88
93
  selectedDate: startOrSelectedDate,
@@ -105,6 +110,7 @@ export const DatePickerCalendar = ({
105
110
  disabledDates: disabledDates,
106
111
  endDate: endDate,
107
112
  focusedDate: focusTarget,
113
+ hasAdjacentMonthLeft: isTwoMonthsVisible,
108
114
  labelledById: headingId,
109
115
  locale: locale,
110
116
  selectedDate: startOrSelectedDate,
@@ -118,9 +124,11 @@ export const DatePickerCalendar = ({
118
124
  })]
119
125
  })]
120
126
  }), /*#__PURE__*/_jsx(CalendarFooter, {
127
+ clearText: translations.clearText,
128
+ disabled: startOrSelectedDate === null && endDate === null,
129
+ locale: locale,
130
+ showClearButton: isRange,
121
131
  onClearDate: handleClearDate,
122
- onCurrentMonthYearChange: setVisibleDate,
123
- onSelectedDateChange: date => date === null ? handleClearDate() : handleTodayClick(),
124
132
  onTodayClick: handleTodayClick
125
133
  })]
126
134
  });
@@ -8,4 +8,4 @@ export declare const DatePickerProvider: import("react").Provider<DatePickerCont
8
8
  * Must be used inside a DatePicker. For composed layouts, use this to get
9
9
  * openCalendar, closeCalendar, isCalendarOpen, inputRef, calendarDialogId, etc.
10
10
  */
11
- export declare function useDatePicker(): DatePickerContextValueType;
11
+ export declare const useDatePicker: () => DatePickerContextValueType;
@@ -9,10 +9,10 @@ export const DatePickerProvider = DatePickerContext.Provider;
9
9
  * Must be used inside a DatePicker. For composed layouts, use this to get
10
10
  * openCalendar, closeCalendar, isCalendarOpen, inputRef, calendarDialogId, etc.
11
11
  */
12
- export function useDatePicker() {
12
+ export const useDatePicker = () => {
13
13
  const value = useContext(DatePickerContext);
14
14
  if (value == null) {
15
15
  throw new Error('useDatePickerContext must be used within a DatePicker.');
16
16
  }
17
17
  return value;
18
- }
18
+ };
@@ -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,39 @@ 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
+ mb: 0,
109
+ pb: 0,
110
+ spacing: "tight",
111
+ width: "170px",
112
+ children: /*#__PURE__*/_jsx(Input, {
113
+ ...rest,
114
+ "aria-autocomplete": "none",
115
+ "aria-controls": calendarDialogId,
116
+ "aria-expanded": isCalendarOpen,
117
+ "aria-haspopup": "dialog",
118
+ icon: () => /*#__PURE__*/_jsx(MiniCalendarIcon, {
119
+ size: 16
120
+ }),
121
+ id: inputId,
122
+ placeholder: placeholder ?? getDateFormatPattern(locale),
123
+ ref: ref,
124
+ role: "combobox",
125
+ type: "text",
126
+ value: inputValue,
127
+ onBlur: handleBlur,
128
+ onChange: handleChange,
129
+ onClick: handleOpenCalendar,
130
+ onFocus: () => {
131
+ isInputFocusedRef.current = true;
132
+ if (isRange && rangePart) context.setActiveRangePart(rangePart);
133
+ },
134
+ onKeyDown: handleKeyDown
135
+ })
125
136
  });
126
137
  });
@@ -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). */
@@ -1,3 +1,5 @@
1
1
  import { ActiveRangePart } from './types';
2
+ /** True if any disabled date falls within [start, end] (inclusive, by calendar day). */
3
+ export declare const rangeContainsDisabled: (start: Date, end: Date, disabledDates: Date[]) => boolean;
2
4
  export declare const handleDateSelectSingle: (date: Date, selectedDate: Date | null, setSelection: (date: Date | null) => void) => void;
3
- export declare const handleDateSelectRange: (date: Date, activeRangePart: ActiveRangePart, startDate: Date | null, endDate: Date | null, setSelection: (startDate: Date | null, endDate?: Date | null) => void) => void;
5
+ export declare const handleDateSelectRange: (date: Date, activeRangePart: ActiveRangePart, startDate: Date | null, endDate: Date | null, setSelection: (startDate: Date | null, endDate?: Date | null) => void, disabledDates: Date[]) => void;