@spaced-out/ui-design-system 0.1.26 → 0.1.27

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 (70) hide show
  1. package/.cspell/custom-words.txt +3 -0
  2. package/.storybook/main.js +40 -28
  3. package/.storybook/manager.js +0 -4
  4. package/.storybook/preview-head.html +15 -6
  5. package/.storybook/preview.js +0 -5
  6. package/CHANGELOG.md +22 -0
  7. package/babel.config.js +1 -0
  8. package/lib/components/Badge/Badge.module.css +1 -0
  9. package/lib/components/ButtonDropdown/ButtonDropdown.js +10 -9
  10. package/lib/components/ButtonDropdown/ButtonDropdown.js.flow +6 -4
  11. package/lib/components/Chip/Chip.js +1 -1
  12. package/lib/components/Chip/Chip.js.flow +2 -2
  13. package/lib/components/CollapsibleCard/CollapsibleCard.js +3 -0
  14. package/lib/components/CollapsibleCard/CollapsibleCard.js.flow +4 -0
  15. package/lib/components/Dialog/Dialog.js +23 -2
  16. package/lib/components/Dialog/Dialog.js.flow +38 -0
  17. package/lib/components/Dropdown/Dropdown.js +10 -9
  18. package/lib/components/Dropdown/Dropdown.js.flow +6 -4
  19. package/lib/components/FocusManager/FocusManager.js +7 -5
  20. package/lib/components/FocusManager/FocusManager.js.flow +3 -3
  21. package/lib/components/InlineDropdown/InlineDropdown.js +10 -9
  22. package/lib/components/InlineDropdown/InlineDropdown.js.flow +6 -4
  23. package/lib/components/Menu/Menu.js +48 -12
  24. package/lib/components/Menu/Menu.js.flow +102 -9
  25. package/lib/components/Menu/Menu.module.css +10 -0
  26. package/lib/components/Menu/MenuOptionButton.js +21 -4
  27. package/lib/components/Menu/MenuOptionButton.js.flow +21 -0
  28. package/lib/components/Modal/Modal.js +35 -8
  29. package/lib/components/Modal/Modal.js.flow +52 -7
  30. package/lib/components/Modal/Modal.module.css +1 -3
  31. package/lib/components/Panel/Panel.js +21 -1
  32. package/lib/components/Panel/Panel.js.flow +30 -1
  33. package/lib/components/Panel/Panel.module.css +0 -1
  34. package/lib/components/Table/DefaultRow.js +5 -5
  35. package/lib/components/Table/DefaultRow.js.flow +14 -11
  36. package/lib/components/Table/StaticTable.js +5 -1
  37. package/lib/components/Table/StaticTable.js.flow +4 -0
  38. package/lib/components/Table/Table.js.flow +2 -0
  39. package/lib/components/Tabs/TabList/TabDropdown.js +10 -9
  40. package/lib/components/Tabs/TabList/TabDropdown.js.flow +6 -4
  41. package/lib/components/Toast/Toast.js +7 -5
  42. package/lib/components/Toast/Toast.js.flow +5 -3
  43. package/lib/components/Tooltip/Tooltip.js +22 -25
  44. package/lib/components/Tooltip/Tooltip.js.flow +25 -22
  45. package/lib/components/Typeahead/Typeahead.js +10 -9
  46. package/lib/components/Typeahead/Typeahead.js.flow +6 -4
  47. package/lib/hooks/index.js +55 -0
  48. package/lib/hooks/index.js.flow +5 -0
  49. package/lib/hooks/useCopyToClipboard.js +31 -0
  50. package/lib/hooks/useCopyToClipboard.js.flow +31 -0
  51. package/lib/hooks/useInputState.js +23 -0
  52. package/lib/hooks/useInputState.js.flow +28 -0
  53. package/lib/hooks/useLockedBody.js +54 -0
  54. package/lib/hooks/useLockedBody.js.flow +55 -0
  55. package/lib/hooks/useToggle.js +18 -0
  56. package/lib/hooks/useToggle.js.flow +17 -0
  57. package/lib/hooks/useWindowSize.js +32 -0
  58. package/lib/hooks/useWindowSize.js.flow +37 -0
  59. package/lib/styles/typography.module.css +1 -0
  60. package/lib/types/common.js +0 -1
  61. package/lib/utils/index.js +11 -0
  62. package/lib/utils/index.js.flow +1 -0
  63. package/lib/utils/menu.js +57 -2
  64. package/lib/utils/menu.js.flow +109 -1
  65. package/lib/utils/string.js +4 -2
  66. package/lib/utils/string.js.flow +3 -0
  67. package/lib/utils/tokens.js +74 -0
  68. package/lib/utils/tokens.js.flow +82 -0
  69. package/package.json +18 -16
  70. package/.storybook/public/favicon.svg +0 -6
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.InlineDropdown = void 0;
7
7
  var React = _interopRequireWildcard(require("react"));
8
- var _reactDom = require("@floating-ui/react-dom");
8
+ var _react2 = require("@floating-ui/react");
9
9
  var _space = require("../../styles/variables/_space");
10
10
  var _classify = require("../../utils/classify");
11
11
  var _clickAway = require("../../utils/click-away");
@@ -36,14 +36,14 @@ const InlineDropdown = /*#__PURE__*/React.forwardRef((_ref, ref) => {
36
36
  const {
37
37
  x,
38
38
  y,
39
- reference,
40
- floating,
39
+ refs,
41
40
  strategy
42
- } = (0, _reactDom.useFloating)({
41
+ } = (0, _react2.useFloating)({
42
+ open: true,
43
43
  strategy: 'absolute',
44
44
  placement: anchorPosition,
45
- whileElementsMounted: _reactDom.autoUpdate,
46
- middleware: [(0, _reactDom.shift)(), (0, _reactDom.flip)(), (0, _reactDom.offset)(parseInt(_space.spaceXXSmall))]
45
+ whileElementsMounted: _react2.autoUpdate,
46
+ middleware: [(0, _react2.shift)(), (0, _react2.flip)(), (0, _react2.offset)(parseInt(_space.spaceXXSmall))]
47
47
  });
48
48
  const onMenuToggle = isOpen => {
49
49
  isOpen ? onMenuOpen && onMenuOpen() : onMenuClose && onMenuClose();
@@ -63,7 +63,7 @@ const InlineDropdown = /*#__PURE__*/React.forwardRef((_ref, ref) => {
63
63
  ref: menuBtnRef
64
64
  }, /*#__PURE__*/React.createElement(_Button.UnstyledButton, _extends({}, restButtonProps, {
65
65
  disabled: disabled,
66
- ref: reference,
66
+ ref: refs.setReference,
67
67
  onClick: e => {
68
68
  e.stopPropagation();
69
69
  onOpen();
@@ -80,7 +80,7 @@ const InlineDropdown = /*#__PURE__*/React.forwardRef((_ref, ref) => {
80
80
  })
81
81
  })), isOpen && menu && /*#__PURE__*/React.createElement("div", {
82
82
  onClickCapture: cancelNext,
83
- ref: floating,
83
+ ref: refs.setFloating,
84
84
  style: {
85
85
  display: 'flex',
86
86
  position: strategy,
@@ -94,7 +94,8 @@ const InlineDropdown = /*#__PURE__*/React.forwardRef((_ref, ref) => {
94
94
  clickAway();
95
95
  }
96
96
  },
97
- size: menu.size || 'medium'
97
+ size: menu.size || 'medium',
98
+ onTabOut: clickAway
98
99
  }))));
99
100
  });
100
101
  });
@@ -12,7 +12,7 @@ import {
12
12
  shift,
13
13
  // $FlowFixMe[untyped-import]
14
14
  useFloating,
15
- } from '@floating-ui/react-dom';
15
+ } from '@floating-ui/react';
16
16
 
17
17
  import {spaceNone, spaceXXSmall} from '../../styles/variables/_space';
18
18
  import {classify} from '../../utils/classify';
@@ -66,7 +66,8 @@ export const InlineDropdown: React$AbstractComponent<
66
66
  ): React.Node => {
67
67
  const menuBtnRef = React.useRef(null);
68
68
  React.useImperativeHandle(ref, () => menuBtnRef.current);
69
- const {x, y, reference, floating, strategy} = useFloating({
69
+ const {x, y, refs, strategy} = useFloating({
70
+ open: true,
70
71
  strategy: 'absolute',
71
72
  placement: anchorPosition,
72
73
  whileElementsMounted: autoUpdate,
@@ -91,7 +92,7 @@ export const InlineDropdown: React$AbstractComponent<
91
92
  <UnstyledButton
92
93
  {...restButtonProps}
93
94
  disabled={disabled}
94
- ref={reference}
95
+ ref={refs.setReference}
95
96
  onClick={(e) => {
96
97
  e.stopPropagation();
97
98
  onOpen();
@@ -118,7 +119,7 @@ export const InlineDropdown: React$AbstractComponent<
118
119
  {isOpen && menu && (
119
120
  <div
120
121
  onClickCapture={cancelNext}
121
- ref={floating}
122
+ ref={refs.setFloating}
122
123
  style={{
123
124
  display: 'flex',
124
125
  position: strategy,
@@ -138,6 +139,7 @@ export const InlineDropdown: React$AbstractComponent<
138
139
  }
139
140
  }}
140
141
  size={menu.size || 'medium'}
142
+ onTabOut={clickAway}
141
143
  />
142
144
  </div>
143
145
  )}
@@ -6,6 +6,9 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.Menu = void 0;
7
7
  var React = _interopRequireWildcard(require("react"));
8
8
  var _classify = require("../../utils/classify");
9
+ var _menu = require("../../utils/menu");
10
+ var _SearchInput = require("../SearchInput");
11
+ var _Text = require("../Text");
9
12
  var _MenuOptionButton = require("./MenuOptionButton");
10
13
  var _MenuModule = _interopRequireDefault(require("./Menu.module.css"));
11
14
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -18,44 +21,69 @@ const RenderOption = _ref => {
18
21
  composeOptions,
19
22
  groupTitleOptions,
20
23
  classNames,
24
+ searchText = '',
21
25
  ...restProps
22
26
  } = _ref;
27
+ const {
28
+ allowSearch
29
+ } = restProps;
23
30
  if (options && Array.isArray(options) && options.length) {
24
- return /*#__PURE__*/React.createElement(React.Fragment, null, options.map(option => /*#__PURE__*/React.createElement(React.Fragment, {
31
+ const optionsFiltered = !allowSearch ? options : (0, _menu.getFilteredOptionsFromSearchText)(options, searchText);
32
+ const resultText = !allowSearch ? '' : (0, _menu.getFilteredOptionsResultText)(optionsFiltered);
33
+ return /*#__PURE__*/React.createElement(React.Fragment, null, allowSearch && /*#__PURE__*/React.createElement(_Text.FormLabelSmall, {
34
+ className: _MenuModule.default.filterOptionsResultText,
35
+ color: "tertiary"
36
+ }, resultText), optionsFiltered.map((option, idx) => /*#__PURE__*/React.createElement(React.Fragment, {
25
37
  key: option.key
26
38
  }, /*#__PURE__*/React.createElement(_MenuOptionButton.MenuOptionButton, _extends({
27
39
  option: option,
28
40
  classNames: classNames
29
- }, restProps)))));
41
+ }, restProps, {
42
+ isLastItem: idx === optionsFiltered.length - 1
43
+ })))));
30
44
  }
31
45
  if (composeOptions && Array.isArray(composeOptions) && composeOptions.length) {
32
- return /*#__PURE__*/React.createElement(React.Fragment, null, composeOptions.map((composeMenuOptions, index) =>
46
+ const optionsFiltered = !allowSearch ? composeOptions : (0, _menu.getFilteredComposeOptionsFromSearchText)(composeOptions, searchText);
47
+ const resultText = !allowSearch ? '' : (0, _menu.getFilteredComposeOptionsResultText)(optionsFiltered);
48
+ return /*#__PURE__*/React.createElement(React.Fragment, null, allowSearch && /*#__PURE__*/React.createElement(_Text.FormLabelSmall, {
49
+ className: _MenuModule.default.filterOptionsResultText,
50
+ color: "tertiary"
51
+ }, resultText), optionsFiltered.map((composeMenuOptions, index) =>
33
52
  /*#__PURE__*/
34
53
  // eslint-disable-next-line react/no-array-index-key
35
54
  React.createElement("span", {
36
- className: _MenuModule.default.menuDivider,
37
- key: index
38
- }, composeMenuOptions.map(option => /*#__PURE__*/React.createElement(React.Fragment, {
55
+ key: index,
56
+ className: _MenuModule.default.menuDivider
57
+ }, composeMenuOptions.map((option, idx) => /*#__PURE__*/React.createElement(React.Fragment, {
39
58
  key: option.key
40
59
  }, /*#__PURE__*/React.createElement(_MenuOptionButton.MenuOptionButton, _extends({
41
60
  option: option,
42
61
  classNames: classNames
43
- }, restProps)))))));
62
+ }, restProps, {
63
+ isLastItem: index === optionsFiltered.length - 1 && idx === composeMenuOptions.length - 1
64
+ })))))));
44
65
  }
45
66
  if (groupTitleOptions && Array.isArray(groupTitleOptions) && groupTitleOptions.length) {
46
- return /*#__PURE__*/React.createElement(React.Fragment, null, groupTitleOptions.map((optionsGroup, index) =>
67
+ const optionsFiltered = !allowSearch ? groupTitleOptions : (0, _menu.getFilteredGroupTitleOptionsFromSearchText)(groupTitleOptions, searchText);
68
+ const resultText = !allowSearch ? '' : (0, _menu.getFilteredGroupTitleOptionsResultText)(optionsFiltered);
69
+ return /*#__PURE__*/React.createElement(React.Fragment, null, allowSearch && /*#__PURE__*/React.createElement(_Text.FormLabelSmall, {
70
+ className: _MenuModule.default.filterOptionsResultText,
71
+ color: "tertiary"
72
+ }, resultText), optionsFiltered.map((optionsGroup, index) =>
47
73
  /*#__PURE__*/
48
74
  // eslint-disable-next-line react/no-array-index-key
49
75
  React.createElement(React.Fragment, {
50
76
  key: index
51
77
  }, !!optionsGroup.groupTitle && /*#__PURE__*/React.createElement("div", {
52
78
  className: (0, _classify.classify)(_MenuModule.default.groupTitleWrapper, classNames?.groupTitle)
53
- }, optionsGroup.groupTitle), optionsGroup.options?.map(option => /*#__PURE__*/React.createElement(React.Fragment, {
79
+ }, optionsGroup.groupTitle), optionsGroup.options?.map((option, idx) => /*#__PURE__*/React.createElement(React.Fragment, {
54
80
  key: option.key
55
81
  }, /*#__PURE__*/React.createElement(_MenuOptionButton.MenuOptionButton, _extends({
56
82
  option: option,
57
83
  classNames: classNames
58
- }, restProps)))))));
84
+ }, restProps, {
85
+ isLastItem: index === optionsFiltered.length - 1 && idx === (optionsGroup.options && optionsGroup.options.length - 1)
86
+ })))))));
59
87
  }
60
88
  return /*#__PURE__*/React.createElement(React.Fragment, null);
61
89
  };
@@ -64,8 +92,10 @@ const Menu = /*#__PURE__*/React.forwardRef((props, ref) => {
64
92
  classNames,
65
93
  size = 'medium',
66
94
  width,
67
- isFluid = true
95
+ isFluid = true,
96
+ allowSearch
68
97
  } = props;
98
+ const [searchText, setSearchText] = React.useState('');
69
99
  return /*#__PURE__*/React.createElement("div", {
70
100
  className: (0, _classify.classify)(_MenuModule.default.menuCard, {
71
101
  [_MenuModule.default.fluid]: isFluid,
@@ -76,6 +106,12 @@ const Menu = /*#__PURE__*/React.forwardRef((props, ref) => {
76
106
  width
77
107
  },
78
108
  ref: ref
79
- }, /*#__PURE__*/React.createElement(RenderOption, props));
109
+ }, allowSearch && /*#__PURE__*/React.createElement(_SearchInput.SearchInput, {
110
+ value: searchText,
111
+ onChange: e => setSearchText(e.target.value),
112
+ onClear: () => setSearchText('')
113
+ }), /*#__PURE__*/React.createElement(RenderOption, _extends({}, props, {
114
+ searchText: searchText
115
+ })));
80
116
  });
81
117
  exports.Menu = Menu;
@@ -2,7 +2,17 @@
2
2
  import * as React from 'react';
3
3
 
4
4
  import {classify} from '../../utils/classify';
5
+ import {
6
+ getFilteredComposeOptionsFromSearchText,
7
+ getFilteredComposeOptionsResultText,
8
+ getFilteredGroupTitleOptionsFromSearchText,
9
+ getFilteredGroupTitleOptionsResultText,
10
+ getFilteredOptionsFromSearchText,
11
+ getFilteredOptionsResultText,
12
+ } from '../../utils/menu';
5
13
  import type {IconType} from '../Icon/Icon';
14
+ import {SearchInput} from '../SearchInput';
15
+ import {FormLabelSmall} from '../Text';
6
16
 
7
17
  import {MenuOptionButton} from './MenuOptionButton';
8
18
 
@@ -43,6 +53,10 @@ export type BaseMenuProps = {
43
53
  width?: string,
44
54
  menuDisabled?: boolean,
45
55
  isFluid?: boolean,
56
+ // onTabOut is a callback function that is called when
57
+ // the user navigates outside of the menu using the tab key.
58
+ onTabOut?: () => mixed,
59
+ allowSearch?: boolean,
46
60
  };
47
61
 
48
62
  export type MenuOptionTypes = {
@@ -65,22 +79,45 @@ export type MenuProps = {
65
79
  ...MenuOptionTypes,
66
80
  };
67
81
 
82
+ export type RenderOptionProps = {
83
+ ...MenuProps,
84
+ searchText?: string,
85
+ };
86
+
68
87
  const RenderOption = ({
69
88
  options,
70
89
  composeOptions,
71
90
  groupTitleOptions,
72
91
  classNames,
92
+ searchText = '',
73
93
  ...restProps
74
- }: MenuProps): React.Node => {
94
+ }: RenderOptionProps): React.Node => {
95
+ const {allowSearch} = restProps;
75
96
  if (options && Array.isArray(options) && options.length) {
97
+ const optionsFiltered = !allowSearch
98
+ ? options
99
+ : getFilteredOptionsFromSearchText(options, searchText);
100
+ const resultText = !allowSearch
101
+ ? ''
102
+ : getFilteredOptionsResultText(optionsFiltered);
103
+
76
104
  return (
77
105
  <>
78
- {options.map((option) => (
106
+ {allowSearch && (
107
+ <FormLabelSmall
108
+ className={css.filterOptionsResultText}
109
+ color="tertiary"
110
+ >
111
+ {resultText}
112
+ </FormLabelSmall>
113
+ )}
114
+ {optionsFiltered.map((option, idx) => (
79
115
  <React.Fragment key={option.key}>
80
116
  <MenuOptionButton
81
117
  option={option}
82
118
  classNames={classNames}
83
119
  {...restProps}
120
+ isLastItem={idx === optionsFiltered.length - 1}
84
121
  />
85
122
  </React.Fragment>
86
123
  ))}
@@ -92,17 +129,36 @@ const RenderOption = ({
92
129
  Array.isArray(composeOptions) &&
93
130
  composeOptions.length
94
131
  ) {
132
+ const optionsFiltered = !allowSearch
133
+ ? composeOptions
134
+ : getFilteredComposeOptionsFromSearchText(composeOptions, searchText);
135
+ const resultText = !allowSearch
136
+ ? ''
137
+ : getFilteredComposeOptionsResultText(optionsFiltered);
138
+
95
139
  return (
96
140
  <>
97
- {composeOptions.map((composeMenuOptions, index) => (
141
+ {allowSearch && (
142
+ <FormLabelSmall
143
+ className={css.filterOptionsResultText}
144
+ color="tertiary"
145
+ >
146
+ {resultText}
147
+ </FormLabelSmall>
148
+ )}
149
+ {optionsFiltered.map((composeMenuOptions, index) => (
98
150
  // eslint-disable-next-line react/no-array-index-key
99
- <span className={css.menuDivider} key={index}>
100
- {composeMenuOptions.map((option) => (
151
+ <span key={index} className={css.menuDivider}>
152
+ {composeMenuOptions.map((option, idx) => (
101
153
  <React.Fragment key={option.key}>
102
154
  <MenuOptionButton
103
155
  option={option}
104
156
  classNames={classNames}
105
157
  {...restProps}
158
+ isLastItem={
159
+ index === optionsFiltered.length - 1 &&
160
+ idx === composeMenuOptions.length - 1
161
+ }
106
162
  />
107
163
  </React.Fragment>
108
164
  ))}
@@ -116,9 +172,27 @@ const RenderOption = ({
116
172
  Array.isArray(groupTitleOptions) &&
117
173
  groupTitleOptions.length
118
174
  ) {
175
+ const optionsFiltered = !allowSearch
176
+ ? groupTitleOptions
177
+ : getFilteredGroupTitleOptionsFromSearchText(
178
+ groupTitleOptions,
179
+ searchText,
180
+ );
181
+ const resultText = !allowSearch
182
+ ? ''
183
+ : getFilteredGroupTitleOptionsResultText(optionsFiltered);
184
+
119
185
  return (
120
186
  <>
121
- {groupTitleOptions.map((optionsGroup, index) => (
187
+ {allowSearch && (
188
+ <FormLabelSmall
189
+ className={css.filterOptionsResultText}
190
+ color="tertiary"
191
+ >
192
+ {resultText}
193
+ </FormLabelSmall>
194
+ )}
195
+ {optionsFiltered.map((optionsGroup, index) => (
122
196
  // eslint-disable-next-line react/no-array-index-key
123
197
  <React.Fragment key={index}>
124
198
  {!!optionsGroup.groupTitle && (
@@ -132,12 +206,17 @@ const RenderOption = ({
132
206
  </div>
133
207
  )}
134
208
 
135
- {optionsGroup.options?.map((option) => (
209
+ {optionsGroup.options?.map((option, idx) => (
136
210
  <React.Fragment key={option.key}>
137
211
  <MenuOptionButton
138
212
  option={option}
139
213
  classNames={classNames}
140
214
  {...restProps}
215
+ isLastItem={
216
+ index === optionsFiltered.length - 1 &&
217
+ idx ===
218
+ (optionsGroup.options && optionsGroup.options.length - 1)
219
+ }
141
220
  />
142
221
  </React.Fragment>
143
222
  ))}
@@ -152,7 +231,14 @@ const RenderOption = ({
152
231
  export const Menu: React$AbstractComponent<MenuProps, HTMLDivElement> =
153
232
  React.forwardRef<MenuProps, HTMLDivElement>(
154
233
  (props: MenuProps, ref): React.Node => {
155
- const {classNames, size = 'medium', width, isFluid = true} = props;
234
+ const {
235
+ classNames,
236
+ size = 'medium',
237
+ width,
238
+ isFluid = true,
239
+ allowSearch,
240
+ } = props;
241
+ const [searchText, setSearchText] = React.useState('');
156
242
 
157
243
  return (
158
244
  <div
@@ -168,7 +254,14 @@ export const Menu: React$AbstractComponent<MenuProps, HTMLDivElement> =
168
254
  style={{width}}
169
255
  ref={ref}
170
256
  >
171
- <RenderOption {...props} />
257
+ {allowSearch && (
258
+ <SearchInput
259
+ value={searchText}
260
+ onChange={(e) => setSearchText(e.target.value)}
261
+ onClear={() => setSearchText('')}
262
+ />
263
+ )}
264
+ <RenderOption {...props} searchText={searchText} />
172
265
  </div>
173
266
  );
174
267
  },
@@ -191,6 +191,10 @@
191
191
  padding-bottom: spaceXSmall;
192
192
  }
193
193
 
194
+ .hideDivider {
195
+ border-top: none;
196
+ }
197
+
194
198
  .menuDivider:first-child {
195
199
  padding-top: spaceNone;
196
200
  }
@@ -213,3 +217,9 @@
213
217
  color: colorTextTertiary;
214
218
  padding-top: spaceSmall;
215
219
  }
220
+
221
+ .filterOptionsResultText {
222
+ margin-top: calc(spaceXSmall * 2);
223
+ margin-bottom: spaceXSmall;
224
+ margin-left: spaceSmall;
225
+ }
@@ -15,8 +15,9 @@ var _MenuModule = _interopRequireDefault(require("./Menu.module.css"));
15
15
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16
16
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
17
17
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
18
-
18
+ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
19
19
  const MenuOptionButton = props => {
20
+ const lastMenuItemRef = React.useRef(null);
20
21
  const {
21
22
  option,
22
23
  size,
@@ -25,7 +26,9 @@ const MenuOptionButton = props => {
25
26
  menuDisabled,
26
27
  classNames,
27
28
  optionsVariant = 'normal',
28
- selectedKeys
29
+ selectedKeys,
30
+ isLastItem,
31
+ onTabOut
29
32
  } = props;
30
33
  const {
31
34
  key,
@@ -50,7 +53,19 @@ const MenuOptionButton = props => {
50
53
  React.useEffect(() => {
51
54
  setButtonSize(optionSize || size);
52
55
  }, [optionSize, size]);
53
- return /*#__PURE__*/React.createElement(_Button.UnstyledButton, {
56
+ React.useEffect(() => {
57
+ const handleKeyDown = event => {
58
+ if (event.key === 'Tab' && !event.shiftKey) {
59
+ // Tab pressed without shift key, calling tab out callback
60
+ onTabOut?.();
61
+ }
62
+ };
63
+ lastMenuItemRef.current?.addEventListener('keydown', handleKeyDown);
64
+ return () => {
65
+ lastMenuItemRef.current?.removeEventListener('keydown', handleKeyDown);
66
+ };
67
+ }, [isLastItem]);
68
+ return /*#__PURE__*/React.createElement(_Button.UnstyledButton, _extends({
54
69
  className: (0, _classify.classify)(_MenuModule.default.option, {
55
70
  [_MenuModule.default.selected]: isSelected() || key === selectedOption?.key,
56
71
  [_MenuModule.default.optionSmall]: buttonSize === 'small',
@@ -62,7 +77,9 @@ const MenuOptionButton = props => {
62
77
  disabled: menuDisabled || disabled,
63
78
  onClick: () => onSelect && onSelect(option),
64
79
  autoFocus: selectedOption?.key === key
65
- }, optionVariant === 'checkbox' && /*#__PURE__*/React.createElement(_Checkbox.Checkbox, {
80
+ }, isLastItem ? {
81
+ ref: lastMenuItemRef
82
+ } : {}), optionVariant === 'checkbox' && /*#__PURE__*/React.createElement(_Checkbox.Checkbox, {
66
83
  tabIndex: -1,
67
84
  disabled: menuDisabled || disabled,
68
85
  checked: isSelected()
@@ -16,9 +16,12 @@ import css from './Menu.module.css';
16
16
  export type MenuOptionProps = {
17
17
  ...BaseMenuProps,
18
18
  option: MenuOption,
19
+ isLastItem?: boolean,
19
20
  };
20
21
 
21
22
  export const MenuOptionButton = (props: MenuOptionProps): React.Node => {
23
+ const lastMenuItemRef: {current: HTMLButtonElement | null} =
24
+ React.useRef(null);
22
25
  const {
23
26
  option,
24
27
  size,
@@ -28,6 +31,8 @@ export const MenuOptionButton = (props: MenuOptionProps): React.Node => {
28
31
  classNames,
29
32
  optionsVariant = 'normal',
30
33
  selectedKeys,
34
+ isLastItem,
35
+ onTabOut,
31
36
  } = props;
32
37
  const {
33
38
  key,
@@ -55,6 +60,21 @@ export const MenuOptionButton = (props: MenuOptionProps): React.Node => {
55
60
  setButtonSize(optionSize || size);
56
61
  }, [optionSize, size]);
57
62
 
63
+ React.useEffect(() => {
64
+ const handleKeyDown = (event: KeyboardEvent) => {
65
+ if (event.key === 'Tab' && !event.shiftKey) {
66
+ // Tab pressed without shift key, calling tab out callback
67
+ onTabOut?.();
68
+ }
69
+ };
70
+
71
+ lastMenuItemRef.current?.addEventListener('keydown', handleKeyDown);
72
+
73
+ return () => {
74
+ lastMenuItemRef.current?.removeEventListener('keydown', handleKeyDown);
75
+ };
76
+ }, [isLastItem]);
77
+
58
78
  return (
59
79
  <UnstyledButton
60
80
  className={classify(
@@ -72,6 +92,7 @@ export const MenuOptionButton = (props: MenuOptionProps): React.Node => {
72
92
  disabled={menuDisabled || disabled}
73
93
  onClick={() => onSelect && onSelect(option)}
74
94
  autoFocus={selectedOption?.key === key}
95
+ {...(isLastItem ? {ref: lastMenuItemRef} : {})}
75
96
  >
76
97
  {optionVariant === 'checkbox' && (
77
98
  <Checkbox
@@ -6,9 +6,10 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.ModalHeader = exports.ModalFooter = exports.ModalBody = exports.Modal = void 0;
7
7
  var React = _interopRequireWildcard(require("react"));
8
8
  var _reactDom = require("react-dom");
9
- var _reactDomInteractions = require("@floating-ui/react-dom-interactions");
9
+ var _react2 = require("@floating-ui/react");
10
10
  var _useMountTransition = _interopRequireDefault(require("../../hooks/useMountTransition"));
11
11
  var _motion = require("../../styles/variables/_motion");
12
+ var _space = require("../../styles/variables/_space");
12
13
  var _classify = _interopRequireDefault(require("../../utils/classify"));
13
14
  var _helpers = require("../../utils/helpers");
14
15
  var _Button = require("../Button/Button");
@@ -20,6 +21,21 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
20
21
 
21
22
  // $FlowFixMe[untyped-import]
22
23
 
24
+ const DEFAULT_MODAL_ANIMATION = {
25
+ duration: {
26
+ open: parseInt(_motion.motionDurationSlow),
27
+ close: parseInt(_motion.motionDurationNormal)
28
+ },
29
+ initial: {
30
+ transform: `translate(${_space.spaceNegHalfFluid}, ${_space.spaceNegHalfFluid}) scale(0.95)`
31
+ },
32
+ open: {
33
+ transform: `translate(${_space.spaceNegHalfFluid}, ${_space.spaceNegHalfFluid}) scale(1)`
34
+ },
35
+ close: {
36
+ transform: `translate(${_space.spaceNegHalfFluid}, ${_space.spaceNegHalfFluid}) scale(0.95)`
37
+ }
38
+ };
23
39
  const ModalHeader = _ref => {
24
40
  let {
25
41
  children,
@@ -103,12 +119,19 @@ const Modal = _ref4 => {
103
119
  onClose,
104
120
  hideBackdrop = false,
105
121
  tapOutsideToClose = true,
106
- initialFocus = -1
122
+ initialFocus = -1,
123
+ customAnimation
107
124
  } = _ref4;
108
125
  const {
109
- floating,
126
+ refs,
110
127
  context
111
- } = (0, _reactDomInteractions.useFloating)();
128
+ } = (0, _react2.useFloating)({
129
+ open: isOpen
130
+ });
131
+ const {
132
+ isMounted,
133
+ styles
134
+ } = (0, _react2.useTransitionStyles)(context, customAnimation || DEFAULT_MODAL_ANIMATION);
112
135
  const modalId = (0, _helpers.uuid)();
113
136
  const bodyRef = React.useRef(document.querySelector('body'));
114
137
  const portalRootRef = React.useRef(getModalRoot(modalId) || createPortalRoot(modalId));
@@ -174,11 +197,11 @@ const Modal = _ref4 => {
174
197
  onClose(e);
175
198
  }
176
199
  };
177
- return /*#__PURE__*/(0, _reactDom.createPortal)( /*#__PURE__*/React.createElement(_reactDomInteractions.FloatingFocusManager, {
200
+ return /*#__PURE__*/(0, _reactDom.createPortal)( /*#__PURE__*/React.createElement(_react2.FloatingFocusManager, {
178
201
  context: context,
179
202
  initialFocus: initialFocus
180
203
  }, /*#__PURE__*/React.createElement("div", {
181
- ref: floating,
204
+ ref: refs.setFloating,
182
205
  "aria-hidden": isOpen ? 'false' : 'true',
183
206
  className: (0, _classify.default)(_ModalModule.default.modalContainer, {
184
207
  [_ModalModule.default.in]: isTransitioning,
@@ -189,9 +212,13 @@ const Modal = _ref4 => {
189
212
  [_ModalModule.default.darkBackdrop]: !hideBackdrop
190
213
  }, classNames?.backdrop),
191
214
  onClick: onBackdropClick
192
- }), /*#__PURE__*/React.createElement("div", {
215
+ }), isMounted && /*#__PURE__*/React.createElement("div", {
193
216
  className: (0, _classify.default)(_ModalModule.default.modal, classNames?.content),
194
- role: "dialog"
217
+ role: "dialog",
218
+ style: {
219
+ // Transition styles
220
+ ...styles
221
+ }
195
222
  }, children))), portalRootRef.current);
196
223
  };
197
224
  exports.Modal = Modal;