@codecademy/gamut 68.1.3-alpha.ce69cb.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.
Files changed (97) hide show
  1. package/dist/Alert/elements.d.ts +3 -3
  2. package/dist/Anchor/index.d.ts +9 -18
  3. package/dist/Anchor/index.js +6 -9
  4. package/dist/Box/GridBox.d.ts +0 -1
  5. package/dist/Box/GridBox.js +1 -1
  6. package/dist/Box/props.d.ts +1 -1
  7. package/dist/Button/CTAButton.d.ts +2 -2
  8. package/dist/Button/FillButton.d.ts +4 -4
  9. package/dist/Button/IconButton.d.ts +4 -4
  10. package/dist/Button/StrokeButton.d.ts +4 -4
  11. package/dist/Button/TextButton.d.ts +4 -4
  12. package/dist/Button/shared/InlineIconButton.d.ts +2 -2
  13. package/dist/Button/shared/styles.d.ts +3 -3
  14. package/dist/Button/shared/types.d.ts +1 -1
  15. package/dist/ButtonBase/ButtonBase.d.ts +4 -9
  16. package/dist/ButtonBase/ButtonBase.js +4 -11
  17. package/dist/Card/elements.d.ts +103 -109
  18. package/dist/Card/styles.d.ts +8 -8
  19. package/dist/Coachmark/index.d.ts +1 -1
  20. package/dist/ConnectedForm/utils.d.ts +1 -1
  21. package/dist/DatePicker/Calendar/Calendar.d.ts +9 -0
  22. package/dist/DatePicker/Calendar/Calendar.js +28 -0
  23. package/dist/DatePicker/Calendar/CalendarBody.d.ts +3 -0
  24. package/dist/DatePicker/Calendar/CalendarBody.js +174 -0
  25. package/dist/DatePicker/Calendar/CalendarFooter.d.ts +3 -0
  26. package/dist/DatePicker/Calendar/CalendarFooter.js +54 -0
  27. package/dist/DatePicker/Calendar/CalendarHeader.d.ts +3 -0
  28. package/dist/DatePicker/Calendar/CalendarHeader.js +86 -0
  29. package/dist/DatePicker/Calendar/index.d.ts +6 -0
  30. package/dist/DatePicker/Calendar/index.js +5 -0
  31. package/dist/DatePicker/Calendar/types.d.ts +64 -0
  32. package/dist/DatePicker/Calendar/types.js +1 -0
  33. package/dist/DatePicker/Calendar/utils/dateGrid.d.ts +30 -0
  34. package/dist/DatePicker/Calendar/utils/dateGrid.js +87 -0
  35. package/dist/DatePicker/Calendar/utils/format.d.ts +45 -0
  36. package/dist/DatePicker/Calendar/utils/format.js +123 -0
  37. package/dist/DatePicker/Calendar/utils/index.d.ts +3 -0
  38. package/dist/DatePicker/Calendar/utils/index.js +3 -0
  39. package/dist/DatePicker/Calendar/utils/keyHandler.d.ts +13 -0
  40. package/dist/DatePicker/Calendar/utils/keyHandler.js +125 -0
  41. package/dist/DatePicker/Calendar/utils/validation.d.ts +13 -0
  42. package/dist/DatePicker/Calendar/utils/validation.js +23 -0
  43. package/dist/DatePicker/DatePicker.d.ts +8 -0
  44. package/dist/DatePicker/DatePicker.js +128 -0
  45. package/dist/DatePicker/DatePickerCalendar.d.ts +13 -0
  46. package/dist/DatePicker/DatePickerCalendar.js +135 -0
  47. package/dist/DatePicker/DatePickerContext.d.ts +11 -0
  48. package/dist/DatePicker/DatePickerContext.js +18 -0
  49. package/dist/DatePicker/DatePickerInput.d.ts +16 -0
  50. package/dist/DatePicker/DatePickerInput.js +137 -0
  51. package/dist/DatePicker/index.d.ts +13 -0
  52. package/dist/DatePicker/index.js +10 -0
  53. package/dist/DatePicker/translations.d.ts +3 -0
  54. package/dist/DatePicker/translations.js +8 -0
  55. package/dist/DatePicker/types.d.ts +93 -0
  56. package/dist/DatePicker/types.js +1 -0
  57. package/dist/DatePicker/utils.d.ts +5 -0
  58. package/dist/DatePicker/utils.js +90 -0
  59. package/dist/Disclosure/elements.d.ts +12 -18
  60. package/dist/FeatureShimmer/index.js +1 -1
  61. package/dist/FocusTrap/index.d.ts +2 -2
  62. package/dist/Form/SelectDropdown/SelectDropdown.js +1 -1
  63. package/dist/Form/SelectDropdown/elements/controls.js +2 -2
  64. package/dist/Form/SelectDropdown/elements/multi-value.js +2 -2
  65. package/dist/Form/SelectDropdown/types/internal.d.ts +2 -2
  66. package/dist/Form/elements/Form.d.ts +15 -15
  67. package/dist/Form/elements/FormGroup.d.ts +1 -1
  68. package/dist/GridForm/GridFormButtons/index.d.ts +4 -4
  69. package/dist/List/ListProvider.d.ts +1 -1
  70. package/dist/List/elements.d.ts +43 -45
  71. package/dist/Menu/MenuItem.js +6 -10
  72. package/dist/Menu/elements.d.ts +2 -2
  73. package/dist/Modals/elements.d.ts +1 -1
  74. package/dist/Pagination/AnimatedPaginationButtons.d.ts +32 -34
  75. package/dist/Pagination/EllipsisButton.d.ts +4 -4
  76. package/dist/Pagination/PaginationButton.d.ts +6 -6
  77. package/dist/Pagination/utils.d.ts +30 -32
  78. package/dist/Pagination/utils.js +11 -14
  79. package/dist/Popover/Popover.js +4 -4
  80. package/dist/Popover/types.d.ts +2 -3
  81. package/dist/PopoverContainer/PopoverContainer.js +13 -9
  82. package/dist/PopoverContainer/hooks.d.ts +4 -16
  83. package/dist/PopoverContainer/hooks.js +24 -31
  84. package/dist/PopoverContainer/types.d.ts +7 -3
  85. package/dist/Tabs/TabButton.d.ts +2 -2
  86. package/dist/Tabs/TabNavLink.d.ts +2 -2
  87. package/dist/Tag/elements.d.ts +8 -14
  88. package/dist/Tip/InfoTip/InfoTipButton.d.ts +4 -4
  89. package/dist/Tip/PreviewTip/elements.d.ts +6 -12
  90. package/dist/Tip/__tests__/helpers.d.ts +1 -1
  91. package/dist/Tip/shared/FloatingTip.js +2 -2
  92. package/dist/Tip/shared/types.d.ts +2 -2
  93. package/dist/Typography/Text.d.ts +1 -1
  94. package/dist/index.d.ts +1 -0
  95. package/dist/index.js +1 -0
  96. package/dist/utils/react.js +1 -2
  97. package/package.json +11 -11
@@ -0,0 +1,135 @@
1
+ import { breakpoints } from '@codecademy/gamut-styles';
2
+ import { useEffect, useId, useRef, useState } from 'react';
3
+ import { useMedia } from 'react-use';
4
+ import { Box, FlexBox } from '../Box';
5
+ import { Calendar, CalendarBody, CalendarFooter, CalendarHeader } from './Calendar';
6
+ import { useDatePicker } from './DatePickerContext';
7
+ import { handleDateSelectRange, handleDateSelectSingle } from './utils';
8
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
+ /**
10
+ * Calendar that composes Calendar, CalendarHeader, CalendarBody, CalendarFooter.
11
+ * When inside DatePicker: owns local visibleDate and focusedDate; updates shared
12
+ * state via context. Supports single-date and range modes.
13
+ */
14
+ export const DatePickerCalendar = ({
15
+ dialogId,
16
+ weekStartsOn = 0
17
+ }) => {
18
+ const context = useDatePicker();
19
+ const generatedId = useId();
20
+ const fallbackDialogId = `datepicker-calendar-${generatedId.replace(/:/g, '')}`;
21
+ const headingId = dialogId ?? context?.calendarDialogId ?? fallbackDialogId;
22
+ if (context == null) {
23
+ throw new Error('DatePickerCalendar must be used inside a DatePicker (it reads shared state from context).');
24
+ }
25
+ const {
26
+ mode,
27
+ startOrSelectedDate,
28
+ setSelection,
29
+ disabledDates,
30
+ locale,
31
+ closeCalendar,
32
+ isCalendarOpen,
33
+ translations
34
+ } = context;
35
+ const isRange = mode === 'range';
36
+ const endDate = isRange ? context.endDate : undefined;
37
+ const firstOfMonth = date => new Date(date.getFullYear(), date.getMonth(), 1);
38
+ const [visibleDate, setVisibleDate] = useState(() => firstOfMonth(startOrSelectedDate ?? new Date()));
39
+ const [focusedDate, setFocusedDate] = useState(() => startOrSelectedDate ?? endDate ?? new Date());
40
+ const wasOpenRef = useRef(false);
41
+
42
+ // Sync visible month to selection only when the calendar opens, not on every
43
+ // date click. Otherwise clicking a date in the second month would jump the view.
44
+ useEffect(() => {
45
+ const justOpened = isCalendarOpen && !wasOpenRef.current;
46
+ wasOpenRef.current = isCalendarOpen;
47
+ if (!justOpened) return;
48
+ const anchor = startOrSelectedDate ?? endDate;
49
+ if (anchor) {
50
+ setVisibleDate(firstOfMonth(anchor));
51
+ setFocusedDate(startOrSelectedDate ?? endDate ?? new Date());
52
+ }
53
+ }, [isCalendarOpen, startOrSelectedDate, endDate]);
54
+ const onDateSelect = date => {
55
+ if (!isRange) {
56
+ handleDateSelectSingle(date, startOrSelectedDate, setSelection);
57
+ } else {
58
+ context.setActiveRangePart(null);
59
+ handleDateSelectRange(date, context.activeRangePart, startOrSelectedDate, context.endDate, setSelection, disabledDates);
60
+ }
61
+ };
62
+ const handleClearDate = () => {
63
+ setSelection(null);
64
+ setFocusedDate(visibleDate);
65
+ };
66
+ const handleTodayClick = () => {
67
+ const today = new Date();
68
+ setSelection(today);
69
+ setVisibleDate(firstOfMonth(today));
70
+ setFocusedDate(today);
71
+ };
72
+ const focusTarget = focusedDate ?? startOrSelectedDate ?? endDate ?? new Date();
73
+ const addMonths = (date, n) => new Date(date.getFullYear(), date.getMonth() + n, 1);
74
+ const secondMonthDate = addMonths(visibleDate, 1);
75
+ const isTwoMonthsVisible = useMedia(`(min-width: ${breakpoints.xs})`);
76
+ return /*#__PURE__*/_jsxs(Calendar, {
77
+ children: [/*#__PURE__*/_jsxs(Box, {
78
+ p: 24,
79
+ children: [/*#__PURE__*/_jsx(CalendarHeader, {
80
+ currentMonthYear: visibleDate,
81
+ headingId: headingId,
82
+ locale: locale,
83
+ secondMonthYear: secondMonthDate,
84
+ onCurrentMonthYearChange: setVisibleDate
85
+ }), /*#__PURE__*/_jsxs(FlexBox, {
86
+ children: [/*#__PURE__*/_jsx(CalendarBody, {
87
+ disabledDates: disabledDates,
88
+ endDate: endDate,
89
+ focusedDate: focusTarget,
90
+ hasAdjacentMonthRight: isTwoMonthsVisible,
91
+ labelledById: headingId,
92
+ locale: locale,
93
+ selectedDate: startOrSelectedDate,
94
+ visibleDate: visibleDate,
95
+ weekStartsOn: weekStartsOn,
96
+ onDateSelect: onDateSelect,
97
+ onEscapeKeyPress: closeCalendar,
98
+ onFocusedDateChange: setFocusedDate,
99
+ onVisibleDateChange: setVisibleDate
100
+ }), /*#__PURE__*/_jsx(Box, {
101
+ display: {
102
+ _: 'none',
103
+ xs: 'initial'
104
+ },
105
+ pl: {
106
+ _: 0,
107
+ xs: 32
108
+ },
109
+ children: /*#__PURE__*/_jsx(CalendarBody, {
110
+ disabledDates: disabledDates,
111
+ endDate: endDate,
112
+ focusedDate: focusTarget,
113
+ hasAdjacentMonthLeft: isTwoMonthsVisible,
114
+ labelledById: headingId,
115
+ locale: locale,
116
+ selectedDate: startOrSelectedDate,
117
+ visibleDate: secondMonthDate,
118
+ weekStartsOn: weekStartsOn,
119
+ onDateSelect: onDateSelect,
120
+ onEscapeKeyPress: closeCalendar,
121
+ onFocusedDateChange: setFocusedDate,
122
+ onVisibleDateChange: setVisibleDate
123
+ })
124
+ })]
125
+ })]
126
+ }), /*#__PURE__*/_jsx(CalendarFooter, {
127
+ clearText: translations.clearText,
128
+ disabled: startOrSelectedDate === null && endDate === null,
129
+ locale: locale,
130
+ showClearButton: isRange,
131
+ onClearDate: handleClearDate,
132
+ onTodayClick: handleTodayClick
133
+ })]
134
+ });
135
+ };
@@ -0,0 +1,11 @@
1
+ /// <reference types="react" />
2
+ import type { DatePickerContextValue as DatePickerContextValueType } from './types';
3
+ export declare const DatePickerContext: import("react").Context<DatePickerContextValueType | null>;
4
+ /** Provider component; DatePicker uses this to set the context value. */
5
+ export declare const DatePickerProvider: import("react").Provider<DatePickerContextValueType | null>;
6
+ /**
7
+ * Returns the DatePicker context value (shared state and callbacks).
8
+ * Must be used inside a DatePicker. For composed layouts, use this to get
9
+ * openCalendar, closeCalendar, isCalendarOpen, inputRef, calendarDialogId, etc.
10
+ */
11
+ export declare const useDatePicker: () => DatePickerContextValueType;
@@ -0,0 +1,18 @@
1
+ import { createContext, useContext } from 'react';
2
+ export const DatePickerContext = /*#__PURE__*/createContext(null);
3
+
4
+ /** Provider component; DatePicker uses this to set the context value. */
5
+ export const DatePickerProvider = DatePickerContext.Provider;
6
+
7
+ /**
8
+ * Returns the DatePicker context value (shared state and callbacks).
9
+ * Must be used inside a DatePicker. For composed layouts, use this to get
10
+ * openCalendar, closeCalendar, isCalendarOpen, inputRef, calendarDialogId, etc.
11
+ */
12
+ export const useDatePicker = () => {
13
+ const value = useContext(DatePickerContext);
14
+ if (value == null) {
15
+ throw new Error('useDatePickerContext must be used within a DatePicker.');
16
+ }
17
+ return value;
18
+ };
@@ -0,0 +1,16 @@
1
+ import { ComponentProps } from 'react';
2
+ import { Input } from '../Form/inputs/Input';
3
+ /**
4
+ * Props for DatePickerInput. When used inside DatePicker, only overrides (e.g. placeholder, label).
5
+ * In range mode, use rangePart to bind to start or end date. When outside DatePicker, pass value, onChange, etc.
6
+ */
7
+ export type DatePickerInputProps = Omit<ComponentProps<typeof Input>, 'type' | 'icon'> & {
8
+ /** In range mode: which part of the range this input edits. Omit for single-date or combined display. */
9
+ rangePart?: 'start' | 'end';
10
+ };
11
+ /**
12
+ * Date input. When inside DatePicker: owns local input value state and syncs to
13
+ * shared selectedDate via context on blur/parse; opens calendar on click/arrow down.
14
+ * When outside DatePicker: fully controlled by props.
15
+ */
16
+ export declare const DatePickerInput: import("react").ForwardRefExoticComponent<Omit<DatePickerInputProps, "ref"> & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,137 @@
1
+ import { MiniCalendarIcon } from '@codecademy/gamut-icons';
2
+ import { forwardRef, useEffect, useId, useRef, useState } from 'react';
3
+ import { FormGroup } from '../Form/elements/FormGroup';
4
+ import { Input } from '../Form/inputs/Input';
5
+ import { formatDateForInput, getDateFormatPattern, parseDateFromInput } from './Calendar/utils/format';
6
+ import { useDatePicker } from './DatePickerContext';
7
+
8
+ /**
9
+ * Props for DatePickerInput. When used inside DatePicker, only overrides (e.g. placeholder, label).
10
+ * In range mode, use rangePart to bind to start or end date. When outside DatePicker, pass value, onChange, etc.
11
+ */
12
+ import { jsx as _jsx } from "react/jsx-runtime";
13
+ /**
14
+ * Date input. When inside DatePicker: owns local input value state and syncs to
15
+ * shared selectedDate via context on blur/parse; opens calendar on click/arrow down.
16
+ * When outside DatePicker: fully controlled by props.
17
+ */
18
+ export const DatePickerInput = /*#__PURE__*/forwardRef(({
19
+ placeholder,
20
+ label,
21
+ rangePart,
22
+ ...rest
23
+ }, ref) => {
24
+ const context = useDatePicker();
25
+ // do we want to do this or just throw an error?
26
+ // if (context == null) {
27
+ // return (
28
+ // <Input
29
+ // {...rest}
30
+ // icon={CalendarIcon}
31
+ // placeholder={placeholder ?? 'MM/DD/YYYY'}
32
+ // ref={ref}
33
+ // type="text"
34
+ // />
35
+ // );
36
+ // }
37
+ if (context == null) {
38
+ throw new Error('DatePickerInput must be used inside a DatePicker (it reads shared state from context).');
39
+ }
40
+ const {
41
+ mode,
42
+ startOrSelectedDate,
43
+ setSelection,
44
+ openCalendar,
45
+ locale,
46
+ isCalendarOpen,
47
+ calendarDialogId,
48
+ translations
49
+ } = context;
50
+ const isRange = mode === 'range';
51
+ const inputID = useId();
52
+ const inputId = `datepicker-input-${inputID.replace(/:/g, '')}`;
53
+
54
+ // Range with two inputs: each input binds to one part. Single or range combined: one value.
55
+ const boundDate = isRange && rangePart === 'end' ? context.endDate : startOrSelectedDate;
56
+ const formattedValue = boundDate != null ? formatDateForInput(boundDate, locale) : '';
57
+ const [inputValue, setInputValue] = useState(() => formattedValue);
58
+ const isInputFocusedRef = useRef(false);
59
+
60
+ // Sync input from shared state. Skip when focused so we don't overwrite while typing.
61
+ useEffect(() => {
62
+ if (!isInputFocusedRef.current) {
63
+ setInputValue(formattedValue);
64
+ }
65
+ }, [formattedValue]);
66
+
67
+ /** Apply raw input string to selection state. Returns formatted string if parsed so caller can sync input (e.g. on blur). */
68
+ const applyValueToSelection = raw => {
69
+ const trimmed = raw.trim();
70
+ if (!trimmed) {
71
+ if (isRange && rangePart) {
72
+ if (rangePart === 'start') setSelection(null, context.endDate);else setSelection(startOrSelectedDate, null);
73
+ } else setSelection(null);
74
+ return undefined;
75
+ }
76
+ const parsed = parseDateFromInput(trimmed, locale);
77
+ if (!parsed) return undefined;
78
+ if (isRange && rangePart) {
79
+ if (rangePart === 'start') setSelection(parsed, context.endDate);else setSelection(startOrSelectedDate, parsed);
80
+ } else setSelection(parsed);
81
+ return formatDateForInput(parsed, locale);
82
+ };
83
+ const handleChange = e => {
84
+ const raw = e.target.value;
85
+ setInputValue(raw);
86
+ applyValueToSelection(raw);
87
+ };
88
+ const handleBlur = () => {
89
+ isInputFocusedRef.current = false;
90
+ const formatted = applyValueToSelection(inputValue.trim());
91
+ if (formatted) setInputValue(formatted);else if (inputValue.trim()) setInputValue(formattedValue);
92
+ };
93
+ const handleKeyDown = e => {
94
+ if (e.key === 'ArrowDown' || e.key === 'Down') {
95
+ e.preventDefault();
96
+ openCalendar();
97
+ }
98
+ };
99
+ const handleOpenCalendar = () => {
100
+ openCalendar();
101
+ };
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
106
+ ,
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
+ })
136
+ });
137
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * DatePicker – Single-date and range picker with input + calendar popover.
3
+ */
4
+ export type { DatePickerContextValue, DatePickerProps, DatePickerRangeProps, DatePickerSingleProps, } from './types';
5
+ export { DatePicker } from './DatePicker';
6
+ export { DatePickerContext, DatePickerProvider, useDatePicker, } from './DatePickerContext';
7
+ export { DatePickerCalendar } from './DatePickerCalendar';
8
+ export type { DatePickerCalendarProps } from './DatePickerCalendar';
9
+ export { DatePickerInput } from './DatePickerInput';
10
+ export type { DatePickerInputProps } from './DatePickerInput';
11
+ export { Calendar, CalendarHeader, CalendarBody, CalendarFooter, } from './Calendar';
12
+ export type { CalendarHeaderProps, CalendarBodyProps, CalendarFooterProps, QuickAction, } from './Calendar/types';
13
+ export * from './Calendar/utils';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * DatePicker – Single-date and range picker with input + calendar popover.
3
+ */
4
+
5
+ export { DatePicker } from './DatePicker';
6
+ export { DatePickerContext, DatePickerProvider, useDatePicker } from './DatePickerContext';
7
+ export { DatePickerCalendar } from './DatePickerCalendar';
8
+ export { DatePickerInput } from './DatePickerInput';
9
+ export { Calendar, CalendarHeader, CalendarBody, CalendarFooter } from './Calendar';
10
+ export * from './Calendar/utils';
@@ -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
+ };
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Public and internal types for the DatePicker (single-date and range).
3
+ */
4
+ /// <reference types="react" />
5
+ /** Result of custom validation; null means valid. */
6
+ export interface DatePickerValidationResult {
7
+ errorMessage: string;
8
+ errorType: string;
9
+ }
10
+ /** Shared props for all DatePicker modes. */
11
+ export interface DatePickerBaseProps {
12
+ /** Locale for formatting (e.g. 'en-US'). */
13
+ locale?: string;
14
+ /** Dates that are disabled (unselectable) in the calendar. */
15
+ disabledDates?: Date[];
16
+ /** When provided, only the provider is rendered and children compose Input + Calendar. */
17
+ children?: React.ReactNode;
18
+ /** Placeholder for the input. */
19
+ placeholder?: string;
20
+ /** Override UI strings (e.g. clear button). Merged with defaults. */
21
+ translations?: DatePickerTranslations;
22
+ }
23
+ /** Props for the DatePicker (single-date mode). */
24
+ export interface DatePickerSingleProps extends DatePickerBaseProps {
25
+ mode?: 'single';
26
+ /** Controlled selected date. */
27
+ selectedDate: Date | null;
28
+ /** Called when the user selects a date. */
29
+ setSelectedDate: (date: Date | null) => void;
30
+ /** Label for the input. */
31
+ label?: string;
32
+ }
33
+ /** Props for the DatePicker (range mode). */
34
+ export interface DatePickerRangeProps extends DatePickerBaseProps {
35
+ mode: 'range';
36
+ /** Controlled start date. */
37
+ startDate: Date | null;
38
+ /** Controlled end date. */
39
+ endDate: Date | null;
40
+ /** Called when the user changes the start date. */
41
+ setStartDate: (date: Date | null) => void;
42
+ /** Called when the user changes the end date. */
43
+ setEndDate: (date: Date | null) => void;
44
+ /** Label for the start date input. */
45
+ startLabel?: string;
46
+ /** Label for the end date input. */
47
+ endLabel?: string;
48
+ }
49
+ /** Props for the DatePicker provider / standalone component. */
50
+ export type DatePickerProps = DatePickerSingleProps | DatePickerRangeProps;
51
+ /** Which range input is active (focused); null = calendar drives both (selection mode). */
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
+ }
66
+ /** Shared state provided by DatePicker via context. */
67
+ export interface DatePickerBaseContextValue {
68
+ isCalendarOpen: boolean;
69
+ openCalendar: () => void;
70
+ closeCalendar: () => void;
71
+ locale?: string;
72
+ disabledDates: Date[];
73
+ calendarDialogId: string;
74
+ /** UI string overrides (e.g. clear button). */
75
+ translations: Required<DatePickerTranslations>;
76
+ /** Start date (range) or selected date (single). */
77
+ startOrSelectedDate: Date | null;
78
+ /** Set selection. Single: (date). Range: (start, end). */
79
+ setSelection: (startOrSelectedDate: Date | null, endDate?: Date | null) => void;
80
+ }
81
+ export interface DatePickerSingleContextValue extends DatePickerBaseContextValue {
82
+ mode: 'single';
83
+ }
84
+ export interface DatePickerRangeContextValue extends DatePickerBaseContextValue {
85
+ mode: 'range';
86
+ /** Range only: end date. */
87
+ endDate: Date | null;
88
+ /** Range only: which input is active (start/end focused); null = selection mode. */
89
+ activeRangePart: ActiveRangePart;
90
+ /** Range only: set which input is active (e.g. when input receives focus). */
91
+ setActiveRangePart: (part: ActiveRangePart) => void;
92
+ }
93
+ export type DatePickerContextValue = DatePickerSingleContextValue | DatePickerRangeContextValue;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
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;
4
+ export declare const handleDateSelectSingle: (date: Date, selectedDate: Date | null, setSelection: (date: 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;
@@ -0,0 +1,90 @@
1
+ import { isDateInRange, isSameDay } from './Calendar/utils/dateGrid';
2
+ /** True if any disabled date falls within [start, end] (inclusive, by calendar day). */
3
+ export const rangeContainsDisabled = (start, end, disabledDates) => {
4
+ return disabledDates.some(date => isSameDay(date, start) || isSameDay(date, end) || isDateInRange(date, start, end));
5
+ };
6
+ export const handleDateSelectSingle = (date, selectedDate, setSelection) => {
7
+ // If clicked date is the same as Start Date: Clear Start Date
8
+ if (selectedDate && date.getTime() === selectedDate.getTime()) {
9
+ setSelection(null);
10
+ return;
11
+ }
12
+ // If clicked date is not the same as Start Date: Set Start Date to clicked date
13
+ setSelection(date);
14
+ };
15
+ const applyRangeOrNewStart = (start, end, clickedDate, disabledDates, setSelection) => {
16
+ // if range contains disabled dates, set start date to clicked date and end date to null
17
+ if (rangeContainsDisabled(start, end, disabledDates)) {
18
+ setSelection(clickedDate, null);
19
+ } else {
20
+ setSelection(start, end);
21
+ }
22
+ };
23
+ export const handleDateSelectRange = (date, activeRangePart, startDate, endDate, setSelection, disabledDates) => {
24
+ // Range mode: field targeting (start or end input was focused)
25
+ if (activeRangePart === 'start') {
26
+ if (date.getTime() === startDate?.getTime()) {
27
+ setSelection(null, endDate);
28
+ return;
29
+ }
30
+ const newEnd = endDate != null && date.getTime() <= endDate.getTime() ? endDate : null;
31
+ if (newEnd != null) {
32
+ applyRangeOrNewStart(date, newEnd, date, disabledDates, setSelection);
33
+ } else {
34
+ setSelection(date, newEnd);
35
+ }
36
+ return;
37
+ }
38
+ if (activeRangePart === 'end') {
39
+ if (date.getTime() === endDate?.getTime()) {
40
+ setSelection(startDate, null);
41
+ return;
42
+ }
43
+ const newStart = startDate != null && date.getTime() >= startDate.getTime() ? startDate : null;
44
+ if (newStart != null) {
45
+ applyRangeOrNewStart(newStart, date, date, disabledDates, setSelection);
46
+ } else {
47
+ setSelection(newStart, date);
48
+ }
49
+ return;
50
+ }
51
+
52
+ // Range selection mode (no field focused: calendar drives both)
53
+ if (startDate && endDate) {
54
+ // if start date is end date and is clicked, clears everything
55
+ if (startDate.getTime() === endDate.getTime() && date.getTime() === startDate.getTime()) {
56
+ setSelection(null, null);
57
+ return;
58
+ }
59
+ // if clicked on start date, end date becomes start date
60
+ if (date.getTime() === startDate.getTime()) {
61
+ setSelection(endDate, null);
62
+ return;
63
+ }
64
+ // if clicked on end date, clears end date and start remains
65
+ if (date.getTime() === endDate.getTime()) {
66
+ setSelection(startDate, null);
67
+ return;
68
+ }
69
+ // If clicked date > Start: Updates End Date to new date (Start remains)
70
+ if (date.getTime() > startDate.getTime()) {
71
+ applyRangeOrNewStart(startDate, date, date, disabledDates, setSelection);
72
+ return;
73
+ }
74
+ // If clicked date < Start: Updates Start Date to new date (End remains) - extends range to the left
75
+ applyRangeOrNewStart(date, endDate, date, disabledDates, setSelection);
76
+ return;
77
+ }
78
+ // Start is Set, End is Empty
79
+ if (startDate && !endDate) {
80
+ // If clicked date < Start: Restarts selection with clicked date as new Start
81
+ if (date.getTime() < startDate.getTime()) {
82
+ setSelection(date, null);
83
+ } else {
84
+ // If clicked date > Start: Sets it as End Date (if range valid)
85
+ applyRangeOrNewStart(startDate, date, date, disabledDates, setSelection);
86
+ }
87
+ return;
88
+ }
89
+ setSelection(date, null);
90
+ };