@itwin/itwinui-react 3.0.0-dev.7 → 3.0.0-dev.9

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 (215) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/cjs/core/Alert/Alert.d.ts +20 -9
  3. package/cjs/core/Alert/Alert.js +48 -10
  4. package/cjs/core/ButtonGroup/ButtonGroup.js +41 -36
  5. package/cjs/core/Buttons/DropdownButton/DropdownButton.js +7 -19
  6. package/cjs/core/Buttons/IconButton/IconButton.js +27 -44
  7. package/cjs/core/Buttons/SplitButton/SplitButton.d.ts +4 -4
  8. package/cjs/core/Buttons/SplitButton/SplitButton.js +54 -29
  9. package/cjs/core/ColorPicker/ColorInputPanel.js +172 -231
  10. package/cjs/core/ComboBox/ComboBox.d.ts +2 -2
  11. package/cjs/core/ComboBox/ComboBox.js +33 -25
  12. package/cjs/core/ComboBox/ComboBoxEndIcon.js +3 -22
  13. package/cjs/core/ComboBox/ComboBoxInput.js +29 -21
  14. package/cjs/core/ComboBox/ComboBoxMenu.d.ts +2 -2
  15. package/cjs/core/ComboBox/ComboBoxMenu.js +73 -93
  16. package/cjs/core/ComboBox/ComboBoxMenuItem.d.ts +1 -1
  17. package/cjs/core/ComboBox/ComboBoxMenuItem.js +8 -6
  18. package/cjs/core/ComboBox/helpers.d.ts +5 -3
  19. package/cjs/core/DatePicker/DatePicker.d.ts +30 -8
  20. package/cjs/core/DatePicker/DatePicker.js +40 -5
  21. package/cjs/core/Dialog/Dialog.js +10 -16
  22. package/cjs/core/Dialog/DialogContext.d.ts +3 -4
  23. package/cjs/core/DropdownMenu/DropdownMenu.d.ts +6 -5
  24. package/cjs/core/DropdownMenu/DropdownMenu.js +59 -55
  25. package/cjs/core/ExpandableBlock/ExpandableBlock.d.ts +20 -14
  26. package/cjs/core/ExpandableBlock/ExpandableBlock.js +38 -15
  27. package/cjs/core/Header/HeaderDropdownButton.js +1 -2
  28. package/cjs/core/Header/HeaderSplitButton.js +2 -2
  29. package/cjs/core/Input/Input.d.ts +4 -0
  30. package/cjs/core/Input/Input.js +2 -1
  31. package/cjs/core/InputGrid/InputGrid.d.ts +25 -0
  32. package/cjs/core/InputGrid/InputGrid.js +39 -0
  33. package/cjs/core/InputGrid/index.d.ts +3 -0
  34. package/cjs/core/InputGrid/index.js +15 -0
  35. package/cjs/core/InputGroup/InputGroup.d.ts +13 -0
  36. package/cjs/core/InputGroup/InputGroup.js +35 -9
  37. package/cjs/core/InputWithDecorations/InputWithDecorations.d.ts +39 -0
  38. package/cjs/core/InputWithDecorations/InputWithDecorations.js +81 -0
  39. package/cjs/core/InputWithDecorations/index.d.ts +3 -0
  40. package/cjs/core/InputWithDecorations/index.js +15 -0
  41. package/cjs/core/Label/Label.d.ts +5 -0
  42. package/cjs/core/Label/Label.js +2 -0
  43. package/cjs/core/LabeledInput/LabeledInput.d.ts +22 -16
  44. package/cjs/core/LabeledInput/LabeledInput.js +52 -29
  45. package/cjs/core/LabeledSelect/LabeledSelect.d.ts +17 -7
  46. package/cjs/core/LabeledSelect/LabeledSelect.js +36 -17
  47. package/cjs/core/LabeledTextarea/LabeledTextarea.d.ts +15 -5
  48. package/cjs/core/LabeledTextarea/LabeledTextarea.js +12 -45
  49. package/cjs/core/Menu/Menu.d.ts +1 -1
  50. package/cjs/core/Menu/Menu.js +2 -2
  51. package/cjs/core/Menu/MenuDivider.d.ts +2 -1
  52. package/cjs/core/Menu/MenuDivider.js +1 -1
  53. package/cjs/core/Menu/MenuItem.d.ts +1 -1
  54. package/cjs/core/Menu/MenuItem.js +78 -55
  55. package/cjs/core/Menu/MenuItemSkeleton.d.ts +1 -1
  56. package/cjs/core/Menu/MenuItemSkeleton.js +0 -1
  57. package/cjs/core/SearchBox/SearchBox.js +1 -1
  58. package/cjs/core/Select/Select.d.ts +9 -5
  59. package/cjs/core/Select/Select.js +81 -99
  60. package/cjs/core/SideNavigation/SideNavigation.js +2 -0
  61. package/cjs/core/Slider/Thumb.js +1 -0
  62. package/cjs/core/StatusMessage/StatusMessage.d.ts +12 -2
  63. package/cjs/core/StatusMessage/StatusMessage.js +23 -9
  64. package/cjs/core/Table/SubRowExpander.js +2 -0
  65. package/cjs/core/Table/columns/actionColumn.js +3 -7
  66. package/cjs/core/Table/filters/DateRangeFilter/DatePickerInput.d.ts +6 -1
  67. package/cjs/core/Table/filters/DateRangeFilter/DatePickerInput.js +56 -33
  68. package/cjs/core/Table/filters/DateRangeFilter/DateRangeFilter.d.ts +2 -0
  69. package/cjs/core/Table/filters/DateRangeFilter/DateRangeFilter.js +2 -0
  70. package/cjs/core/Table/filters/FilterToggle.js +3 -2
  71. package/cjs/core/Textarea/Textarea.d.ts +7 -1
  72. package/cjs/core/Textarea/Textarea.js +6 -11
  73. package/cjs/core/ThemeProvider/ThemeProvider.js +1 -1
  74. package/cjs/core/Tile/Tile.d.ts +139 -15
  75. package/cjs/core/Tile/Tile.js +128 -38
  76. package/cjs/core/Toast/Toast.d.ts +12 -4
  77. package/cjs/core/Toast/Toast.js +20 -4
  78. package/cjs/core/Tooltip/Tooltip.d.ts +35 -28
  79. package/cjs/core/Tooltip/Tooltip.js +116 -117
  80. package/cjs/core/TransferList/TransferList.js +4 -12
  81. package/cjs/core/index.d.ts +3 -1
  82. package/cjs/core/index.js +28 -5
  83. package/cjs/core/utils/components/Icon.d.ts +5 -0
  84. package/cjs/core/utils/components/Icon.js +8 -1
  85. package/cjs/core/utils/components/InputContainer.d.ts +4 -5
  86. package/cjs/core/utils/components/InputContainer.js +21 -37
  87. package/cjs/core/utils/components/InputFlexContainer.d.ts +1 -0
  88. package/cjs/core/utils/components/InputFlexContainer.js +3 -1
  89. package/cjs/core/utils/components/Popover.d.ts +113 -27
  90. package/cjs/core/utils/components/Popover.js +156 -118
  91. package/cjs/core/utils/components/Portal.d.ts +27 -0
  92. package/cjs/core/utils/components/Portal.js +43 -0
  93. package/cjs/core/utils/components/index.d.ts +1 -0
  94. package/cjs/core/utils/components/index.js +1 -0
  95. package/cjs/core/utils/functions/index.d.ts +1 -0
  96. package/cjs/core/utils/functions/index.js +1 -0
  97. package/cjs/core/utils/functions/react.d.ts +8 -0
  98. package/cjs/core/utils/functions/react.js +40 -0
  99. package/cjs/core/utils/hooks/index.d.ts +1 -1
  100. package/cjs/core/utils/hooks/index.js +1 -1
  101. package/cjs/core/utils/hooks/useControlledState.d.ts +13 -0
  102. package/cjs/core/utils/hooks/useControlledState.js +39 -0
  103. package/cjs/styles.js +10 -31
  104. package/esm/core/Alert/Alert.d.ts +20 -9
  105. package/esm/core/Alert/Alert.js +49 -10
  106. package/esm/core/ButtonGroup/ButtonGroup.js +41 -36
  107. package/esm/core/Buttons/DropdownButton/DropdownButton.js +8 -24
  108. package/esm/core/Buttons/IconButton/IconButton.js +25 -40
  109. package/esm/core/Buttons/SplitButton/SplitButton.d.ts +4 -4
  110. package/esm/core/Buttons/SplitButton/SplitButton.js +61 -28
  111. package/esm/core/ColorPicker/ColorInputPanel.js +173 -232
  112. package/esm/core/ComboBox/ComboBox.d.ts +2 -2
  113. package/esm/core/ComboBox/ComboBox.js +34 -25
  114. package/esm/core/ComboBox/ComboBoxEndIcon.js +4 -25
  115. package/esm/core/ComboBox/ComboBoxInput.js +22 -21
  116. package/esm/core/ComboBox/ComboBoxMenu.d.ts +2 -2
  117. package/esm/core/ComboBox/ComboBoxMenu.js +67 -87
  118. package/esm/core/ComboBox/ComboBoxMenuItem.d.ts +1 -1
  119. package/esm/core/ComboBox/ComboBoxMenuItem.js +9 -7
  120. package/esm/core/ComboBox/helpers.d.ts +5 -3
  121. package/esm/core/DatePicker/DatePicker.d.ts +30 -8
  122. package/esm/core/DatePicker/DatePicker.js +25 -5
  123. package/esm/core/Dialog/Dialog.js +11 -23
  124. package/esm/core/Dialog/DialogContext.d.ts +3 -4
  125. package/esm/core/DropdownMenu/DropdownMenu.d.ts +6 -5
  126. package/esm/core/DropdownMenu/DropdownMenu.js +64 -56
  127. package/esm/core/ExpandableBlock/ExpandableBlock.d.ts +20 -14
  128. package/esm/core/ExpandableBlock/ExpandableBlock.js +39 -17
  129. package/esm/core/Header/HeaderDropdownButton.js +1 -2
  130. package/esm/core/Header/HeaderSplitButton.js +2 -2
  131. package/esm/core/Input/Input.d.ts +4 -0
  132. package/esm/core/Input/Input.js +2 -1
  133. package/esm/core/InputGrid/InputGrid.d.ts +25 -0
  134. package/esm/core/InputGrid/InputGrid.js +35 -0
  135. package/esm/core/InputGrid/index.d.ts +3 -0
  136. package/esm/core/InputGrid/index.js +6 -0
  137. package/esm/core/InputGroup/InputGroup.d.ts +13 -0
  138. package/esm/core/InputGroup/InputGroup.js +34 -10
  139. package/esm/core/InputWithDecorations/InputWithDecorations.d.ts +39 -0
  140. package/esm/core/InputWithDecorations/InputWithDecorations.js +80 -0
  141. package/esm/core/InputWithDecorations/index.d.ts +3 -0
  142. package/esm/core/InputWithDecorations/index.js +6 -0
  143. package/esm/core/Label/Label.d.ts +5 -0
  144. package/esm/core/Label/Label.js +2 -0
  145. package/esm/core/LabeledInput/LabeledInput.d.ts +22 -16
  146. package/esm/core/LabeledInput/LabeledInput.js +53 -29
  147. package/esm/core/LabeledSelect/LabeledSelect.d.ts +17 -7
  148. package/esm/core/LabeledSelect/LabeledSelect.js +37 -18
  149. package/esm/core/LabeledTextarea/LabeledTextarea.d.ts +15 -5
  150. package/esm/core/LabeledTextarea/LabeledTextarea.js +14 -45
  151. package/esm/core/Menu/Menu.d.ts +1 -1
  152. package/esm/core/Menu/Menu.js +8 -3
  153. package/esm/core/Menu/MenuDivider.d.ts +2 -1
  154. package/esm/core/Menu/MenuDivider.js +1 -1
  155. package/esm/core/Menu/MenuItem.d.ts +1 -1
  156. package/esm/core/Menu/MenuItem.js +85 -52
  157. package/esm/core/Menu/MenuItemSkeleton.d.ts +1 -1
  158. package/esm/core/Menu/MenuItemSkeleton.js +0 -1
  159. package/esm/core/SearchBox/SearchBox.js +1 -1
  160. package/esm/core/Select/Select.d.ts +9 -5
  161. package/esm/core/Select/Select.js +81 -96
  162. package/esm/core/SideNavigation/SideNavigation.js +2 -0
  163. package/esm/core/Slider/Thumb.js +1 -0
  164. package/esm/core/StatusMessage/StatusMessage.d.ts +12 -2
  165. package/esm/core/StatusMessage/StatusMessage.js +23 -16
  166. package/esm/core/Table/SubRowExpander.js +2 -0
  167. package/esm/core/Table/columns/actionColumn.js +3 -7
  168. package/esm/core/Table/filters/DateRangeFilter/DatePickerInput.d.ts +6 -1
  169. package/esm/core/Table/filters/DateRangeFilter/DatePickerInput.js +56 -33
  170. package/esm/core/Table/filters/DateRangeFilter/DateRangeFilter.d.ts +2 -0
  171. package/esm/core/Table/filters/DateRangeFilter/DateRangeFilter.js +2 -0
  172. package/esm/core/Table/filters/FilterToggle.js +3 -2
  173. package/esm/core/Textarea/Textarea.d.ts +7 -1
  174. package/esm/core/Textarea/Textarea.js +6 -11
  175. package/esm/core/ThemeProvider/ThemeProvider.js +4 -3
  176. package/esm/core/Tile/Tile.d.ts +139 -15
  177. package/esm/core/Tile/Tile.js +128 -38
  178. package/esm/core/Toast/Toast.d.ts +12 -4
  179. package/esm/core/Toast/Toast.js +21 -4
  180. package/esm/core/Tooltip/Tooltip.d.ts +35 -28
  181. package/esm/core/Tooltip/Tooltip.js +119 -116
  182. package/esm/core/TransferList/TransferList.js +4 -9
  183. package/esm/core/index.d.ts +3 -1
  184. package/esm/core/index.js +3 -0
  185. package/esm/core/utils/components/Icon.d.ts +5 -0
  186. package/esm/core/utils/components/Icon.js +8 -1
  187. package/esm/core/utils/components/InputContainer.d.ts +4 -5
  188. package/esm/core/utils/components/InputContainer.js +21 -32
  189. package/esm/core/utils/components/InputFlexContainer.d.ts +1 -0
  190. package/esm/core/utils/components/InputFlexContainer.js +3 -1
  191. package/esm/core/utils/components/Popover.d.ts +113 -27
  192. package/esm/core/utils/components/Popover.js +175 -118
  193. package/esm/core/utils/components/Portal.d.ts +27 -0
  194. package/esm/core/utils/components/Portal.js +36 -0
  195. package/esm/core/utils/components/index.d.ts +1 -0
  196. package/esm/core/utils/components/index.js +1 -0
  197. package/esm/core/utils/functions/index.d.ts +1 -0
  198. package/esm/core/utils/functions/index.js +1 -0
  199. package/esm/core/utils/functions/react.d.ts +8 -0
  200. package/esm/core/utils/functions/react.js +35 -0
  201. package/esm/core/utils/hooks/index.d.ts +1 -1
  202. package/esm/core/utils/hooks/index.js +1 -1
  203. package/esm/core/utils/hooks/useControlledState.d.ts +13 -0
  204. package/esm/core/utils/hooks/useControlledState.js +34 -0
  205. package/esm/styles.js +10 -31
  206. package/package.json +3 -5
  207. package/styles.css +23 -20
  208. package/cjs/core/ComboBox/ComboBoxDropdown.d.ts +0 -7
  209. package/cjs/core/ComboBox/ComboBoxDropdown.js +0 -48
  210. package/cjs/core/utils/hooks/useUncontrolledState.d.ts +0 -6
  211. package/cjs/core/utils/hooks/useUncontrolledState.js +0 -18
  212. package/esm/core/ComboBox/ComboBoxDropdown.d.ts +0 -7
  213. package/esm/core/ComboBox/ComboBoxDropdown.js +0 -42
  214. package/esm/core/utils/hooks/useUncontrolledState.d.ts +0 -6
  215. package/esm/core/utils/hooks/useUncontrolledState.js +0 -13
@@ -3,17 +3,27 @@
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import * as React from 'react';
6
- import { Popover, useMergedRefs, SvgCaretRightSmall } from '../utils/index.js';
6
+ import {
7
+ SvgCaretRightSmall,
8
+ usePopover,
9
+ Portal,
10
+ useMergedRefs,
11
+ useId,
12
+ } from '../utils/index.js';
7
13
  import { Menu } from './Menu.js';
8
14
  import { ListItem } from '../List/ListItem.js';
15
+ import { flushSync } from 'react-dom';
9
16
  /**
10
17
  * Context used to provide menu item ref to sub-menu items.
11
18
  */
12
- const MenuItemContext = React.createContext({ ref: undefined });
19
+ const MenuItemContext = React.createContext({
20
+ ref: undefined,
21
+ setIsNestedSubmenuVisible: () => {},
22
+ });
13
23
  /**
14
24
  * Basic menu item component. Should be used inside `Menu` component for each item.
15
25
  */
16
- export const MenuItem = React.forwardRef((props, ref) => {
26
+ export const MenuItem = React.forwardRef((props, forwardedRef) => {
17
27
  const {
18
28
  children,
19
29
  isSelected,
@@ -22,21 +32,33 @@ export const MenuItem = React.forwardRef((props, ref) => {
22
32
  onClick,
23
33
  sublabel,
24
34
  size = !!sublabel ? 'large' : 'default',
25
- startIcon: startIconProp,
26
35
  icon,
27
- endIcon: endIconProp,
36
+ startIcon = icon,
28
37
  badge,
38
+ endIcon = badge,
29
39
  role = 'menuitem',
30
40
  subMenuItems = [],
31
41
  ...rest
32
42
  } = props;
33
43
  const menuItemRef = React.useRef(null);
34
- const refs = useMergedRefs(menuItemRef, ref);
35
- const { ref: parentMenuItemRef } = React.useContext(MenuItemContext);
36
- const subMenuRef = React.useRef(null);
44
+ const [focusOnSubmenu, setFocusOnSubmenu] = React.useState(false);
45
+ const submenuId = useId();
37
46
  const [isSubmenuVisible, setIsSubmenuVisible] = React.useState(false);
38
- const startIcon = startIconProp ?? icon;
39
- const endIcon = endIconProp ?? badge;
47
+ const [isNestedSubmenuVisible, setIsNestedSubmenuVisible] =
48
+ React.useState(false);
49
+ const parent = React.useContext(MenuItemContext);
50
+ const onVisibleChange = (open) => {
51
+ setIsSubmenuVisible(open);
52
+ // we don't want parent to close when mouse goes into a nested submenu,
53
+ // so we need to let the parent know whether the submenu is still open.
54
+ parent.setIsNestedSubmenuVisible(open);
55
+ };
56
+ const popover = usePopover({
57
+ visible: isSubmenuVisible || isNestedSubmenuVisible,
58
+ onVisibleChange,
59
+ placement: 'right-start',
60
+ trigger: { hover: true, focus: true },
61
+ });
40
62
  const onKeyDown = (event) => {
41
63
  if (event.altKey) {
42
64
  return;
@@ -52,46 +74,59 @@ export const MenuItem = React.forwardRef((props, ref) => {
52
74
  case 'ArrowRight': {
53
75
  if (subMenuItems.length > 0) {
54
76
  setIsSubmenuVisible(true);
77
+ // flush and reset state so we are ready to focus again next time
78
+ flushSync(() => setFocusOnSubmenu(true));
79
+ setFocusOnSubmenu(false);
55
80
  event.preventDefault();
56
81
  event.stopPropagation();
57
82
  }
58
83
  break;
59
84
  }
60
85
  case 'ArrowLeft': {
61
- parentMenuItemRef?.current?.focus();
86
+ if (parent.ref) {
87
+ parent.ref.current?.focus();
88
+ parent.setIsNestedSubmenuVisible(false);
89
+ }
62
90
  event.stopPropagation();
63
91
  event.preventDefault();
64
92
  break;
65
93
  }
94
+ case 'Escape': {
95
+ // focus might get lost if submenu closes so move it back to parent
96
+ parent.ref?.current?.focus();
97
+ break;
98
+ }
66
99
  default:
67
100
  break;
68
101
  }
69
102
  };
70
- const listItem = React.createElement(
103
+ const handlers = {
104
+ onClick: () => !disabled && onClick?.(value),
105
+ onKeyDown,
106
+ };
107
+ return React.createElement(
71
108
  ListItem,
72
109
  {
110
+ as: 'div',
73
111
  actionable: true,
74
112
  size: size,
75
113
  active: isSelected,
76
114
  disabled: disabled,
77
- onClick: () => !disabled && onClick?.(value),
78
- ref: refs,
115
+ ref: useMergedRefs(
116
+ menuItemRef,
117
+ forwardedRef,
118
+ subMenuItems.length > 0 ? popover.refs.setReference : null,
119
+ ),
79
120
  role: role,
80
121
  tabIndex: disabled || role === 'presentation' ? undefined : -1,
81
122
  'aria-selected': isSelected,
82
- 'aria-haspopup': subMenuItems.length > 0,
123
+ 'aria-haspopup': subMenuItems.length > 0 ? 'true' : undefined,
124
+ 'aria-controls': subMenuItems.length > 0 ? submenuId : undefined,
125
+ 'aria-expanded': subMenuItems.length > 0 ? popover.open : undefined,
83
126
  'aria-disabled': disabled,
84
- onKeyDown: onKeyDown,
85
- onMouseEnter: () => setIsSubmenuVisible(true),
86
- onMouseLeave: (e) => {
87
- if (
88
- !(e.relatedTarget instanceof Node) ||
89
- !subMenuRef.current?.contains(e.relatedTarget)
90
- ) {
91
- setIsSubmenuVisible(false);
92
- }
93
- },
94
- ...rest,
127
+ ...(subMenuItems.length === 0
128
+ ? { ...handlers, ...rest }
129
+ : popover.getReferenceProps({ ...handlers, ...rest })),
95
130
  },
96
131
  startIcon &&
97
132
  React.createElement(
@@ -118,34 +153,32 @@ export const MenuItem = React.forwardRef((props, ref) => {
118
153
  { as: 'span', 'aria-hidden': true },
119
154
  endIcon,
120
155
  ),
121
- );
122
- return subMenuItems.length === 0
123
- ? listItem
124
- : React.createElement(
125
- MenuItemContext.Provider,
126
- { value: { ref: menuItemRef } },
156
+ subMenuItems.length > 0 &&
157
+ popover.open &&
158
+ React.createElement(
159
+ Portal,
160
+ null,
127
161
  React.createElement(
128
- Popover,
129
- {
130
- placement: 'right-start',
131
- visible: isSubmenuVisible,
132
- appendTo: 'parent',
133
- content: React.createElement(
134
- 'div',
135
- {
136
- onMouseLeave: () => setIsSubmenuVisible(false),
137
- onBlur: (e) => {
138
- !!(e.relatedTarget instanceof Node) &&
139
- !subMenuRef.current?.contains(e.relatedTarget) &&
140
- !subMenuRef.current?.isEqualNode(e.relatedTarget) &&
141
- setIsSubmenuVisible(false);
162
+ MenuItemContext.Provider,
163
+ { value: { ref: menuItemRef, setIsNestedSubmenuVisible } },
164
+ React.createElement(
165
+ Menu,
166
+ {
167
+ setFocus: focusOnSubmenu,
168
+ ref: popover.refs.setFloating,
169
+ ...popover.getFloatingProps({
170
+ id: submenuId,
171
+ onPointerMove: () => {
172
+ // pointer might move into a nested submenu and set isSubmenuVisible to false,
173
+ // so we need to flip it back to true when pointer re-enters this submenu.
174
+ setIsSubmenuVisible(true);
142
175
  },
143
- },
144
- React.createElement(Menu, { ref: subMenuRef }, subMenuItems),
145
- ),
146
- },
147
- listItem,
176
+ }),
177
+ },
178
+ subMenuItems,
179
+ ),
148
180
  ),
149
- );
181
+ ),
182
+ );
150
183
  });
151
184
  export default MenuItem;
@@ -26,5 +26,5 @@ type MenuItemSkeletonProps = {
26
26
  /**
27
27
  * Menu item that uses skeletons to indicate loading state.
28
28
  */
29
- export declare const MenuItemSkeleton: PolymorphicForwardRefComponent<"li", MenuItemSkeletonProps>;
29
+ export declare const MenuItemSkeleton: PolymorphicForwardRefComponent<"div", MenuItemSkeletonProps>;
30
30
  export default MenuItemSkeleton;
@@ -21,7 +21,6 @@ export const MenuItemSkeleton = React.forwardRef((props, forwardedRef) => {
21
21
  return React.createElement(
22
22
  Box,
23
23
  {
24
- as: 'li',
25
24
  className: cx('iui-menu-item-skeleton', className),
26
25
  'data-iui-size': hasSublabel && 'large',
27
26
  style: {
@@ -71,7 +71,7 @@ const SearchBoxComponent = React.forwardRef((props, ref) => {
71
71
  { 'iui-expandable-searchbox': expandable },
72
72
  className,
73
73
  ),
74
- 'data-iui-size': size,
74
+ size: size,
75
75
  isDisabled: isDisabled,
76
76
  'data-iui-expanded': isExpanded,
77
77
  ...rest,
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
- import type { PopoverProps, CommonProps } from '../utils/index.js';
2
+ import { usePopover } from '../utils/index.js';
3
+ import type { CommonProps } from '../utils/index.js';
3
4
  export type ItemRendererProps = {
4
5
  /**
5
6
  * Close handler that closes the dropdown.
@@ -93,6 +94,10 @@ export type SelectProps<T> = {
93
94
  * Modify size of select.
94
95
  */
95
96
  size?: 'small' | 'large';
97
+ /**
98
+ * Status of select.
99
+ */
100
+ status?: 'positive' | 'warning' | 'negative';
96
101
  /**
97
102
  * Custom renderer for an item in the dropdown list. `MenuItem` item props are going to be populated if not provided.
98
103
  */
@@ -106,15 +111,14 @@ export type SelectProps<T> = {
106
111
  */
107
112
  menuStyle?: React.CSSProperties;
108
113
  /**
109
- * Props to customize {@link Popover} behavior.
110
- * @see [tippy.js props](https://atomiks.github.io/tippyjs/v6/all-props/)
114
+ * Props to customize Popover behavior.
111
115
  */
112
- popoverProps?: Omit<PopoverProps, 'onShow' | 'onHide' | 'disabled'>;
116
+ popoverProps?: Pick<Parameters<typeof usePopover>[0], 'visible' | 'onVisibleChange' | 'placement' | 'matchWidth' | 'closeOnOutsideClick'>;
113
117
  /**
114
118
  * Props to pass to the select button (trigger) element.
115
119
  */
116
120
  triggerProps?: React.ComponentPropsWithoutRef<'div'>;
117
- } & SelectMultipleTypeProps<T> & Pick<PopoverProps, 'onShow' | 'onHide'> & Omit<React.ComponentPropsWithoutRef<'div'>, 'size' | 'disabled' | 'placeholder' | 'onChange'>;
121
+ } & SelectMultipleTypeProps<T> & Omit<React.ComponentPropsWithoutRef<'div'>, 'size' | 'disabled' | 'placeholder' | 'onChange'>;
118
122
  /**
119
123
  * Select component to select value from options.
120
124
  * Generic type is used for value. It prevents you from mistakenly using other types in `options`, `value` and `onChange`.
@@ -7,10 +7,14 @@ import cx from 'classnames';
7
7
  import { Menu, MenuItem } from '../Menu/index.js';
8
8
  import {
9
9
  SvgCaretDownSmall,
10
- Popover,
11
10
  useId,
12
11
  AutoclearingHiddenLiveRegion,
13
12
  Box,
13
+ usePopover,
14
+ Portal,
15
+ useMergedRefs,
16
+ SvgCheckmark,
17
+ Icon,
14
18
  } from '../utils/index.js';
15
19
  import SelectTag from './SelectTag.js';
16
20
  import SelectTagContainer from './SelectTagContainer.js';
@@ -85,55 +89,27 @@ export const Select = (props) => {
85
89
  style,
86
90
  menuClassName,
87
91
  menuStyle,
88
- onShow,
89
- onHide,
90
- popoverProps,
91
92
  multiple = false,
92
93
  triggerProps,
94
+ status,
95
+ popoverProps,
93
96
  ...rest
94
97
  } = props;
95
- const [isOpenState, setIsOpen] = React.useState(false);
96
- const isOpen = popoverProps?.visible ?? isOpenState;
97
- const [minWidth, setMinWidth] = React.useState(0);
98
+ const [isOpen, setIsOpen] = React.useState(false);
98
99
  const [liveRegionSelection, setLiveRegionSelection] = React.useState('');
99
100
  const selectRef = React.useRef(null);
100
- const toggleButtonRef = React.useRef(null);
101
- const onShowHandler = React.useCallback(
102
- (instance) => {
103
- setIsOpen(true);
104
- onShow?.(instance);
105
- },
106
- [onShow],
107
- );
108
- const onHideHandler = React.useCallback(
109
- (instance) => {
110
- setIsOpen(false);
111
- selectRef.current?.focus({ preventScroll: true }); // move focus back to select button
112
- onHide?.(instance);
113
- },
114
- [onHide],
115
- );
116
- React.useEffect(() => {
117
- if (selectRef.current) {
118
- setMinWidth(selectRef.current.offsetWidth);
119
- }
120
- }, [isOpen]);
121
- const onKeyDown = (event) => {
122
- if (event.altKey) {
101
+ const show = React.useCallback(() => {
102
+ if (disabled) {
123
103
  return;
124
104
  }
125
- switch (event.key) {
126
- case 'Enter':
127
- case ' ':
128
- case 'Spacebar': {
129
- setIsOpen((o) => !o);
130
- event.preventDefault();
131
- break;
132
- }
133
- default:
134
- break;
135
- }
136
- };
105
+ setIsOpen(true);
106
+ popoverProps?.onVisibleChange?.(true);
107
+ }, [disabled, popoverProps]);
108
+ const hide = React.useCallback(() => {
109
+ setIsOpen(false);
110
+ selectRef.current?.focus({ preventScroll: true }); // move focus back to select button
111
+ popoverProps?.onVisibleChange?.(false);
112
+ }, [popoverProps]);
137
113
  const menuItems = React.useMemo(() => {
138
114
  return options.map((option, index) => {
139
115
  const isSelected = isMultipleEnabled(value, multiple)
@@ -148,13 +124,16 @@ export const Select = (props) => {
148
124
  key: `${label}-${index}`,
149
125
  isSelected,
150
126
  startIcon: startIcon,
127
+ endIcon: isSelected
128
+ ? React.createElement(SvgCheckmark, { 'aria-hidden': true })
129
+ : null,
151
130
  onClick: () => {
152
131
  if (option.disabled) {
153
132
  return;
154
133
  }
155
134
  if (isSingleOnChange(onChange, multiple)) {
156
135
  onChange?.(option.value);
157
- setIsOpen(false);
136
+ hide();
158
137
  } else {
159
138
  onChange?.(option.value, isSelected ? 'removed' : 'added');
160
139
  }
@@ -183,7 +162,7 @@ export const Select = (props) => {
183
162
  ...menuItem.props,
184
163
  });
185
164
  });
186
- }, [itemRenderer, multiple, onChange, options, value]);
165
+ }, [hide, itemRenderer, multiple, onChange, options, value]);
187
166
  const selectedItems = React.useMemo(() => {
188
167
  if (value == null) {
189
168
  return undefined;
@@ -198,54 +177,39 @@ export const Select = (props) => {
198
177
  label: item.label,
199
178
  });
200
179
  }, []);
180
+ const popover = usePopover({
181
+ visible: isOpen,
182
+ matchWidth: true,
183
+ closeOnOutsideClick: true,
184
+ ...popoverProps,
185
+ onVisibleChange: (open) => (open ? show() : hide()),
186
+ });
201
187
  return React.createElement(
202
- Box,
203
- { className: cx('iui-input-with-icon', className), style: style, ...rest },
188
+ React.Fragment,
189
+ null,
204
190
  React.createElement(
205
- Popover,
191
+ Box,
206
192
  {
207
- content: React.createElement(
208
- Menu,
209
- {
210
- role: 'listbox',
211
- className: cx('iui-scroll', menuClassName),
212
- style: {
213
- minInlineSize: minWidth,
214
- maxInlineSize: `min(${minWidth * 2}px, 90vw)`,
215
- ...menuStyle,
216
- },
217
- id: `${uid}-menu`,
218
- key: `${uid}-menu`,
219
- },
220
- menuItems,
221
- ),
222
- placement: 'bottom-start',
223
- aria: { content: null },
224
- onShow: onShowHandler,
225
- onHide: onHideHandler,
226
- ...popoverProps,
227
- visible: isOpen,
228
- onClickOutside: (_, { target }) => {
229
- if (!toggleButtonRef.current?.contains(target)) {
230
- setIsOpen(false);
231
- }
232
- },
193
+ className: cx('iui-input-with-icon', className),
194
+ style: style,
195
+ ...rest,
196
+ ref: popover.refs.setPositionReference,
233
197
  },
234
198
  React.createElement(
235
199
  Box,
236
200
  {
201
+ ...popover.getReferenceProps(),
237
202
  tabIndex: 0,
238
203
  role: 'combobox',
239
- ref: selectRef,
240
204
  'data-iui-size': size,
241
- onClick: () => !disabled && setIsOpen((o) => !o),
242
- onKeyDown: (e) => !disabled && onKeyDown(e),
205
+ 'data-iui-status': status,
243
206
  'aria-disabled': disabled,
244
207
  'aria-autocomplete': 'none',
245
208
  'aria-expanded': isOpen,
246
209
  'aria-haspopup': 'listbox',
247
210
  'aria-controls': `${uid}-menu`,
248
211
  ...triggerProps,
212
+ ref: useMergedRefs(selectRef, popover.refs.setReference),
249
213
  className: cx(
250
214
  'iui-select-button',
251
215
  {
@@ -273,27 +237,48 @@ export const Select = (props) => {
273
237
  selectedItemRenderer: selectedItemRenderer,
274
238
  }),
275
239
  ),
240
+ React.createElement(
241
+ Icon,
242
+ {
243
+ as: 'span',
244
+ 'aria-hidden': true,
245
+ className: cx('iui-end-icon', {
246
+ 'iui-disabled': disabled,
247
+ 'iui-open': isOpen,
248
+ }),
249
+ },
250
+ React.createElement(SvgCaretDownSmall, null),
251
+ ),
252
+ multiple
253
+ ? React.createElement(AutoclearingHiddenLiveRegion, {
254
+ text: liveRegionSelection,
255
+ })
256
+ : null,
276
257
  ),
277
- React.createElement(
278
- Box,
279
- {
280
- as: 'span',
281
- 'aria-hidden': true,
282
- ref: toggleButtonRef,
283
- className: cx('iui-end-icon', {
284
- 'iui-actionable': !disabled,
285
- 'iui-disabled': disabled,
286
- 'iui-open': isOpen,
287
- }),
288
- onClick: () => !disabled && setIsOpen((o) => !o),
289
- },
290
- React.createElement(SvgCaretDownSmall, null),
291
- ),
292
- multiple
293
- ? React.createElement(AutoclearingHiddenLiveRegion, {
294
- text: liveRegionSelection,
295
- })
296
- : null,
258
+ popover.open &&
259
+ React.createElement(
260
+ Portal,
261
+ null,
262
+ React.createElement(
263
+ Menu,
264
+ {
265
+ role: 'listbox',
266
+ className: cx('iui-scroll', menuClassName),
267
+ id: `${uid}-menu`,
268
+ key: `${uid}-menu`,
269
+ ...popover.getFloatingProps({
270
+ style: menuStyle,
271
+ onKeyDown: ({ key }) => {
272
+ if (key === 'Tab') {
273
+ hide();
274
+ }
275
+ },
276
+ }),
277
+ ref: popover.refs.setFloating,
278
+ },
279
+ menuItems,
280
+ ),
281
+ ),
297
282
  );
298
283
  };
299
284
  const SingleSelectButton = ({ selectedItem, selectedItemRenderer }) => {
@@ -44,6 +44,8 @@ export const SideNavigation = React.forwardRef((props, forwardedRef) => {
44
44
  const ExpandButton = React.createElement(
45
45
  IconButton,
46
46
  {
47
+ label: 'Toggle icon labels',
48
+ 'aria-expanded': _isExpanded,
47
49
  className: 'iui-sidenav-button iui-expand',
48
50
  onClick: React.useCallback(() => {
49
51
  _setIsExpanded((expanded) => !expanded);
@@ -88,6 +88,7 @@ export const Thumb = (props) => {
88
88
  {
89
89
  placement: 'top',
90
90
  autoUpdateOptions: { animationFrame: true },
91
+ ariaStrategy: 'none',
91
92
  ...tooltipProps,
92
93
  },
93
94
  React.createElement(Box, {
@@ -1,4 +1,6 @@
1
1
  import * as React from 'react';
2
+ import { Icon } from '../utils/index.js';
3
+ import type { PolymorphicForwardRefComponent } from '../utils/index.js';
2
4
  type StatusMessageProps = {
3
5
  /**
4
6
  * Custom icon to be displayed at the beginning.
@@ -13,12 +15,20 @@ type StatusMessageProps = {
13
15
  * Status of the message.
14
16
  */
15
17
  status?: 'positive' | 'warning' | 'negative';
18
+ /**
19
+ * Passes props to icon
20
+ */
21
+ iconProps?: React.ComponentProps<typeof Icon>;
22
+ /**
23
+ * Passes props to content
24
+ */
25
+ contentProps?: React.ComponentPropsWithRef<'div'>;
16
26
  };
17
27
  /**
18
- * Component to display icon and text below the `Combobox` component.
28
+ * Component to display icon and text below the form field.
19
29
  * @example
20
30
  * <StatusMessage>This is the text</StatusMessage>
21
31
  * <StatusMessage startIcon={<SvgStar />}>This is the text</StatusMessage>
22
32
  */
23
- export declare const StatusMessage: ({ startIcon: userStartIcon, children, status, }: StatusMessageProps) => React.JSX.Element;
33
+ export declare const StatusMessage: PolymorphicForwardRefComponent<"div", StatusMessageProps>;
24
34
  export default StatusMessage;
@@ -3,30 +3,37 @@
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import * as React from 'react';
6
- import { Box, StatusIconMap } from '../utils/index.js';
6
+ import { Box, StatusIconMap, Icon } from '../utils/index.js';
7
+ import cx from 'classnames';
7
8
  /**
8
- * Component to display icon and text below the `Combobox` component.
9
+ * Component to display icon and text below the form field.
9
10
  * @example
10
11
  * <StatusMessage>This is the text</StatusMessage>
11
12
  * <StatusMessage startIcon={<SvgStar />}>This is the text</StatusMessage>
12
13
  */
13
- export const StatusMessage = ({
14
- startIcon: userStartIcon,
15
- children,
16
- status,
17
- }) => {
14
+ export const StatusMessage = React.forwardRef((props, ref) => {
15
+ const {
16
+ children,
17
+ startIcon: userStartIcon,
18
+ status,
19
+ className,
20
+ iconProps,
21
+ contentProps,
22
+ ...rest
23
+ } = props;
18
24
  const icon = userStartIcon ?? (status && StatusIconMap[status]());
19
25
  return React.createElement(
20
- React.Fragment,
21
- null,
26
+ Box,
27
+ {
28
+ className: cx('iui-status-message', className),
29
+ 'data-iui-status': status,
30
+ ref: ref,
31
+ ...rest,
32
+ },
22
33
  !!icon
23
- ? React.createElement(
24
- Box,
25
- { as: 'span', className: 'iui-input-icon', 'aria-hidden': true },
26
- icon,
27
- )
34
+ ? React.createElement(Icon, { 'aria-hidden': true, ...iconProps }, icon)
28
35
  : null,
29
- React.createElement(Box, { className: 'iui-message' }, children),
36
+ React.createElement(Box, { ...contentProps }, children),
30
37
  );
31
- };
38
+ });
32
39
  export default StatusMessage;
@@ -15,6 +15,8 @@ export const SubRowExpander = (props) => {
15
15
  : React.createElement(
16
16
  IconButton,
17
17
  {
18
+ 'aria-label': 'Toggle sub row',
19
+ 'aria-expanded': cell.row.isExpanded ? 'true' : 'false',
18
20
  style: {
19
21
  marginInlineEnd:
20
22
  density === 'default' || density === undefined ? 8 : 4,
@@ -105,13 +105,9 @@ export const ActionColumn = ({ columnManager = false } = {}) => {
105
105
  {
106
106
  ...dropdownMenuProps,
107
107
  menuItems: headerCheckBoxes,
108
- onHide: (i) => {
109
- setIsOpen(false);
110
- dropdownMenuProps.onHide?.(i);
111
- },
112
- onShow: (i) => {
113
- setIsOpen(true);
114
- dropdownMenuProps.onShow?.(i);
108
+ onVisibleChange: (open) => {
109
+ setIsOpen(open);
110
+ dropdownMenuProps?.onVisibleChange?.(open);
115
111
  },
116
112
  className: cx('iui-scroll', dropdownMenuProps.className),
117
113
  },
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react';
2
+ import type { LabeledInput } from '../../../LabeledInput/index.js';
2
3
  import type { PolymorphicForwardRefComponent } from '../../../utils/index.js';
3
- import { LabeledInput } from '../../../LabeledInput/index.js';
4
+ import type { DatePickerLocalizedNames } from '../../../DatePicker/DatePicker.js';
4
5
  export type DatePickerInputProps = {
5
6
  date?: Date;
6
7
  onChange: (date?: Date) => void;
@@ -14,6 +15,10 @@ export type DatePickerInputProps = {
14
15
  * The 'to' date for the 'from' DatePickerInput or the 'from' date for the 'to' DatePickerInput
15
16
  */
16
17
  selectedDate?: Date;
18
+ /**
19
+ * Months, short days and days localized names for DatePicker
20
+ */
21
+ localizedNames?: DatePickerLocalizedNames;
17
22
  } & Omit<React.ComponentProps<typeof LabeledInput>, 'value' | 'onChange' | 'svgIcon' | 'displayStyle'>;
18
23
  declare const DatePickerInput: PolymorphicForwardRefComponent<"input", DatePickerInputProps>;
19
24
  export default DatePickerInput;