@kaizen/components 0.0.0-canary-fix-filter-select-jump-issue-useHasStableYPosition-20250120225815 → 0.0.0-canary-test-1-20250201010717

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 (32) hide show
  1. package/dist/cjs/Calendar/CalendarRange/CalendarRange.cjs +7 -4
  2. package/dist/cjs/Calendar/CalendarRange/CalendarRange.module.scss.cjs +1 -0
  3. package/dist/cjs/Filter/FilterDateRangePicker/FilterDateRangePicker.cjs +6 -1
  4. package/dist/cjs/Filter/FilterDateRangePicker/FilterDateRangePicker.module.css.cjs +6 -0
  5. package/dist/cjs/Filter/FilterDateRangePicker/subcomponents/FilterDateRangePickerField/FilterDateRangePickerField.cjs +4 -1
  6. package/dist/cjs/__rc__/Select/subcomponents/ListBox/ListBox.cjs +6 -5
  7. package/dist/cjs/__utilities__/useIsClientReady/useIsClientReady.cjs +20 -0
  8. package/dist/esm/Calendar/CalendarRange/CalendarRange.mjs +7 -4
  9. package/dist/esm/Calendar/CalendarRange/CalendarRange.module.scss.mjs +1 -0
  10. package/dist/esm/Filter/FilterDateRangePicker/FilterDateRangePicker.mjs +5 -1
  11. package/dist/esm/Filter/FilterDateRangePicker/FilterDateRangePicker.module.css.mjs +4 -0
  12. package/dist/esm/Filter/FilterDateRangePicker/subcomponents/FilterDateRangePickerField/FilterDateRangePickerField.mjs +4 -1
  13. package/dist/esm/__rc__/Select/subcomponents/ListBox/ListBox.mjs +6 -5
  14. package/dist/esm/__utilities__/useIsClientReady/useIsClientReady.mjs +18 -0
  15. package/dist/styles.css +15 -2
  16. package/dist/types/Calendar/CalendarRange/CalendarRange.d.ts +1 -1
  17. package/package.json +8 -1
  18. package/src/Calendar/CalendarRange/CalendarRange.module.scss +5 -0
  19. package/src/Calendar/CalendarRange/CalendarRange.tsx +3 -1
  20. package/src/Content/Content.module.scss +3 -2
  21. package/src/Filter/FilterDateRangePicker/FilterDateRangePicker.module.css +7 -0
  22. package/src/Filter/FilterDateRangePicker/FilterDateRangePicker.tsx +3 -1
  23. package/src/Filter/FilterDateRangePicker/_docs/FilterDateRangePicker.mdx +1 -1
  24. package/src/Filter/FilterDateRangePicker/_docs/FilterDateRangePicker.stories.tsx +36 -1
  25. package/src/Filter/FilterDateRangePicker/subcomponents/FilterDateRangePickerField/FilterDateRangePickerField.tsx +5 -1
  26. package/src/Filter/FilterMultiSelect/_docs/FilterMultiSelect.mdx +1 -1
  27. package/src/Filter/FilterSelect/_docs/FilterSelect.stories.tsx +0 -45
  28. package/src/__rc__/Select/subcomponents/ListBox/ListBox.tsx +6 -5
  29. package/dist/cjs/__rc__/Select/hooks/useHasStableYPosition.cjs +0 -36
  30. package/dist/esm/__rc__/Select/hooks/useHasStableYPosition.mjs +0 -34
  31. package/dist/types/__rc__/Select/hooks/useHasStableYPosition.d.ts +0 -5
  32. package/src/__rc__/Select/hooks/useHasStableYPosition.ts +0 -31
@@ -24,9 +24,11 @@ var CalendarRange = function (_a) {
24
24
  classNameOverride = _a.classNameOverride,
25
25
  selected = _a.selected,
26
26
  defaultMonth = _a.defaultMonth,
27
- _d = _a.locale,
28
- locale$1 = _d === undefined ? locale.enAU : _d,
29
- restProps = tslib.__rest(_a, ["id", "onMount", "hasDivider", "classNameOverride", "selected", "defaultMonth", "locale"]);
27
+ _d = _a.numberOfMonths,
28
+ numberOfMonths = _d === undefined ? 2 : _d,
29
+ _e = _a.locale,
30
+ locale$1 = _e === undefined ? locale.enAU : _e,
31
+ restProps = tslib.__rest(_a, ["id", "onMount", "hasDivider", "classNameOverride", "selected", "defaultMonth", "numberOfMonths", "locale"]);
30
32
  var calendarRef = React.useRef(null);
31
33
  React.useEffect(function () {
32
34
  if (calendarRef.current) onMount === null || onMount === undefined ? undefined : onMount(calendarRef.current);
@@ -37,6 +39,7 @@ var CalendarRange = function (_a) {
37
39
  var classNames = tslib.__assign(tslib.__assign({}, baseCalendarClassNames.baseCalendarClassNames), {
38
40
  month: hasDivider ? CalendarRange_module.monthWithDivider : CalendarRange_module.month,
39
41
  caption_end: CalendarRange_module.captionEnd,
42
+ caption_start: CalendarRange_module.captionStart,
40
43
  nav: CalendarRange_module.nav,
41
44
  day_range_start: CalendarRange_module.dayRangeStart,
42
45
  day_range_end: CalendarRange_module.dayRangeEnd,
@@ -68,7 +71,7 @@ var CalendarRange = function (_a) {
68
71
  });
69
72
  }
70
73
  },
71
- numberOfMonths: 2,
74
+ numberOfMonths: numberOfMonths,
72
75
  locale: locale$1
73
76
  }, restProps)));
74
77
  };
@@ -5,6 +5,7 @@ var styles = {
5
5
  "monthWithDivider": "CalendarRange-module_monthWithDivider__JY-56",
6
6
  "nav": "CalendarRange-module_nav__OtaVb",
7
7
  "captionEnd": "CalendarRange-module_captionEnd__GSLQO",
8
+ "captionStart": "CalendarRange-module_captionStart__K5hlF",
8
9
  "dayRangeStart": "CalendarRange-module_dayRangeStart__TwdDT",
9
10
  "dayRangeEnd": "CalendarRange-module_dayRangeEnd__y6dEB",
10
11
  "dayRangeMiddle": "CalendarRange-module_dayRangeMiddle__SybKY"
@@ -2,6 +2,7 @@
2
2
 
3
3
  var tslib = require('tslib');
4
4
  var React = require('react');
5
+ var classnames = require('classnames');
5
6
  var getLocale = require('../../DatePicker/utils/getLocale.cjs');
6
7
  var Filter = require('../Filter/Filter.cjs');
7
8
  var FilterContents = require('../Filter/subcomponents/FilterContents/FilterContents.cjs');
@@ -9,12 +10,14 @@ var DateRangeDisplayLabel = require('./subcomponents/DateRangeDisplayLabel/DateR
9
10
  var FilterDateRangePickerField = require('./subcomponents/FilterDateRangePickerField/FilterDateRangePickerField.cjs');
10
11
  var isValidRange = require('./subcomponents/FilterDateRangePickerField/utils/isValidRange.cjs');
11
12
  var isCompleteDateRange = require('./utils/isCompleteDateRange.cjs');
13
+ var FilterDateRangePicker_module = require('./FilterDateRangePicker.module.css.cjs');
12
14
  function _interopDefault(e) {
13
15
  return e && e.__esModule ? e : {
14
16
  default: e
15
17
  };
16
18
  }
17
19
  var React__default = /*#__PURE__*/_interopDefault(React);
20
+ var classnames__default = /*#__PURE__*/_interopDefault(classnames);
18
21
  var FilterDateRangePicker = function (_a) {
19
22
  var propsId = _a.id,
20
23
  isOpen = _a.isOpen,
@@ -38,7 +41,9 @@ var FilterDateRangePicker = function (_a) {
38
41
  label: label
39
42
  }, triggerProps));
40
43
  }
41
- }, React__default.default.createElement(FilterContents.FilterContents, null, React__default.default.createElement(FilterDateRangePickerField.FilterDateRangePickerField, tslib.__assign({
44
+ }, React__default.default.createElement(FilterContents.FilterContents, {
45
+ classNameOverride: classnames__default.default(FilterDateRangePicker_module.filterDateRangePickerContents)
46
+ }, React__default.default.createElement(FilterDateRangePickerField.FilterDateRangePickerField, tslib.__assign({
42
47
  id: "".concat(id, "--input"),
43
48
  label: label,
44
49
  locale: locale,
@@ -0,0 +1,6 @@
1
+ 'use strict';
2
+
3
+ var styles = {
4
+ "filterDateRangePickerContents": "FilterDateRangePicker-module_filterDateRangePickerContents__P7eNa"
5
+ };
6
+ module.exports = styles;
@@ -13,6 +13,7 @@ require('react-day-picker');
13
13
  var parseDateFromTextFormatValue = require('../../../../Calendar/utils/parseDateFromTextFormatValue.cjs');
14
14
  var getLocale = require('../../../../DatePicker/utils/getLocale.cjs');
15
15
  var useDateInputHandlers = require('../../../FilterDatePicker/hooks/useDateInputHandlers.cjs');
16
+ var useMediaQueries = require('../../../../utils/useMediaQueries.cjs');
16
17
  var DateRangeInputField = require('../DateRangeInputField/DateRangeInputField.cjs');
17
18
  var filterDateRangePickerFieldReducer = require('./filterDateRangePickerFieldReducer.cjs');
18
19
  var useEndDateValidation = require('./hooks/useEndDateValidation.cjs');
@@ -44,6 +45,7 @@ var FilterDateRangePickerField = function (_a) {
44
45
  restProps = tslib.__rest(_a, ["id", "label", "locale", "defaultMonth", "selectedRange", "onRangeChange", "disabledDays", "inputStartDateProps", "inputEndDateProps", "description", "validationMessage", "onValidate", "classNameOverride"]);
45
46
  var formatMessage = i18nReactIntl.useIntl().formatMessage;
46
47
  var locale = getLocale.getLocale(propsLocale);
48
+ var queries = useMediaQueries.useMediaQueries().queries;
47
49
  var translatedDateFrom = formatMessage({
48
50
  id: 'filterDateRangePicker.dateFrom',
49
51
  defaultMessage: 'Date from',
@@ -222,7 +224,7 @@ var FilterDateRangePickerField = function (_a) {
222
224
  dateStart: dateStartValidation.validationMessage,
223
225
  dateEnd: dateEndValidation.validationMessage
224
226
  },
225
- classNameOverride: FilterDateRangePickerField_module.dateRangeInputField
227
+ classNameOverride: classnames__default.default(FilterDateRangePickerField_module.dateRangeInputField)
226
228
  }), React__default.default.createElement(CalendarRange.CalendarRange, {
227
229
  disabled: disabledDays,
228
230
  locale: locale,
@@ -232,6 +234,7 @@ var FilterDateRangePickerField = function (_a) {
232
234
  },
233
235
  onSelect: handleCalendarSelectRange,
234
236
  month: state.startMonth,
237
+ numberOfMonths: queries.isSmall ? 1 : 2,
235
238
  onMonthChange: function (value) {
236
239
  return dispatch({
237
240
  type: 'navigate_months',
@@ -4,8 +4,8 @@ var tslib = require('tslib');
4
4
  var React = require('react');
5
5
  var listbox = require('@react-aria/listbox');
6
6
  var classnames = require('classnames');
7
+ var useIsClientReady = require('../../../../__utilities__/useIsClientReady/useIsClientReady.cjs');
7
8
  var SelectContext = require('../../context/SelectContext.cjs');
8
- var useHasStableYPosition = require('../../hooks/useHasStableYPosition.cjs');
9
9
  var ListBox_module = require('./ListBox.module.scss.cjs');
10
10
  function _interopDefault(e) {
11
11
  return e && e.__esModule ? e : {
@@ -43,31 +43,32 @@ var ListBox = function (_a) {
43
43
  menuProps = _a.menuProps,
44
44
  classNameOverride = _a.classNameOverride,
45
45
  restProps = tslib.__rest(_a, ["children", "menuProps", "classNameOverride"]);
46
+ var isClientReady = useIsClientReady.useIsClientReady();
46
47
  var state = SelectContext.useSelectContext().state;
47
48
  var ref = React.useRef(null);
48
- var hasStableYPosition = useHasStableYPosition.useHasStableYPosition(ref);
49
49
  var listBoxProps = listbox.useListBox(tslib.__assign(tslib.__assign({}, menuProps), {
50
50
  disallowEmptySelection: true,
51
51
  // This is to ensure that the listbox doesn't use React Aria's auto focus feature for Listbox, which creates a visual bug
52
52
  autoFocus: false
53
53
  }), state, ref).listBoxProps;
54
54
  /**
55
- * This uses the hasStableYPosition to determine if the position is stable within the window
55
+ * This uses the new useIsClientReady to ensure document exists before trying to querySelector and give the time to focus to the correct element
56
56
  */
57
57
  React.useEffect(function () {
58
58
  var _a;
59
- if (hasStableYPosition) {
59
+ if (isClientReady) {
60
60
  var optionKey = getOptionKeyFromCollection(state);
61
61
  var focusToElement = safeQuerySelector("[data-key='".concat(optionKey, "']"));
62
62
  if (focusToElement) {
63
63
  focusToElement.focus();
64
64
  } else {
65
+ // If an element is not found, focus on the listbox. This ensures the list can still be navigated to via keyboard if the keys do not align to the data attributes of the list items.
65
66
  (_a = ref.current) === null || _a === undefined ? undefined : _a.focus();
66
67
  }
67
68
  }
68
69
  // Only run this effect for checking the first successful render
69
70
  // eslint-disable-next-line react-hooks/exhaustive-deps
70
- }, [hasStableYPosition]);
71
+ }, [isClientReady]);
71
72
  return React__default.default.createElement("ul", tslib.__assign({
72
73
  ref: ref,
73
74
  className: classnames__default.default(ListBox_module.listBox, classNameOverride)
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+
5
+ /**
6
+ * A hook that returns a truthy value indicating if the code can be run on client side.
7
+ * This is a useful hook for determining if the `document` or `window` objects are available.
8
+ */
9
+ var useIsClientReady = function () {
10
+ var _a = React.useState(false),
11
+ isClientReady = _a[0],
12
+ setIsClientReady = _a[1];
13
+ React.useEffect(function () {
14
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
15
+ setIsClientReady(true);
16
+ }
17
+ }, []);
18
+ return isClientReady;
19
+ };
20
+ exports.useIsClientReady = useIsClientReady;
@@ -17,9 +17,11 @@ const CalendarRange = /*#__PURE__*/function () {
17
17
  classNameOverride = _a.classNameOverride,
18
18
  selected = _a.selected,
19
19
  defaultMonth = _a.defaultMonth,
20
- _d = _a.locale,
21
- locale = _d === undefined ? enAU : _d,
22
- restProps = __rest(_a, ["id", "onMount", "hasDivider", "classNameOverride", "selected", "defaultMonth", "locale"]);
20
+ _d = _a.numberOfMonths,
21
+ numberOfMonths = _d === undefined ? 2 : _d,
22
+ _e = _a.locale,
23
+ locale = _e === undefined ? enAU : _e,
24
+ restProps = __rest(_a, ["id", "onMount", "hasDivider", "classNameOverride", "selected", "defaultMonth", "numberOfMonths", "locale"]);
23
25
  var calendarRef = useRef(null);
24
26
  useEffect(function () {
25
27
  if (calendarRef.current) onMount === null || onMount === undefined ? undefined : onMount(calendarRef.current);
@@ -30,6 +32,7 @@ const CalendarRange = /*#__PURE__*/function () {
30
32
  var classNames = __assign(__assign({}, baseCalendarClassNames), {
31
33
  month: hasDivider ? styles.monthWithDivider : styles.month,
32
34
  caption_end: styles.captionEnd,
35
+ caption_start: styles.captionStart,
33
36
  nav: styles.nav,
34
37
  day_range_start: styles.dayRangeStart,
35
38
  day_range_end: styles.dayRangeEnd,
@@ -61,7 +64,7 @@ const CalendarRange = /*#__PURE__*/function () {
61
64
  });
62
65
  }
63
66
  },
64
- numberOfMonths: 2,
67
+ numberOfMonths: numberOfMonths,
65
68
  locale: locale
66
69
  }, restProps)));
67
70
  };
@@ -3,6 +3,7 @@ var styles = {
3
3
  "monthWithDivider": "CalendarRange-module_monthWithDivider__JY-56",
4
4
  "nav": "CalendarRange-module_nav__OtaVb",
5
5
  "captionEnd": "CalendarRange-module_captionEnd__GSLQO",
6
+ "captionStart": "CalendarRange-module_captionStart__K5hlF",
6
7
  "dayRangeStart": "CalendarRange-module_dayRangeStart__TwdDT",
7
8
  "dayRangeEnd": "CalendarRange-module_dayRangeEnd__y6dEB",
8
9
  "dayRangeMiddle": "CalendarRange-module_dayRangeMiddle__SybKY"
@@ -1,5 +1,6 @@
1
1
  import { __rest, __assign } from 'tslib';
2
2
  import React, { useId } from 'react';
3
+ import classnames from 'classnames';
3
4
  import { getLocale } from '../../DatePicker/utils/getLocale.mjs';
4
5
  import { Filter } from '../Filter/Filter.mjs';
5
6
  import { FilterContents } from '../Filter/subcomponents/FilterContents/FilterContents.mjs';
@@ -7,6 +8,7 @@ import { DateRangeDisplayLabel } from './subcomponents/DateRangeDisplayLabel/Dat
7
8
  import { FilterDateRangePickerField } from './subcomponents/FilterDateRangePickerField/FilterDateRangePickerField.mjs';
8
9
  import { isValidRange } from './subcomponents/FilterDateRangePickerField/utils/isValidRange.mjs';
9
10
  import { isCompleteDateRange } from './utils/isCompleteDateRange.mjs';
11
+ import styles from './FilterDateRangePicker.module.css.mjs';
10
12
  const FilterDateRangePicker = /*#__PURE__*/function () {
11
13
  const FilterDateRangePicker = function (_a) {
12
14
  var propsId = _a.id,
@@ -31,7 +33,9 @@ const FilterDateRangePicker = /*#__PURE__*/function () {
31
33
  label: label
32
34
  }, triggerProps));
33
35
  }
34
- }, /*#__PURE__*/React.createElement(FilterContents, null, /*#__PURE__*/React.createElement(FilterDateRangePickerField, __assign({
36
+ }, /*#__PURE__*/React.createElement(FilterContents, {
37
+ classNameOverride: classnames(styles.filterDateRangePickerContents)
38
+ }, /*#__PURE__*/React.createElement(FilterDateRangePickerField, __assign({
35
39
  id: "".concat(id, "--input"),
36
40
  label: label,
37
41
  locale: locale,
@@ -0,0 +1,4 @@
1
+ var styles = {
2
+ "filterDateRangePickerContents": "FilterDateRangePicker-module_filterDateRangePickerContents__P7eNa"
3
+ };
4
+ export { styles as default };
@@ -11,6 +11,7 @@ import 'react-day-picker';
11
11
  import { parseDateFromTextFormatValue } from '../../../../Calendar/utils/parseDateFromTextFormatValue.mjs';
12
12
  import { getLocale } from '../../../../DatePicker/utils/getLocale.mjs';
13
13
  import { useDateInputHandlers } from '../../../FilterDatePicker/hooks/useDateInputHandlers.mjs';
14
+ import { useMediaQueries } from '../../../../utils/useMediaQueries.mjs';
14
15
  import { DateRangeInputField } from '../DateRangeInputField/DateRangeInputField.mjs';
15
16
  import { filterDatePickerFieldReducer } from './filterDateRangePickerFieldReducer.mjs';
16
17
  import { useEndDateValidation } from './hooks/useEndDateValidation.mjs';
@@ -36,6 +37,7 @@ const FilterDateRangePickerField = /*#__PURE__*/function () {
36
37
  restProps = __rest(_a, ["id", "label", "locale", "defaultMonth", "selectedRange", "onRangeChange", "disabledDays", "inputStartDateProps", "inputEndDateProps", "description", "validationMessage", "onValidate", "classNameOverride"]);
37
38
  var formatMessage = useIntl().formatMessage;
38
39
  var locale = getLocale(propsLocale);
40
+ var queries = useMediaQueries().queries;
39
41
  var translatedDateFrom = formatMessage({
40
42
  id: 'filterDateRangePicker.dateFrom',
41
43
  defaultMessage: 'Date from',
@@ -214,7 +216,7 @@ const FilterDateRangePickerField = /*#__PURE__*/function () {
214
216
  dateStart: dateStartValidation.validationMessage,
215
217
  dateEnd: dateEndValidation.validationMessage
216
218
  },
217
- classNameOverride: styles.dateRangeInputField
219
+ classNameOverride: classnames(styles.dateRangeInputField)
218
220
  }), /*#__PURE__*/React.createElement(CalendarRange, {
219
221
  disabled: disabledDays,
220
222
  locale: locale,
@@ -224,6 +226,7 @@ const FilterDateRangePickerField = /*#__PURE__*/function () {
224
226
  },
225
227
  onSelect: handleCalendarSelectRange,
226
228
  month: state.startMonth,
229
+ numberOfMonths: queries.isSmall ? 1 : 2,
227
230
  onMonthChange: function (value) {
228
231
  return dispatch({
229
232
  type: 'navigate_months',
@@ -2,8 +2,8 @@ import { __rest, __assign } from 'tslib';
2
2
  import React, { useRef, useEffect } from 'react';
3
3
  import { useListBox } from '@react-aria/listbox';
4
4
  import classnames from 'classnames';
5
+ import { useIsClientReady } from '../../../../__utilities__/useIsClientReady/useIsClientReady.mjs';
5
6
  import { useSelectContext } from '../../context/SelectContext.mjs';
6
- import { useHasStableYPosition } from '../../hooks/useHasStableYPosition.mjs';
7
7
  import styles from './ListBox.module.scss.mjs';
8
8
 
9
9
  /** A util to retrieve the key of the correct focusable items based of the focus strategy
@@ -35,31 +35,32 @@ const ListBox = /*#__PURE__*/function () {
35
35
  menuProps = _a.menuProps,
36
36
  classNameOverride = _a.classNameOverride,
37
37
  restProps = __rest(_a, ["children", "menuProps", "classNameOverride"]);
38
+ var isClientReady = useIsClientReady();
38
39
  var state = useSelectContext().state;
39
40
  var ref = useRef(null);
40
- var hasStableYPosition = useHasStableYPosition(ref);
41
41
  var listBoxProps = useListBox(__assign(__assign({}, menuProps), {
42
42
  disallowEmptySelection: true,
43
43
  // This is to ensure that the listbox doesn't use React Aria's auto focus feature for Listbox, which creates a visual bug
44
44
  autoFocus: false
45
45
  }), state, ref).listBoxProps;
46
46
  /**
47
- * This uses the hasStableYPosition to determine if the position is stable within the window
47
+ * This uses the new useIsClientReady to ensure document exists before trying to querySelector and give the time to focus to the correct element
48
48
  */
49
49
  useEffect(function () {
50
50
  var _a;
51
- if (hasStableYPosition) {
51
+ if (isClientReady) {
52
52
  var optionKey = getOptionKeyFromCollection(state);
53
53
  var focusToElement = safeQuerySelector("[data-key='".concat(optionKey, "']"));
54
54
  if (focusToElement) {
55
55
  focusToElement.focus();
56
56
  } else {
57
+ // If an element is not found, focus on the listbox. This ensures the list can still be navigated to via keyboard if the keys do not align to the data attributes of the list items.
57
58
  (_a = ref.current) === null || _a === undefined ? undefined : _a.focus();
58
59
  }
59
60
  }
60
61
  // Only run this effect for checking the first successful render
61
62
  // eslint-disable-next-line react-hooks/exhaustive-deps
62
- }, [hasStableYPosition]);
63
+ }, [isClientReady]);
63
64
  return /*#__PURE__*/React.createElement("ul", __assign({
64
65
  ref: ref,
65
66
  className: classnames(styles.listBox, classNameOverride)
@@ -0,0 +1,18 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ /**
4
+ * A hook that returns a truthy value indicating if the code can be run on client side.
5
+ * This is a useful hook for determining if the `document` or `window` objects are available.
6
+ */
7
+ var useIsClientReady = function () {
8
+ var _a = useState(false),
9
+ isClientReady = _a[0],
10
+ setIsClientReady = _a[1];
11
+ useEffect(function () {
12
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
13
+ setIsClientReady(true);
14
+ }
15
+ }, []);
16
+ return isClientReady;
17
+ };
18
+ export { useIsClientReady };
package/dist/styles.css CHANGED
@@ -1863,6 +1863,10 @@
1863
1863
  .CalendarRange-module_captionEnd__GSLQO .CalendarRange-module_nav__OtaVb {
1864
1864
  flex-direction: row-reverse;
1865
1865
  }
1866
+ .CalendarRange-module_captionStart__K5hlF.CalendarRange-module_captionEnd__GSLQO .CalendarRange-module_nav__OtaVb {
1867
+ justify-content: space-between;
1868
+ flex-direction: row;
1869
+ }
1866
1870
 
1867
1871
  .CalendarRange-module_dayRangeStart__TwdDT,
1868
1872
  .CalendarRange-module_dayRangeEnd__y6dEB {
@@ -2390,12 +2394,13 @@
2390
2394
  /** THIS IS AN AUTOGENERATED FILE **/
2391
2395
  .Content-module_content__ZeTRs {
2392
2396
  max-width: 1392px;
2393
- padding: 0 72px;
2397
+ margin: 0 72px;
2394
2398
  width: 100%;
2395
2399
  }
2396
2400
  @media (max-width: calc(1080px - 1px)) {
2397
2401
  .Content-module_content__ZeTRs {
2398
- padding: 0 12px;
2402
+ margin: 0 12px;
2403
+ width: calc(100% - 2 * 12px);
2399
2404
  }
2400
2405
  }
2401
2406
  /** THIS IS AN AUTOGENERATED FILE **/
@@ -3710,6 +3715,14 @@ input[type=range].InputRange-module_ratingScaleRange__gI-rs::-ms-thumb:not(:disa
3710
3715
  .FilterDateRangePickerField-module_dateRangeInputField__EEU-X {
3711
3716
  margin-bottom: var(--spacing-24, 1.5rem);
3712
3717
  }
3718
+ @media (width <= 320px) {
3719
+ .FilterDateRangePicker-module_filterDateRangePickerContents__P7eNa {
3720
+ padding: var(--spacing-16);
3721
+ max-width: 320px;
3722
+ box-sizing: border-box;
3723
+ }
3724
+ }
3725
+
3713
3726
  /** THIS IS AN AUTOGENERATED FILE **/
3714
3727
  .ListBox-module_listBox__q95MO {
3715
3728
  list-style: none;
@@ -7,6 +7,6 @@ export type CalendarRangeProps = {
7
7
  hasDivider?: boolean;
8
8
  } & OverrideClassName<Omit<DayPickerRangeProps, 'mode'>>;
9
9
  export declare const CalendarRange: {
10
- ({ id, onMount, hasDivider, classNameOverride, selected, defaultMonth, locale, ...restProps }: CalendarRangeProps): JSX.Element;
10
+ ({ id, onMount, hasDivider, classNameOverride, selected, defaultMonth, numberOfMonths, locale, ...restProps }: CalendarRangeProps): JSX.Element;
11
11
  displayName: string;
12
12
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaizen/components",
3
- "version": "0.0.0-canary-fix-filter-select-jump-issue-useHasStableYPosition-20250120225815",
3
+ "version": "0.0.0-canary-test-1-20250201010717",
4
4
  "description": "Kaizen component library",
5
5
  "author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
6
6
  "homepage": "https://cultureamp.design",
@@ -30,6 +30,13 @@
30
30
  "sideEffects": [
31
31
  "styles.css"
32
32
  ],
33
+ "exports": {
34
+ ".": {
35
+ "import": "./dist/esm/index.mjs",
36
+ "require": "./dist/cjs/index.cjs",
37
+ "types": "./dist/types/index.d.ts"
38
+ }
39
+ },
33
40
  "bin": {
34
41
  "kaizen-codemod": "./bin/codemod.sh"
35
42
  },
@@ -47,6 +47,11 @@ $cell-border-radius: 3px;
47
47
  .captionEnd & {
48
48
  flex-direction: row-reverse;
49
49
  }
50
+
51
+ .captionStart.captionEnd & {
52
+ justify-content: space-between;
53
+ flex-direction: row;
54
+ }
50
55
  }
51
56
 
52
57
  .dayRangeStart,
@@ -22,6 +22,7 @@ export const CalendarRange = ({
22
22
  classNameOverride,
23
23
  selected,
24
24
  defaultMonth,
25
+ numberOfMonths = 2,
25
26
  locale = enAU,
26
27
  ...restProps
27
28
  }: CalendarRangeProps): JSX.Element => {
@@ -39,6 +40,7 @@ export const CalendarRange = ({
39
40
  ...baseCalendarClassNames,
40
41
  month: hasDivider ? styles.monthWithDivider : styles.month,
41
42
  caption_end: styles.captionEnd,
43
+ caption_start: styles.captionStart,
42
44
  nav: styles.nav,
43
45
  day_range_start: styles.dayRangeStart,
44
46
  day_range_end: styles.dayRangeEnd,
@@ -57,7 +59,7 @@ export const CalendarRange = ({
57
59
  IconRight: () => <Icon name="arrow_forward" isPresentational shouldMirrorInRTL />,
58
60
  IconLeft: () => <Icon name="arrow_back" isPresentational shouldMirrorInRTL />,
59
61
  }}
60
- numberOfMonths={2}
62
+ numberOfMonths={numberOfMonths}
61
63
  locale={locale}
62
64
  {...restProps}
63
65
  />
@@ -3,10 +3,11 @@
3
3
 
4
4
  .content {
5
5
  max-width: $layout-content-max-width;
6
- padding: 0 $layout-content-side-margin;
6
+ margin: 0 $layout-content-side-margin;
7
7
  width: 100%;
8
8
 
9
9
  @media (max-width: calc(#{$layout-breakpoints-large} - 1px)) {
10
- padding: 0 $content-margin-width-on-medium-and-small;
10
+ margin: 0 $content-margin-width-on-medium-and-small;
11
+ width: calc(100% - 2 * #{$content-margin-width-on-medium-and-small});
11
12
  }
12
13
  }
@@ -0,0 +1,7 @@
1
+ @media (width <= 320px) {
2
+ .filterDateRangePickerContents {
3
+ padding: var(--spacing-16);
4
+ max-width: 320px;
5
+ box-sizing: border-box;
6
+ }
7
+ }
@@ -1,4 +1,5 @@
1
1
  import React, { useId } from 'react'
2
+ import classNames from 'classnames'
2
3
  import { getLocale } from '~components/DatePicker/utils/getLocale'
3
4
  import { Filter, FilterContents, type FilterProps } from '~components/Filter/Filter'
4
5
  import { type FilterButtonProps } from '../FilterButton'
@@ -9,6 +10,7 @@ import {
9
10
  } from './subcomponents/FilterDateRangePickerField'
10
11
  import { isValidRange } from './subcomponents/FilterDateRangePickerField/utils/isValidRange'
11
12
  import { isCompleteDateRange } from './utils/isCompleteDateRange'
13
+ import styles from './FilterDateRangePicker.module.css'
12
14
 
13
15
  export type FilterDateRangePickerProps = {
14
16
  id?: string
@@ -46,7 +48,7 @@ export const FilterDateRangePicker = ({
46
48
  })
47
49
  }
48
50
  >
49
- <FilterContents>
51
+ <FilterContents classNameOverride={classNames(styles.filterDateRangePickerContents)}>
50
52
  <FilterDateRangePickerField
51
53
  id={`${id}--input`}
52
54
  label={label}
@@ -4,7 +4,7 @@ import * as FilterDRPStories from './FilterDateRangePicker.stories'
4
4
 
5
5
  <Meta of={FilterDRPStories} />
6
6
 
7
- # Filter
7
+ # FilterDateRangePicker
8
8
 
9
9
  <ResourceLinks
10
10
  sourceCode="https://github.com/cultureamp/kaizen-design-system/tree/main/packages/components/src/FilterDateRangePicker"
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useState } from 'react'
2
2
  import { type Meta, type StoryObj } from '@storybook/react'
3
- import { fn } from '@storybook/test'
3
+ import { expect, fn, waitFor, within } from '@storybook/test'
4
4
  import Highlight from 'react-highlight'
5
5
  import { type DateRange } from '~components/Calendar'
6
6
  import { defaultMonthControls } from '~components/Calendar/_docs/controls/defaultMonthControls'
@@ -428,3 +428,38 @@ export const Validation: Story = {
428
428
  controls: { disable: true },
429
429
  },
430
430
  }
431
+
432
+ export const OnSmallViewport: Story = {
433
+ parameters: { viewport: { defaultViewport: 'mobile1' } },
434
+ ...FilterDateRangePickerTemplate,
435
+ play: async ({ canvasElement, step }) => {
436
+ const canvas = within(canvasElement)
437
+
438
+ await step('initial render complete', async () => {
439
+ await waitFor(() => {
440
+ canvas.getByRole('button', {
441
+ name: 'Dates',
442
+ })
443
+ })
444
+ })
445
+
446
+ await step('Can open the date picker', async () => {
447
+ await waitFor(() => {
448
+ const button = canvas.getByRole('button', {
449
+ name: 'Dates',
450
+ })
451
+ button.click()
452
+ })
453
+ })
454
+
455
+ await step('Back and Forward arrow and both visible', async () => {
456
+ await waitFor(() => {
457
+ const prevMonth = canvas.getByLabelText('Go to previous month')
458
+ const nextMonth = canvas.getByLabelText('Go to next month')
459
+
460
+ expect(prevMonth).toBeVisible()
461
+ expect(nextMonth).toBeVisible()
462
+ })
463
+ })
464
+ },
465
+ }
@@ -15,6 +15,7 @@ import { type DateValidationResponse } from '~components/Filter/FilterDatePicker
15
15
  import { useDateInputHandlers } from '~components/Filter/FilterDatePicker/hooks/useDateInputHandlers'
16
16
  import { type DataAttributes } from '~components/types/DataAttributes'
17
17
  import { type OverrideClassName } from '~components/types/OverrideClassName'
18
+ import { useMediaQueries } from '~components/utils/useMediaQueries'
18
19
  import { DateRangeInputField, type DateRangeInputFieldProps } from '../DateRangeInputField'
19
20
  import { filterDatePickerFieldReducer } from './filterDateRangePickerFieldReducer'
20
21
  import { useEndDateValidation } from './hooks/useEndDateValidation'
@@ -84,6 +85,8 @@ export const FilterDateRangePickerField = ({
84
85
  const { formatMessage } = useIntl()
85
86
  const locale = getLocale(propsLocale)
86
87
 
88
+ const { queries } = useMediaQueries()
89
+
87
90
  const translatedDateFrom = formatMessage({
88
91
  id: 'filterDateRangePicker.dateFrom',
89
92
  defaultMessage: 'Date from',
@@ -292,7 +295,7 @@ export const FilterDateRangePickerField = ({
292
295
  dateStart: dateStartValidation.validationMessage,
293
296
  dateEnd: dateEndValidation.validationMessage,
294
297
  }}
295
- classNameOverride={styles.dateRangeInputField}
298
+ classNameOverride={classnames(styles.dateRangeInputField)}
296
299
  />
297
300
  <CalendarRange
298
301
  disabled={disabledDays}
@@ -303,6 +306,7 @@ export const FilterDateRangePickerField = ({
303
306
  }}
304
307
  onSelect={handleCalendarSelectRange}
305
308
  month={state.startMonth}
309
+ numberOfMonths={queries.isSmall ? 1 : 2}
306
310
  onMonthChange={(value: Date) => dispatch({ type: 'navigate_months', date: value })}
307
311
  />
308
312
  </div>
@@ -4,7 +4,7 @@ import * as FilterMultiSelectStories from './FilterMultiSelect.stories'
4
4
 
5
5
  <Meta of={FilterMultiSelectStories} />
6
6
 
7
- # Filter Bar
7
+ # FilterMultiSelect
8
8
 
9
9
  <ResourceLinks
10
10
  sourceCode="https://github.com/cultureamp/kaizen-design-system/tree/main/packages/components/src/FilterMultiSelect"
@@ -100,48 +100,3 @@ export const AdditionalProperties: Story = {
100
100
  },
101
101
  name: 'Additional option properties',
102
102
  }
103
-
104
- /**
105
- * Extend the option type to have additional properties to use for rendering.
106
- */
107
- export const TestPageWithFilterSelect: Story = {
108
- render: (args) => {
109
- const [isOpen, setIsOpen] = useState<boolean>(false)
110
-
111
- return (
112
- <div>
113
- <div style={{ color: 'coral', display: 'block', height: '1500px' }}>Content</div>
114
- <FilterSelect<SelectOption & { isFruit: boolean }>
115
- {...args}
116
- label="Custom"
117
- isOpen={isOpen}
118
- setIsOpen={setIsOpen}
119
- items={[
120
- { label: 'Bubblegum', value: 'bubblegum', isFruit: false },
121
- { label: 'Strawberry', value: 'strawberry', isFruit: true },
122
- { label: 'Chocolate', value: 'chocolate', isFruit: false },
123
- { label: 'Apple', value: 'apple', isFruit: true },
124
- { label: 'Lemon', value: 'lemon', isFruit: true },
125
- ]}
126
- >
127
- {({ items }): JSX.Element[] =>
128
- items.map((item) =>
129
- item.type === 'item' ? (
130
- <FilterSelect.Option
131
- key={item.key}
132
- item={{
133
- ...item,
134
- rendered: item.value?.isFruit ? `${item.rendered} (Fruit)` : item.rendered,
135
- }}
136
- />
137
- ) : (
138
- <FilterSelect.ItemDefaultRender key={item.key} item={item} />
139
- ),
140
- )
141
- }
142
- </FilterSelect>
143
- </div>
144
- )
145
- },
146
- name: 'Additional option properties',
147
- }
@@ -2,9 +2,9 @@ import React, { useEffect, useRef, type HTMLAttributes, type Key, type ReactNode
2
2
  import { useListBox, type AriaListBoxOptions } from '@react-aria/listbox'
3
3
  import { type SelectState } from '@react-stately/select'
4
4
  import classnames from 'classnames'
5
+ import { useIsClientReady } from '~components/__utilities__/useIsClientReady'
5
6
  import { type OverrideClassName } from '~components/types/OverrideClassName'
6
7
  import { useSelectContext } from '../../context'
7
- import { useHasStableYPosition } from '../../hooks/useHasStableYPosition'
8
8
  import { type SelectItem, type SelectOption } from '../../types'
9
9
  import styles from './ListBox.module.scss'
10
10
 
@@ -47,9 +47,9 @@ export const ListBox = <Option extends SelectOption>({
47
47
  classNameOverride,
48
48
  ...restProps
49
49
  }: SingleListBoxProps<Option>): JSX.Element => {
50
+ const isClientReady = useIsClientReady()
50
51
  const { state } = useSelectContext<Option>()
51
52
  const ref = useRef<HTMLUListElement>(null)
52
- const hasStableYPosition = useHasStableYPosition(ref)
53
53
  const { listBoxProps } = useListBox(
54
54
  {
55
55
  ...menuProps,
@@ -62,22 +62,23 @@ export const ListBox = <Option extends SelectOption>({
62
62
  )
63
63
 
64
64
  /**
65
- * This uses the hasStableYPosition to determine if the position is stable within the window
65
+ * This uses the new useIsClientReady to ensure document exists before trying to querySelector and give the time to focus to the correct element
66
66
  */
67
67
  useEffect(() => {
68
- if (hasStableYPosition) {
68
+ if (isClientReady) {
69
69
  const optionKey = getOptionKeyFromCollection(state)
70
70
  const focusToElement = safeQuerySelector(`[data-key='${optionKey}']`)
71
71
 
72
72
  if (focusToElement) {
73
73
  focusToElement.focus()
74
74
  } else {
75
+ // If an element is not found, focus on the listbox. This ensures the list can still be navigated to via keyboard if the keys do not align to the data attributes of the list items.
75
76
  ref.current?.focus()
76
77
  }
77
78
  }
78
79
  // Only run this effect for checking the first successful render
79
80
  // eslint-disable-next-line react-hooks/exhaustive-deps
80
- }, [hasStableYPosition])
81
+ }, [isClientReady])
81
82
 
82
83
  return (
83
84
  <ul
@@ -1,36 +0,0 @@
1
- 'use strict';
2
-
3
- var React = require('react');
4
-
5
- /** This is a helper util to resolve the focus jumping issue with future Select and FilterSelect.
6
- * Due to the floating element's position starting as a negative value on render and then jumping to the correct position, this caused the focus to jump to the top of the page.
7
- * This now polls to check if the element's position is stable by comparing the first and last position.
8
- */
9
- var useHasStableYPosition = function (ref) {
10
- var _a = React.useState(false),
11
- isStable = _a[0],
12
- setIsStable = _a[1];
13
- var _b = React.useState(null),
14
- lastYPosition = _b[0],
15
- setLastYPosition = _b[1];
16
- React.useEffect(function () {
17
- var checkPosition = function () {
18
- if (ref.current) {
19
- var y = ref.current.getBoundingClientRect().y;
20
- if (lastYPosition === null) {
21
- setLastYPosition(y);
22
- } else if (y === lastYPosition) {
23
- setIsStable(true);
24
- } else {
25
- setLastYPosition(y);
26
- }
27
- }
28
- };
29
- var intervalId = setInterval(checkPosition, 1);
30
- return function () {
31
- return clearInterval(intervalId);
32
- };
33
- }, [ref, lastYPosition]);
34
- return isStable;
35
- };
36
- exports.useHasStableYPosition = useHasStableYPosition;
@@ -1,34 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
-
3
- /** This is a helper util to resolve the focus jumping issue with future Select and FilterSelect.
4
- * Due to the floating element's position starting as a negative value on render and then jumping to the correct position, this caused the focus to jump to the top of the page.
5
- * This now polls to check if the element's position is stable by comparing the first and last position.
6
- */
7
- var useHasStableYPosition = function (ref) {
8
- var _a = useState(false),
9
- isStable = _a[0],
10
- setIsStable = _a[1];
11
- var _b = useState(null),
12
- lastYPosition = _b[0],
13
- setLastYPosition = _b[1];
14
- useEffect(function () {
15
- var checkPosition = function () {
16
- if (ref.current) {
17
- var y = ref.current.getBoundingClientRect().y;
18
- if (lastYPosition === null) {
19
- setLastYPosition(y);
20
- } else if (y === lastYPosition) {
21
- setIsStable(true);
22
- } else {
23
- setLastYPosition(y);
24
- }
25
- }
26
- };
27
- var intervalId = setInterval(checkPosition, 1);
28
- return function () {
29
- return clearInterval(intervalId);
30
- };
31
- }, [ref, lastYPosition]);
32
- return isStable;
33
- };
34
- export { useHasStableYPosition };
@@ -1,5 +0,0 @@
1
- /** This is a helper util to resolve the focus jumping issue with future Select and FilterSelect.
2
- * Due to the floating element's position starting as a negative value on render and then jumping to the correct position, this caused the focus to jump to the top of the page.
3
- * This now polls to check if the element's position is stable by comparing the first and last position.
4
- */
5
- export declare const useHasStableYPosition: (ref: React.RefObject<HTMLElement>) => boolean;
@@ -1,31 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
-
3
- /** This is a helper util to resolve the focus jumping issue with future Select and FilterSelect.
4
- * Due to the floating element's position starting as a negative value on render and then jumping to the correct position, this caused the focus to jump to the top of the page.
5
- * This now polls to check if the element's position is stable by comparing the first and last position.
6
- */
7
- export const useHasStableYPosition = (ref: React.RefObject<HTMLElement>): boolean => {
8
- const [isStable, setIsStable] = useState(false)
9
- const [lastYPosition, setLastYPosition] = useState<number | null>(null)
10
-
11
- useEffect(() => {
12
- const checkPosition = (): void => {
13
- if (ref.current) {
14
- const { y } = ref.current.getBoundingClientRect()
15
- if (lastYPosition === null) {
16
- setLastYPosition(y)
17
- } else if (y === lastYPosition) {
18
- setIsStable(true)
19
- } else {
20
- setLastYPosition(y)
21
- }
22
- }
23
- }
24
-
25
- const intervalId = setInterval(checkPosition, 1)
26
-
27
- return () => clearInterval(intervalId)
28
- }, [ref, lastYPosition])
29
-
30
- return isStable
31
- }