reshaped 3.9.0-canary.2 → 3.9.0-canary.4

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.
@@ -11,7 +11,7 @@ import useIsomorphicLayoutEffect from "../../hooks/useIsomorphicLayoutEffect.js"
11
11
  import s from "./Autocomplete.module.css";
12
12
  const AutocompleteContext = React.createContext({});
13
13
  const Autocomplete = (props) => {
14
- const { children, onChange, onInput, onItemSelect, name, containerRef, instanceRef, onBackspace, onEnter, active, onOpen, onClose, fallbackAdjustLayout, fallbackMinWidth, fallbackMinHeight, ...textFieldProps } = props;
14
+ const { children, onChange, onInput, onItemSelect, name, containerRef, instanceRef, onBackspace, onEnter, active, onOpen, onClose, fallbackAdjustLayout, fallbackMinWidth, fallbackMinHeight, contentMaxHeight, ...textFieldProps } = props;
15
15
  const [highlightedId, setHighlightedId] = React.useState();
16
16
  const onBackspaceRef = useHandlerRef(onBackspace);
17
17
  const contentRef = React.useRef(null);
@@ -120,7 +120,7 @@ const Autocomplete = (props) => {
120
120
  highlightedId,
121
121
  setHighlightedId,
122
122
  }), [highlightedId, handleItemClick]);
123
- return (_jsx(AutocompleteContext.Provider, { value: contextValue, children: _jsxs(DropdownMenu, { position: "bottom", width: "trigger", triggerType: "focus", trapFocusMode: false, active: isDropdownActive, onClose: handleClose, onOpen: handleOpen, containerRef: containerRef, fallbackAdjustLayout: fallbackAdjustLayout, fallbackMinWidth: fallbackMinWidth, fallbackMinHeight: fallbackMinHeight, disableHideAnimation: true, instanceRef: instanceRef, children: [_jsx(DropdownMenu.Trigger, { children: ({ ref, ...attributes }) => (_jsx(TextField, { ...textFieldProps, name: name, onChange: handleChange, focused: isDropdownActive, attributes: {
123
+ return (_jsx(AutocompleteContext.Provider, { value: contextValue, children: _jsxs(DropdownMenu, { position: "bottom", width: "trigger", triggerType: "focus", trapFocusMode: false, active: isDropdownActive, onClose: handleClose, onOpen: handleOpen, containerRef: containerRef, fallbackAdjustLayout: fallbackAdjustLayout, fallbackMinWidth: fallbackMinWidth, fallbackMinHeight: fallbackMinHeight, contentMaxHeight: contentMaxHeight, disableHideAnimation: true, instanceRef: instanceRef, children: [_jsx(DropdownMenu.Trigger, { children: ({ ref, ...attributes }) => (_jsx(TextField, { ...textFieldProps, name: name, onChange: handleChange, focused: isDropdownActive, attributes: {
124
124
  ...textFieldProps.attributes,
125
125
  // Ignoring the type check since TS can't infer the correct html element type
126
126
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -7,7 +7,7 @@ type SelectArgs = {
7
7
  /** Additional data that will be passed to the onItemSelect callback */
8
8
  data?: unknown;
9
9
  };
10
- export type Props = TextFieldProps & Pick<DropdownMenuProps, "containerRef" | "instanceRef" | "active" | "onOpen" | "onClose" | "fallbackAdjustLayout" | "fallbackMinWidth" | "fallbackMinHeight"> & {
10
+ export type Props = TextFieldProps & Pick<DropdownMenuProps, "containerRef" | "instanceRef" | "active" | "onOpen" | "onClose" | "fallbackAdjustLayout" | "fallbackMinWidth" | "fallbackMinHeight" | "contentMaxHeight"> & {
11
11
  /** Callback for when value changes from user input */
12
12
  onInput?: TextFieldProps["onChange"];
13
13
  /** Callback for when an item is selected in the dropdown */
@@ -1,19 +1,31 @@
1
1
  export type SelectionMode = "date" | "month";
2
2
  export type BaseProps = {
3
- /** Default month to display
3
+ /** Default month to display. Used in uncontrolled mode to provide the initial month and then updated by the component internally
4
4
  * @default Date.now()
5
5
  */
6
6
  defaultMonth?: Date;
7
+ /** Month to display. Used in controlled mode and should be updated using the `onMonthChange` callback */
8
+ month?: Date;
9
+ /** Callback when the month changes */
10
+ onMonthChange?: (args: {
11
+ date: Date;
12
+ }) => void;
7
13
  /** Minimum date that can be selected */
8
14
  min?: Date;
9
15
  /** Maximum date that can be selected */
10
16
  max?: Date;
17
+ /** Number of months to render at the same time
18
+ * @default 1
19
+ */
20
+ monthsToRender?: number;
11
21
  /** First day of the week
12
22
  * @default 1, Monday
13
23
  */
14
24
  firstWeekDay?: number;
15
25
  /** Dates that are selected */
16
26
  selectedDates?: Date[];
27
+ /** Dates that are disabled */
28
+ disabledDates?: Date[];
17
29
  /** Render a custom weekday label, can be used for localization */
18
30
  renderWeekDay?: (args: {
19
31
  weekDay: number;
@@ -111,7 +123,7 @@ export type MonthProps = {
111
123
  hoveredDate: Date | null;
112
124
  onDateHover: (date: Date) => void;
113
125
  onDateHoverEnd: (date: Date) => void;
114
- } & Pick<BaseProps, "max" | "min" | "firstWeekDay" | "selectedDates" | "renderMonthLabel" | "renderWeekDay" | "renderSelectedMonthLabel" | "renderDateAriaLabel" | "renderDateSlot"> & Pick<ControlledProps, "value" | "onChange" | "range">;
126
+ } & Pick<BaseProps, "max" | "min" | "firstWeekDay" | "selectedDates" | "disabledDates" | "renderMonthLabel" | "renderWeekDay" | "renderSelectedMonthLabel" | "renderDateAriaLabel" | "renderDateSlot"> & Pick<ControlledProps, "value" | "onChange" | "range">;
115
127
  export type YearProps = {
116
128
  monthDate: Date;
117
129
  onMonthClick: (month: number) => void;
@@ -126,15 +138,16 @@ export type DateProps = {
126
138
  renderAriaLabel?: MonthProps["renderDateAriaLabel"];
127
139
  renderSlot?: MonthProps["renderDateSlot"];
128
140
  onDateFocus: (date: Date) => void;
129
- } & Pick<MonthProps, "hoveredDate" | "onDateHover" | "onDateHoverEnd" | "onChange" | "range" | "min" | "max" | "selectedDates">;
141
+ } & Pick<MonthProps, "hoveredDate" | "onDateHover" | "onDateHoverEnd" | "onChange" | "range" | "min" | "max" | "selectedDates" | "disabledDates">;
130
142
  export type ControlsProps = {
131
143
  selectionMode: SelectionMode;
144
+ monthsToRender: number;
132
145
  onMonthTitleClick: () => void;
133
- monthTitleRef: React.MutableRefObject<HTMLButtonElement | null>;
146
+ monthTitleRef?: React.RefObject<HTMLButtonElement | null>;
134
147
  monthDate: Date;
135
148
  renderSelectedMonthLabel?: BaseProps["renderSelectedMonthLabel"];
136
- isFirstMonth?: boolean;
137
- isLastMonth?: boolean;
149
+ hidePrevious?: boolean;
150
+ hideNext?: boolean;
138
151
  onNextClick: () => void;
139
152
  onPreviousClick: () => void;
140
153
  } & Pick<BaseProps, "nextMonthAriaLabel" | "nextYearAriaLabel" | "previousMonthAriaLabel" | "previousYearAriaLabel" | "monthSelectionAriaLabel">;
@@ -9,9 +9,9 @@ import CalendarMonth from "./CalendarMonth.js";
9
9
  import CalendarYear from "./CalendarYear.js";
10
10
  import useCalendarKeyboardNavigation from "./useCalendarKeyboardNavigation.js";
11
11
  const CalendarControlled = (props) => {
12
- const { value, onChange, defaultMonth, min, max, range, firstWeekDay, selectedDates, renderMonthLabel, renderSelectedMonthLabel, renderWeekDay, previousMonthAriaLabel, previousYearAriaLabel, nextMonthAriaLabel, nextYearAriaLabel, monthSelectionAriaLabel, renderMonthAriaLabel, renderDateAriaLabel, renderDateSlot, } = props;
12
+ const { value, onChange, defaultMonth, month, onMonthChange, min, max, range, firstWeekDay, selectedDates, disabledDates, monthsToRender = 1, renderMonthLabel, renderSelectedMonthLabel, renderWeekDay, previousMonthAriaLabel, previousYearAriaLabel, nextMonthAriaLabel, nextYearAriaLabel, monthSelectionAriaLabel, renderMonthAriaLabel, renderDateAriaLabel, renderDateSlot, } = props;
13
13
  const [selectionMode, setSelectionMode] = React.useState("date");
14
- const [monthDate, setMonthDate] = React.useState(defaultMonth || new Date());
14
+ const [monthDate, setMonthDate] = React.useState(month || defaultMonth || new Date());
15
15
  const [hoveredDate, setHoveredDate] = React.useState(null);
16
16
  const monthTitleRef = React.useRef(null);
17
17
  const prevSelectionModeRef = React.useRef(selectionMode);
@@ -19,23 +19,38 @@ const CalendarControlled = (props) => {
19
19
  const selectionRootRef = React.useRef(null);
20
20
  const handlePreviousClick = () => {
21
21
  if (selectionMode === "month") {
22
- setMonthDate((prev) => setYearToPrevious(prev));
22
+ const updatedMonth = setYearToPrevious(monthDate);
23
+ onMonthChange?.({ date: updatedMonth });
24
+ if (month === undefined)
25
+ setMonthDate(updatedMonth);
23
26
  return;
24
27
  }
25
- setMonthDate((prev) => setMonthToPrevious(prev));
28
+ const updatedMonth = setMonthToPrevious(monthDate);
29
+ onMonthChange?.({ date: updatedMonth });
30
+ if (month === undefined)
31
+ setMonthDate(updatedMonth);
26
32
  };
27
33
  const handleNextClick = () => {
28
34
  if (selectionMode === "month") {
29
- setMonthDate((prev) => setYearToNext(prev));
35
+ const updatedMonth = setYearToNext(monthDate);
36
+ onMonthChange?.({ date: updatedMonth });
37
+ if (month === undefined)
38
+ setMonthDate(updatedMonth);
30
39
  return;
31
40
  }
32
- setMonthDate((prev) => setMonthToNext(prev));
41
+ const updatedMonth = setMonthToNext(monthDate);
42
+ onMonthChange?.({ date: updatedMonth });
43
+ if (month === undefined)
44
+ setMonthDate(updatedMonth);
33
45
  };
34
46
  const handleMonthTitleClick = () => {
35
47
  setSelectionMode("month");
36
48
  };
37
49
  const handleMonthClick = (i) => {
38
- setMonthDate((prev) => setMonthTo(prev, i));
50
+ const updatedMonth = setMonthTo(monthDate, i);
51
+ onMonthChange?.({ date: updatedMonth });
52
+ if (month === undefined)
53
+ setMonthDate(updatedMonth);
39
54
  setSelectionMode("date");
40
55
  };
41
56
  const handleDateHover = (date) => {
@@ -53,6 +68,14 @@ const CalendarControlled = (props) => {
53
68
  }
54
69
  prevSelectionModeRef.current = selectionMode;
55
70
  }, [selectionMode]);
71
+ /**
72
+ * Handle rendering in controlled mode
73
+ */
74
+ React.useEffect(() => {
75
+ if (!month)
76
+ return;
77
+ setMonthDate(month);
78
+ }, [month]);
56
79
  useCalendarKeyboardNavigation({
57
80
  monthDate,
58
81
  rootRef: selectionRootRef,
@@ -63,7 +86,19 @@ const CalendarControlled = (props) => {
63
86
  min,
64
87
  max,
65
88
  });
66
- return (_jsxs(View, { gap: 2, children: [_jsx(CalendarControls, { renderSelectedMonthLabel: renderSelectedMonthLabel, monthDate: monthDate, selectionMode: selectionMode, isFirstMonth: bounds.isFirstMonth, isLastMonth: bounds.isLastMonth, monthTitleRef: monthTitleRef, onMonthTitleClick: handleMonthTitleClick, onNextClick: handleNextClick, onPreviousClick: handlePreviousClick, previousMonthAriaLabel: previousMonthAriaLabel, previousYearAriaLabel: previousYearAriaLabel, nextMonthAriaLabel: nextMonthAriaLabel, nextYearAriaLabel: nextYearAriaLabel, monthSelectionAriaLabel: monthSelectionAriaLabel }), _jsxs(View.Item, { attributes: { ref: selectionRootRef }, children: [selectionMode === "date" && (_jsx(CalendarMonth, { date: monthDate, value: value, onChange: onChange, min: min, max: max, range: range, firstWeekDay: firstWeekDay, hoveredDate: hoveredDate, selectedDates: selectedDates, onDateHover: handleDateHover, onDateHoverEnd: handleDateHoverEnd, renderWeekDay: renderWeekDay, renderDateAriaLabel: renderDateAriaLabel, renderDateSlot: renderDateSlot })), selectionMode === "month" && (_jsx(CalendarYear, { monthDate: monthDate, onMonthClick: handleMonthClick, renderMonthLabel: renderMonthLabel, renderMonthAriaLabel: renderMonthAriaLabel, min: min, max: max }))] })] }));
89
+ return (_jsxs(View, { gap: 2, children: [_jsx(View, { direction: "row", gap: 4, children: Array.from({ length: selectionMode === "date" ? monthsToRender : 1 }).map((_, index) => {
90
+ const hidePrevious = bounds.isFirstMonth || (monthsToRender > 0 && index > 0);
91
+ const hideNext = bounds.isLastMonth ||
92
+ (selectionMode === "date" && monthsToRender > 0 && index < monthsToRender - 1);
93
+ const currentMonthDate = new Date(monthDate);
94
+ currentMonthDate.setMonth(currentMonthDate.getMonth() + index);
95
+ return (_jsx(View.Item, { grow: true, children: _jsx(CalendarControls, { renderSelectedMonthLabel: renderSelectedMonthLabel, monthDate: currentMonthDate, selectionMode: selectionMode, hidePrevious: hidePrevious, hideNext: hideNext, monthTitleRef: index === 0 ? monthTitleRef : undefined, onMonthTitleClick: handleMonthTitleClick, onNextClick: handleNextClick, onPreviousClick: handlePreviousClick, previousMonthAriaLabel: previousMonthAriaLabel, previousYearAriaLabel: previousYearAriaLabel, nextMonthAriaLabel: nextMonthAriaLabel, nextYearAriaLabel: nextYearAriaLabel, monthSelectionAriaLabel: monthSelectionAriaLabel, monthsToRender: monthsToRender }) }, index));
96
+ }) }), _jsxs(View, { direction: "row", gap: 4, attributes: { ref: selectionRootRef }, children: [selectionMode === "date" &&
97
+ Array.from({ length: monthsToRender }).map((_, index) => {
98
+ const currentMonthDate = new Date(monthDate);
99
+ currentMonthDate.setMonth(currentMonthDate.getMonth() + index);
100
+ return (_jsx(View.Item, { grow: true, children: _jsx(CalendarMonth, { date: currentMonthDate, value: value, onChange: onChange, min: min, max: max, range: range, firstWeekDay: firstWeekDay, hoveredDate: hoveredDate, selectedDates: selectedDates, disabledDates: disabledDates, onDateHover: handleDateHover, onDateHoverEnd: handleDateHoverEnd, renderWeekDay: renderWeekDay, renderDateAriaLabel: renderDateAriaLabel, renderDateSlot: renderDateSlot }) }, index));
101
+ }), selectionMode === "month" && (_jsx(CalendarYear, { monthDate: monthDate, onMonthClick: handleMonthClick, renderMonthLabel: renderMonthLabel, renderMonthAriaLabel: renderMonthAriaLabel, min: min, max: max }))] })] }));
67
102
  };
68
103
  CalendarControlled.displayName = "CalendarControlled";
69
104
  export default CalendarControlled;
@@ -10,35 +10,35 @@ import IconChevronRight from "../../icons/ChevronRight.js";
10
10
  import { onNextFrame } from "../../utilities/animation.js";
11
11
  import s from "./Calendar.module.css";
12
12
  const CalendarControls = (props) => {
13
- const { selectionMode, onMonthTitleClick, monthTitleRef, monthDate, renderSelectedMonthLabel, isFirstMonth, isLastMonth, onNextClick, onPreviousClick, monthSelectionAriaLabel = "Select a month", previousMonthAriaLabel = "Previous month", previousYearAriaLabel = "Previous year", nextMonthAriaLabel = "Next month", nextYearAriaLabel = "Next year", } = props;
13
+ const { selectionMode, onMonthTitleClick, monthTitleRef, monthDate, renderSelectedMonthLabel, hidePrevious, hideNext, onNextClick, onPreviousClick, monthSelectionAriaLabel = "Select a month", previousMonthAriaLabel = "Previous month", previousYearAriaLabel = "Previous year", nextMonthAriaLabel = "Next month", nextYearAriaLabel = "Next year", } = props;
14
14
  const prevRef = React.useRef(null);
15
15
  const nextRef = React.useRef(null);
16
16
  React.useEffect(() => {
17
- if (!isFirstMonth)
17
+ if (!hidePrevious)
18
18
  return;
19
19
  if (document.activeElement !== prevRef.current)
20
20
  return;
21
- const targetEl = nextRef.current || monthTitleRef.current;
21
+ const targetEl = nextRef.current || monthTitleRef?.current;
22
22
  onNextFrame(() => {
23
23
  targetEl?.focus();
24
24
  });
25
- }, [isFirstMonth, monthTitleRef]);
25
+ }, [hidePrevious, monthTitleRef]);
26
26
  React.useEffect(() => {
27
- if (!isLastMonth)
27
+ if (!hideNext)
28
28
  return;
29
29
  if (document.activeElement !== nextRef.current)
30
30
  return;
31
- const targetEl = prevRef.current || monthTitleRef.current;
31
+ const targetEl = prevRef.current || monthTitleRef?.current;
32
32
  onNextFrame(() => {
33
33
  targetEl?.focus();
34
34
  });
35
- }, [isLastMonth, monthTitleRef]);
36
- return (_jsxs(View, { direction: "row", gap: 2, align: "center", children: [_jsx(Hidden, { visibility: true, hide: isFirstMonth, children: _jsx("div", { className: s.control, children: _jsx(Button, { variant: "ghost", icon: IconChevronLeft, onClick: onPreviousClick, attributes: {
35
+ }, [hideNext, monthTitleRef]);
36
+ return (_jsxs(View, { direction: "row", gap: 2, align: "center", children: [_jsx(Hidden, { visibility: true, hide: hidePrevious, children: _jsx("div", { className: s.control, children: _jsx(Button, { variant: "ghost", icon: IconChevronLeft, onClick: onPreviousClick, attributes: {
37
37
  ref: prevRef,
38
38
  "aria-label": selectionMode === "date" ? previousMonthAriaLabel : previousYearAriaLabel,
39
39
  } }) }) }), _jsxs(View.Item, { grow: true, children: [selectionMode === "date" && (_jsxs(Button, { fullWidth: true, variant: "ghost", onClick: onMonthTitleClick, attributes: { ref: monthTitleRef }, children: [renderSelectedMonthLabel
40
40
  ? renderSelectedMonthLabel({ date: monthDate })
41
- : monthDate.toLocaleDateString("en-US", { month: "long", year: "numeric" }), _jsx(HiddenVisually, { children: monthSelectionAriaLabel })] })), selectionMode === "month" && (_jsx(Text, { align: "center", weight: "medium", children: monthDate.toLocaleDateString("en-US", { year: "numeric" }) }))] }), _jsx(Hidden, { visibility: true, hide: isLastMonth, children: _jsx("div", { className: s.control, children: _jsx(Button, { variant: "ghost", icon: IconChevronRight, onClick: onNextClick, attributes: {
41
+ : monthDate.toLocaleDateString("en-US", { month: "long", year: "numeric" }), _jsx(HiddenVisually, { children: monthSelectionAriaLabel })] })), selectionMode === "month" && (_jsx(Text, { align: "center", weight: "medium", children: monthDate.toLocaleDateString("en-US", { year: "numeric" }) }))] }), _jsx(Hidden, { visibility: true, hide: hideNext, children: _jsx("div", { className: s.control, children: _jsx(Button, { variant: "ghost", icon: IconChevronRight, onClick: onNextClick, attributes: {
42
42
  ref: nextRef,
43
43
  "aria-label": selectionMode === "date" ? nextMonthAriaLabel : nextYearAriaLabel,
44
44
  } }) }) })] }));
@@ -5,17 +5,19 @@ import { classNames } from "../../utilities/props.js";
5
5
  import s from "./Calendar.module.css";
6
6
  import { getLocalISODate } from "./Calendar.utils.js";
7
7
  const CalendarDate = (props) => {
8
- const { date, isoDate, startValue, endValue, disabled, focusable, onChange, range, hoveredDate, onDateHover, onDateHoverEnd, onDateFocus, selectedDates, renderAriaLabel, renderSlot, } = props;
8
+ const { date, isoDate, startValue, endValue, disabled: passedDisabled, focusable, onChange, range, hoveredDate, onDateHover, onDateHoverEnd, onDateFocus, selectedDates, disabledDates, renderAriaLabel, renderSlot, } = props;
9
9
  if (!date)
10
10
  return _jsx("td", { className: s.cell, "aria-hidden": "true" });
11
11
  const isoStartValue = startValue && getLocalISODate({ date: startValue });
12
12
  const isoEndValue = endValue && getLocalISODate({ date: endValue });
13
- const isStartValue = !!isoDate && !!isoStartValue && isoDate === isoStartValue;
14
- const isEndValue = !!isoDate && !!isoEndValue && isoDate === isoEndValue;
15
- const isAfterStartValue = startValue && date > startValue;
16
- const isBeforeEndValue = endValue && date < endValue;
17
- const isInHoveredRange = hoveredDate && !endValue && hoveredDate > date;
18
- const isInSelectedDates = !!selectedDates?.find((selectedDate) => getLocalISODate({ date: selectedDate }) === isoDate);
13
+ const isStartValue = Boolean(isoDate && !!isoStartValue && isoDate === isoStartValue);
14
+ const isEndValue = Boolean(isoDate && !!isoEndValue && isoDate === isoEndValue);
15
+ const isAfterStartValue = Boolean(isoDate && isoStartValue && isoDate > isoStartValue);
16
+ const isBeforeEndValue = Boolean(isoDate && isoEndValue && isoDate < isoEndValue);
17
+ const isInHoveredRange = Boolean(hoveredDate && !endValue && hoveredDate > date);
18
+ const isInSelectedDates = !!selectedDates?.some((selectedDate) => getLocalISODate({ date: selectedDate }) === isoDate);
19
+ const disabled = passedDisabled ||
20
+ disabledDates?.some((disabledDate) => getLocalISODate({ date: disabledDate }) === isoDate);
19
21
  let selection;
20
22
  switch (true) {
21
23
  case isAfterStartValue && isInHoveredRange:
@@ -5,7 +5,7 @@ import s from "./Calendar.module.css";
5
5
  import { getMonthWeeks, getWeekdayNames, getLocalISODate, isDateFocusable } from "./Calendar.utils.js";
6
6
  import CalendarDate from "./CalendarDate.js";
7
7
  const CalendarMonth = (props) => {
8
- const { date, value, onChange, min, max, range, firstWeekDay, selectedDates, hoveredDate, onDateHover, onDateHoverEnd, renderWeekDay, renderDateAriaLabel, renderDateSlot, } = props;
8
+ const { date, value, onChange, min, max, range, firstWeekDay, selectedDates, disabledDates, hoveredDate, onDateHover, onDateHoverEnd, renderWeekDay, renderDateAriaLabel, renderDateSlot, } = props;
9
9
  let foundFocusableDate = false;
10
10
  const [lastFocusedDate, setLastFocusedDate] = useState();
11
11
  const month = date.getMonth();
@@ -26,7 +26,7 @@ const CalendarMonth = (props) => {
26
26
  // eslint-disable-next-line react-hooks/immutability
27
27
  if (focusable)
28
28
  foundFocusableDate = true;
29
- return (_jsx(CalendarDate, { date: date, isoDate: isoDate, disabled: disabled, range: range, focusable: focusable, startValue: startValue, endValue: endValue, onChange: onChange, hoveredDate: hoveredDate, onDateHover: onDateHover, onDateHoverEnd: onDateHoverEnd, onDateFocus: setLastFocusedDate, renderAriaLabel: renderDateAriaLabel, selectedDates: selectedDates, renderSlot: renderDateSlot }, index));
29
+ return (_jsx(CalendarDate, { date: date, isoDate: isoDate, disabled: disabled, range: range, focusable: focusable, startValue: startValue, endValue: endValue, onChange: onChange, hoveredDate: hoveredDate, onDateHover: onDateHover, onDateHoverEnd: onDateHoverEnd, onDateFocus: setLastFocusedDate, renderAriaLabel: renderDateAriaLabel, selectedDates: selectedDates, disabledDates: disabledDates, renderSlot: renderDateSlot }, index));
30
30
  }) }, key));
31
31
  }) })] }));
32
32
  };
@@ -3,7 +3,7 @@ import type { MenuItemProps } from "../MenuItem";
3
3
  import type { PopoverProps, PopoverInstance } from "../Popover";
4
4
  import type React from "react";
5
5
  export type Instance = PopoverInstance;
6
- export type Props = Pick<PopoverProps, "children" | "position" | "forcePosition" | "fallbackPositions" | "fallbackAdjustLayout" | "fallbackMinWidth" | "fallbackMinHeight" | "triggerType" | "contentGap" | "contentShift" | "onOpen" | "onClose" | "active" | "defaultActive" | "width" | "disableHideAnimation" | "disableCloseOnOutsideClick" | "instanceRef" | "containerRef" | "positionRef" | "originCoordinates" | "borderRadius" | "elevation" | "initialFocusRef"> & {
6
+ export type Props = Pick<PopoverProps, "children" | "position" | "forcePosition" | "fallbackPositions" | "fallbackAdjustLayout" | "fallbackMinWidth" | "fallbackMinHeight" | "triggerType" | "contentGap" | "contentShift" | "contentMaxHeight" | "onOpen" | "onClose" | "active" | "defaultActive" | "width" | "disableHideAnimation" | "disableCloseOnOutsideClick" | "instanceRef" | "containerRef" | "positionRef" | "originCoordinates" | "borderRadius" | "elevation" | "initialFocusRef"> & {
7
7
  /** Change component trap focus keyboard behavior and shortcuts */
8
8
  trapFocusMode?: Extract<PopoverProps["trapFocusMode"], "action-menu" | "selection-menu"> | false;
9
9
  };
@@ -1 +1 @@
1
- .content{--rs-flyout-gap:0;--rs-flyout-origin-x:50%;--rs-flyout-origin-y:50%;isolation:isolate;pointer-events:none;position:absolute}.content.--hover{pointer-events:all}.content.--hover-disabled,.content.--hover-disabled .inner{pointer-events:none}.inner{backface-visibility:hidden;height:100%;max-height:100%;max-width:100%;opacity:0;outline:none;overflow:auto;pointer-events:all;transform:scale(.92) translateY(0);transform-origin:var(--rs-flyout-origin-x) var(--rs-flyout-origin-y);transition:1ms var(--rs-easing-accelerate)}[data-rs-keyboard] .inner:focus{box-shadow:var(--rs-shadow-focus)}.content.--width-trigger .inner{transform:scale(1) translateY(var(--rs-unit-x2))}.content.--position-top,.content.--position-top-end,.content.--position-top-start{--rs-flyout-origin-y:100%;padding-bottom:calc(var(--rs-unit-x1) * var(--rs-flyout-gap))}.content.--position-bottom,.content.--position-bottom-end,.content.--position-bottom-start{--rs-flyout-origin-y:0%;padding-top:calc(var(--rs-unit-x1) * var(--rs-flyout-gap))}.content.--position-bottom-start,.content.--position-top-start{--rs-flyout-origin-x:0%}.content.--position-bottom-end,.content.--position-top-end{--rs-flyout-origin-x:100%}.content.--position-start,.content.--position-start-bottom,.content.--position-start-top{--rs-flyout-origin-x:100%;padding-right:calc(var(--rs-unit-x1) * var(--rs-flyout-gap))}.content.--position-end,.content.--position-end-bottom,.content.--position-end-top{--rs-flyout-origin-x:0%;padding-left:calc(var(--rs-unit-x1) * var(--rs-flyout-gap))}.content.--position-end-top,.content.--position-start-top{--rs-flyout-origin-y:0%}.content.--position-end-bottom,.content.--position-start-bottom{--rs-flyout-origin-y:100%}.content.--visible .inner{opacity:1;transform:scale(1) translateY(0)}.content.--animated .inner{transition-duration:var(--rs-duration-rapid);transition-property:opacity,transform}.content.--animated.--visible .inner{transition-duration:var(--rs-duration-fast);transition-timing-function:var(--rs-easing-decelerate)}
1
+ .content{--rs-flyout-gap:0;--rs-flyout-max-h:100%;--rs-flyout-origin-x:50%;--rs-flyout-origin-y:50%;isolation:isolate;pointer-events:none;position:absolute}.content.--hover{pointer-events:all}.content.--hover-disabled,.content.--hover-disabled .inner{pointer-events:none}.inner{backface-visibility:hidden;height:100%;max-height:var(--rs-flyout-max-h);max-width:100%;opacity:0;outline:none;overflow:auto;pointer-events:all;transform:scale(.92) translateY(0);transform-origin:var(--rs-flyout-origin-x) var(--rs-flyout-origin-y);transition:1ms var(--rs-easing-accelerate)}[data-rs-keyboard] .inner:focus{box-shadow:var(--rs-shadow-focus)}.content.--width-trigger .inner{transform:scale(1) translateY(var(--rs-unit-x2))}.content.--position-top,.content.--position-top-end,.content.--position-top-start{--rs-flyout-origin-y:100%;padding-bottom:calc(var(--rs-unit-x1) * var(--rs-flyout-gap))}.content.--position-bottom,.content.--position-bottom-end,.content.--position-bottom-start{--rs-flyout-origin-y:0%;padding-top:calc(var(--rs-unit-x1) * var(--rs-flyout-gap))}.content.--position-bottom-start,.content.--position-top-start{--rs-flyout-origin-x:0%}.content.--position-bottom-end,.content.--position-top-end{--rs-flyout-origin-x:100%}.content.--position-start,.content.--position-start-bottom,.content.--position-start-top{--rs-flyout-origin-x:100%;padding-right:calc(var(--rs-unit-x1) * var(--rs-flyout-gap))}.content.--position-end,.content.--position-end-bottom,.content.--position-end-top{--rs-flyout-origin-x:0%;padding-left:calc(var(--rs-unit-x1) * var(--rs-flyout-gap))}.content.--position-end-top,.content.--position-start-top{--rs-flyout-origin-y:0%}.content.--position-end-bottom,.content.--position-start-bottom{--rs-flyout-origin-y:100%}.content.--visible .inner{opacity:1;transform:scale(1) translateY(0)}.content.--animated .inner{transition-duration:var(--rs-duration-rapid);transition-property:opacity,transform}.content.--animated.--visible .inner{transition-duration:var(--rs-duration-fast);transition-timing-function:var(--rs-easing-decelerate)}
@@ -132,6 +132,8 @@ type BaseProps = {
132
132
  contentGap?: number;
133
133
  /** Shift the content on the secondary axis, relative to its original position */
134
134
  contentShift?: number;
135
+ /** Maximum height for the content */
136
+ contentMaxHeight?: string;
135
137
  /** Additional classname for the content element */
136
138
  contentClassName?: string;
137
139
  /** Additional attributes for the content element */
@@ -189,7 +191,7 @@ export type ContextProps = {
189
191
  handleContentMouseDown: () => void;
190
192
  handleContentMouseUp: () => void;
191
193
  isSubmenu: boolean;
192
- } & Pick<Props, "triggerType" | "contentClassName" | "contentAttributes" | "contentGap" | "trapFocusMode" | "containerRef" | "disableContentHover" | "autoFocus">;
194
+ } & Pick<Props, "triggerType" | "contentClassName" | "contentAttributes" | "contentGap" | "contentMaxHeight" | "trapFocusMode" | "containerRef" | "disableContentHover" | "autoFocus">;
193
195
  export type TriggerContextProps = {
194
196
  elRef?: ContextProps["triggerElRef"];
195
197
  };
@@ -11,7 +11,7 @@ import s from "./Flyout.module.css";
11
11
  import cooldown from "./utilities/cooldown.js";
12
12
  const FlyoutContent = (props) => {
13
13
  const { children, className, attributes } = props;
14
- const { flyout, id, flyoutElRef, triggerElRef, handleClose, handleTransitionEnd, handleTransitionStart, triggerType, handleMouseEnter, handleMouseLeave, handleContentMouseDown, handleContentMouseUp, contentClassName, contentAttributes, contentGap, trapFocusMode, disableContentHover, autoFocus, width, containerRef: passedContainerRef, isSubmenu, } = useFlyoutContext();
14
+ const { flyout, id, flyoutElRef, triggerElRef, handleClose, handleTransitionEnd, handleTransitionStart, triggerType, handleMouseEnter, handleMouseLeave, handleContentMouseDown, handleContentMouseUp, contentClassName, contentAttributes, contentGap, contentMaxHeight, trapFocusMode, disableContentHover, autoFocus, width, containerRef: passedContainerRef, isSubmenu, } = useFlyoutContext();
15
15
  const { styles, status, position } = flyout;
16
16
  const [mounted, setMounted] = React.useState(false);
17
17
  const closestFixedContainer = React.useMemo(() => {
@@ -97,6 +97,7 @@ const FlyoutContent = (props) => {
97
97
  const content = (_jsx(ContentProvider, { value: { elRef: flyoutElRef }, children: _jsx("div", { className: rootClassNames, style: {
98
98
  ...styles,
99
99
  "--rs-flyout-gap": contentGap,
100
+ "--rs-flyout-max-h": contentMaxHeight,
100
101
  }, ref: flyoutElRef, onTransitionEnd: handleTransitionEnd, onMouseEnter: triggerType === "hover" ? handleMouseEnter : undefined, onMouseLeave: triggerType === "hover" ? handleMouseLeave : undefined, onMouseDown: handleContentMouseDown, onTouchStart: handleContentMouseDown, onMouseUp: handleContentMouseUp, onTouchEnd: handleContentMouseUp, children: _jsx("div", { role: role, ...attributes, id: id, tabIndex: !autoFocus ? -1 : undefined, "aria-modal": role === "dialog" ? true : undefined, style: { ...attributes?.style, ...contentAttributes?.style }, className: innerClassNames, children: children }) }) }));
101
102
  return _jsx(Portal, { targetRef: containerRef, children: content });
102
103
  };
@@ -15,7 +15,7 @@ import { Provider, useFlyoutTriggerContext, useFlyoutContext, useFlyoutContentCo
15
15
  import useFlyout from "./useFlyout.js";
16
16
  import cooldown from "./utilities/cooldown.js";
17
17
  const FlyoutControlled = (props) => {
18
- const { triggerType = "click", groupTimeouts, onOpen, onClose, children, disabled, forcePosition, fallbackAdjustLayout, fallbackMinWidth, fallbackMinHeight, trapFocusMode = "dialog", width, disableHideAnimation, disableContentHover, disableCloseOnOutsideClick, autoFocus = true, originCoordinates, contentGap = 2, contentShift, contentClassName, contentAttributes, position: passedPosition, active: passedActive, id: passedId, instanceRef, containerRef, initialFocusRef, positionRef, } = props;
18
+ const { triggerType = "click", groupTimeouts, onOpen, onClose, children, disabled, forcePosition, fallbackAdjustLayout, fallbackMinWidth, fallbackMinHeight, trapFocusMode = "dialog", width, disableHideAnimation, disableContentHover, disableCloseOnOutsideClick, autoFocus = true, originCoordinates, contentGap = 2, contentShift, contentMaxHeight, contentClassName, contentAttributes, position: passedPosition, active: passedActive, id: passedId, instanceRef, containerRef, initialFocusRef, positionRef, } = props;
19
19
  const fallbackPositions = props.fallbackPositions === false || forcePosition ? [] : props.fallbackPositions;
20
20
  const onOpenRef = useHandlerRef(onOpen);
21
21
  const onCloseRef = useHandlerRef(onClose);
@@ -305,7 +305,7 @@ const FlyoutControlled = (props) => {
305
305
  shouldReturnFocusRef.current = false;
306
306
  handleClose({ reason: "outside-click" });
307
307
  }, {
308
- disabled: Boolean(status !== "visible" || disableCloseOnOutsideClick),
308
+ disabled: !isRendered || disableCloseOnOutsideClick,
309
309
  });
310
310
  return (_jsx(Provider, { value: {
311
311
  id,
@@ -331,6 +331,7 @@ const FlyoutControlled = (props) => {
331
331
  contentClassName,
332
332
  contentAttributes,
333
333
  contentGap,
334
+ contentMaxHeight,
334
335
  containerRef,
335
336
  disableContentHover,
336
337
  autoFocus,
@@ -1 +1 @@
1
- .content{max-width:360px}.content--variant-elevated{background:var(--rs-color-background-elevation-overlay);border-radius:var(--rs-radius-medium);box-shadow:0 0 0 1px var(--rs-color-border-neutral-faded) inset,var(--rs-shadow-overlay);color:var(--rs-color-foreground-neutral);min-width:220px}.content--variant-elevated.content--elevation-raised{box-shadow:0 0 0 1px var(--rs-color-border-neutral-faded) inset,var(--rs-shadow-raised)}.content--radius-small{border-radius:var(--rs-radius-small)}.content.content--has-width{max-width:none;min-width:0}@media (--rs-viewport-s ){.content{max-width:none}}
1
+ .content{--rs-border-w:1px;border:var(--rs-border-w) solid var(--rs-color-border-neutral-faded);max-width:360px}.content--variant-elevated{background:var(--rs-color-background-elevation-overlay);border-radius:var(--rs-radius-medium);box-shadow:var(--rs-shadow-overlay);color:var(--rs-color-foreground-neutral);min-width:220px}.content--variant-elevated.content--elevation-raised{box-shadow:var(--rs-shadow-raised)}.content--radius-small{border-radius:var(--rs-radius-small)}.content.content--has-width{max-width:none;min-width:0}@media (--rs-viewport-s ){.content{max-width:none}}
@@ -1,7 +1,7 @@
1
1
  import type { FlyoutProps, FlyoutInstance } from "../Flyout";
2
2
  import type React from "react";
3
3
  export type Instance = FlyoutInstance;
4
- export type Props = Pick<FlyoutProps, "id" | "position" | "forcePosition" | "fallbackPositions" | "fallbackAdjustLayout" | "fallbackMinWidth" | "fallbackMinHeight" | "onOpen" | "onClose" | "width" | "trapFocusMode" | "active" | "defaultActive" | "contentGap" | "contentShift" | "instanceRef" | "triggerType" | "disableHideAnimation" | "disableContentHover" | "disableCloseOnOutsideClick" | "autoFocus" | "containerRef" | "positionRef" | "initialFocusRef" | "originCoordinates"> & {
4
+ export type Props = Pick<FlyoutProps, "id" | "position" | "forcePosition" | "fallbackPositions" | "fallbackAdjustLayout" | "fallbackMinWidth" | "fallbackMinHeight" | "onOpen" | "onClose" | "width" | "trapFocusMode" | "active" | "defaultActive" | "contentGap" | "contentShift" | "contentMaxHeight" | "instanceRef" | "triggerType" | "disableHideAnimation" | "disableContentHover" | "disableCloseOnOutsideClick" | "autoFocus" | "containerRef" | "positionRef" | "initialFocusRef" | "originCoordinates"> & {
5
5
  /** Node for inserting children */
6
6
  children?: React.ReactNode;
7
7
  /** Content element padding, unit token multiplier */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reshaped",
3
3
  "description": "Professionally crafted design system in React & Figma for building products of any scale and complexity",
4
- "version": "3.9.0-canary.2",
4
+ "version": "3.9.0-canary.4",
5
5
  "license": "MIT",
6
6
  "email": "hello@reshaped.so",
7
7
  "homepage": "https://reshaped.so",