@itwin/itwinui-react 1.30.0 → 1.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/cjs/core/Badge/Badge.js +2 -2
  3. package/cjs/core/Checkbox/Checkbox.d.ts +13 -0
  4. package/cjs/core/Checkbox/Checkbox.js +15 -22
  5. package/cjs/core/ColorPicker/ColorBuilder.js +7 -8
  6. package/cjs/core/ColorPicker/ColorSwatch.d.ts +1 -1
  7. package/cjs/core/ColorPicker/ColorSwatch.js +2 -2
  8. package/cjs/core/ComboBox/ComboBox.d.ts +11 -0
  9. package/cjs/core/ComboBox/ComboBox.js +93 -55
  10. package/cjs/core/ExpandableBlock/ExpandableBlock.d.ts +6 -0
  11. package/cjs/core/ExpandableBlock/ExpandableBlock.js +3 -2
  12. package/cjs/core/Menu/Menu.js +3 -3
  13. package/cjs/core/Menu/MenuItem.js +1 -1
  14. package/cjs/core/Radio/Radio.d.ts +13 -0
  15. package/cjs/core/Radio/Radio.js +7 -8
  16. package/cjs/core/Select/Select.js +23 -8
  17. package/cjs/core/Table/TablePaginator.js +7 -9
  18. package/cjs/core/Table/filters/DateRangeFilter/DatePickerInput.js +1 -1
  19. package/cjs/core/Tile/Tile.js +4 -4
  20. package/cjs/core/Tree/Tree.d.ts +123 -0
  21. package/cjs/core/Tree/Tree.js +177 -0
  22. package/cjs/core/Tree/TreeContext.d.ts +25 -0
  23. package/cjs/core/Tree/TreeContext.js +20 -0
  24. package/cjs/core/Tree/TreeNode.d.ts +87 -0
  25. package/cjs/core/Tree/TreeNode.js +169 -0
  26. package/cjs/core/Tree/TreeNodeExpander.d.ts +8 -0
  27. package/cjs/core/Tree/TreeNodeExpander.js +46 -0
  28. package/cjs/core/Tree/index.d.ts +6 -0
  29. package/cjs/core/Tree/index.js +13 -0
  30. package/cjs/core/index.d.ts +2 -0
  31. package/cjs/core/index.js +5 -1
  32. package/cjs/core/utils/components/Popover.js +1 -1
  33. package/cjs/core/utils/functions/focusable.js +6 -2
  34. package/esm/core/Badge/Badge.js +2 -2
  35. package/esm/core/Checkbox/Checkbox.d.ts +13 -0
  36. package/esm/core/Checkbox/Checkbox.js +15 -22
  37. package/esm/core/ColorPicker/ColorBuilder.js +7 -8
  38. package/esm/core/ColorPicker/ColorSwatch.d.ts +1 -1
  39. package/esm/core/ColorPicker/ColorSwatch.js +2 -2
  40. package/esm/core/ComboBox/ComboBox.d.ts +11 -0
  41. package/esm/core/ComboBox/ComboBox.js +94 -56
  42. package/esm/core/ExpandableBlock/ExpandableBlock.d.ts +6 -0
  43. package/esm/core/ExpandableBlock/ExpandableBlock.js +3 -2
  44. package/esm/core/Menu/Menu.js +3 -3
  45. package/esm/core/Menu/MenuItem.js +1 -1
  46. package/esm/core/Radio/Radio.d.ts +13 -0
  47. package/esm/core/Radio/Radio.js +7 -8
  48. package/esm/core/Select/Select.js +23 -8
  49. package/esm/core/Table/TablePaginator.js +7 -9
  50. package/esm/core/Table/filters/DateRangeFilter/DatePickerInput.js +1 -1
  51. package/esm/core/Tile/Tile.js +4 -4
  52. package/esm/core/Tree/Tree.d.ts +123 -0
  53. package/esm/core/Tree/Tree.js +170 -0
  54. package/esm/core/Tree/TreeContext.d.ts +25 -0
  55. package/esm/core/Tree/TreeContext.js +13 -0
  56. package/esm/core/Tree/TreeNode.d.ts +87 -0
  57. package/esm/core/Tree/TreeNode.js +162 -0
  58. package/esm/core/Tree/TreeNodeExpander.d.ts +8 -0
  59. package/esm/core/Tree/TreeNodeExpander.js +39 -0
  60. package/esm/core/Tree/index.d.ts +6 -0
  61. package/esm/core/Tree/index.js +7 -0
  62. package/esm/core/index.d.ts +2 -0
  63. package/esm/core/index.js +1 -0
  64. package/esm/core/utils/components/Popover.js +1 -1
  65. package/esm/core/utils/functions/focusable.js +6 -2
  66. package/package.json +2 -2
@@ -29,7 +29,7 @@ import cx from 'classnames';
29
29
  import { Input } from '../Input';
30
30
  import { Menu, MenuExtraContent, MenuItem } from '../Menu';
31
31
  import { Text } from '../Typography';
32
- import { InputContainer, useTheme, Popover, getFocusableElements, getRandomValue, } from '../utils';
32
+ import { InputContainer, useTheme, Popover, getFocusableElements, getRandomValue, mergeRefs, } from '../utils';
33
33
  import SvgCaretDownSmall from '@itwin/itwinui-icons-react/cjs/icons/CaretDownSmall';
34
34
  import 'tippy.js/animations/shift-away.css';
35
35
  /**
@@ -46,7 +46,7 @@ import 'tippy.js/animations/shift-away.css';
46
46
  * />
47
47
  */
48
48
  export var ComboBox = function (props) {
49
- var options = props.options, value = props.value, onChange = props.onChange, filterFunction = props.filterFunction, className = props.className, inputProps = props.inputProps, dropdownMenuProps = props.dropdownMenuProps, _a = props.emptyStateMessage, emptyStateMessage = _a === void 0 ? 'No options found' : _a, rest = __rest(props, ["options", "value", "onChange", "filterFunction", "className", "inputProps", "dropdownMenuProps", "emptyStateMessage"]);
49
+ var options = props.options, value = props.value, onChange = props.onChange, filterFunction = props.filterFunction, className = props.className, inputProps = props.inputProps, dropdownMenuProps = props.dropdownMenuProps, _a = props.emptyStateMessage, emptyStateMessage = _a === void 0 ? 'No options found' : _a, itemRenderer = props.itemRenderer, rest = __rest(props, ["options", "value", "onChange", "filterFunction", "className", "inputProps", "dropdownMenuProps", "emptyStateMessage", "itemRenderer"]);
50
50
  // Generate a stateful random id if not specified
51
51
  var id = React.useState(function () {
52
52
  var _a, _b;
@@ -61,16 +61,31 @@ export var ComboBox = function (props) {
61
61
  return value === options[index].value;
62
62
  });
63
63
  }, [options, id]);
64
+ var userOnChange = React.useRef(onChange);
64
65
  var memoizedItems = React.useMemo(function () {
65
- return options.map(function (_a, index) {
66
- var label = _a.label, value = _a.value, rest = __rest(_a, ["label", "value"]);
67
- return (React.createElement(MenuItem, __assign({ id: getOptionId(index), key: getOptionId(index), value: value, role: 'option', onClick: function (value) {
66
+ return options.map(function (option, index) {
67
+ var label = option.label, value = option.value, rest = __rest(option, ["label", "value"]);
68
+ var additionalProps = {
69
+ value: value,
70
+ role: 'option',
71
+ onClick: function () {
72
+ var _a;
68
73
  setSelectedValue(value);
69
- onChange === null || onChange === void 0 ? void 0 : onChange(value);
74
+ (_a = userOnChange.current) === null || _a === void 0 ? void 0 : _a.call(userOnChange, value);
70
75
  setIsOpen(false);
71
- } }, rest), label));
76
+ },
77
+ };
78
+ if (itemRenderer) {
79
+ return React.cloneElement(itemRenderer(option, {
80
+ id: getOptionId(index),
81
+ index: index,
82
+ isSelected: false,
83
+ isFocused: false,
84
+ }), additionalProps);
85
+ }
86
+ return (React.createElement(MenuItem, __assign({ id: getOptionId(index), key: getOptionId(index) }, additionalProps, rest), label));
72
87
  });
73
- }, [options, getOptionId, onChange]);
88
+ }, [options, getOptionId, itemRenderer]);
74
89
  var inputRef = React.useRef(null);
75
90
  var menuRef = React.useRef(null);
76
91
  var toggleButtonRef = React.useRef(null);
@@ -149,6 +164,7 @@ export var ComboBox = function (props) {
149
164
  });
150
165
  }, [inputValue, options, selectedValue, isOpen, filterFunction]);
151
166
  var onKeyDown = React.useCallback(function (event) {
167
+ var _a;
152
168
  var focusableOptions = getFocusableElements(menuRef.current);
153
169
  var focusedIndexInFilteredList = focusableOptions.findIndex(function (_a) {
154
170
  var _b;
@@ -182,7 +198,7 @@ export var ComboBox = function (props) {
182
198
  case 'Enter':
183
199
  if (isOpen) {
184
200
  setSelectedValue(options[focusedIndex].value);
185
- onChange === null || onChange === void 0 ? void 0 : onChange(options[focusedIndex].value);
201
+ (_a = userOnChange.current) === null || _a === void 0 ? void 0 : _a.call(userOnChange, options[focusedIndex].value);
186
202
  }
187
203
  setIsOpen(function (open) { return !open; });
188
204
  event.preventDefault();
@@ -202,26 +218,41 @@ export var ComboBox = function (props) {
202
218
  }
203
219
  break;
204
220
  }
205
- }, [focusedIndex, isOpen, options, getOptionId, onChange]);
221
+ }, [focusedIndex, isOpen, options, getOptionId]);
206
222
  var menuItems = React.useMemo(function () {
207
223
  if (filteredOptions.length === 0) {
208
- return (React.createElement(MenuExtraContent, null,
209
- React.createElement(Text, { isMuted: true }, emptyStateMessage)));
224
+ return [
225
+ React.createElement(MenuExtraContent, { key: 0 },
226
+ React.createElement(Text, { isMuted: true }, emptyStateMessage)),
227
+ ];
210
228
  }
211
229
  return filteredOptions.map(function (option) {
230
+ var _a;
212
231
  var index = options.findIndex(function (_a) {
213
232
  var value = _a.value;
214
233
  return option.value === value;
215
234
  });
216
235
  if (index < 0) {
217
- return;
236
+ return React.createElement(React.Fragment, null);
218
237
  }
219
- if (selectedValue === option.value || focusedIndex === index) {
220
- return React.cloneElement(memoizedItems[index], {
221
- isSelected: selectedValue === option.value,
222
- className: cx({ 'iui-focused': focusedIndex === index }),
223
- ref: function (el) {
224
- return focusedIndex === index && (el === null || el === void 0 ? void 0 : el.scrollIntoView(false));
238
+ var id = getOptionId(index);
239
+ var isSelected = selectedValue === option.value;
240
+ var isFocused = focusedIndex === index;
241
+ var focusScrollRef = function (el) {
242
+ return isFocused && (el === null || el === void 0 ? void 0 : el.scrollIntoView({ block: 'nearest' }));
243
+ };
244
+ if (isSelected || isFocused) {
245
+ var item = (_a = itemRenderer === null || itemRenderer === void 0 ? void 0 : itemRenderer(option, { index: index, id: id, isSelected: isSelected, isFocused: isFocused })) !== null && _a !== void 0 ? _a : React.cloneElement(memoizedItems[index], { isSelected: isSelected });
246
+ return React.cloneElement(item, {
247
+ className: cx({ 'iui-focused': isFocused }, item.props.className),
248
+ ref: mergeRefs(focusScrollRef, item.props.ref),
249
+ value: option.value,
250
+ role: 'option',
251
+ onClick: function () {
252
+ var _a;
253
+ setSelectedValue(option.value);
254
+ (_a = userOnChange.current) === null || _a === void 0 ? void 0 : _a.call(userOnChange, option.value);
255
+ setIsOpen(false);
225
256
  },
226
257
  });
227
258
  }
@@ -231,45 +262,52 @@ export var ComboBox = function (props) {
231
262
  filteredOptions,
232
263
  emptyStateMessage,
233
264
  options,
234
- focusedIndex,
265
+ getOptionId,
235
266
  selectedValue,
267
+ focusedIndex,
268
+ itemRenderer,
236
269
  memoizedItems,
237
270
  ]);
238
- return (React.createElement(InputContainer, __assign({ className: className, isIconInline: true, icon: React.useMemo(function () { return (React.createElement("span", { ref: toggleButtonRef, className: cx({
239
- 'iui-actionable': !(inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled),
240
- 'iui-open': isOpen,
241
- }), onClick: function () {
242
- var _a;
243
- if (isOpen) {
244
- setIsOpen(false);
245
- }
246
- else {
247
- (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
248
- }
249
- } },
250
- React.createElement(SvgCaretDownSmall, { "aria-hidden": true }))); }, [inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled, isOpen]) }, rest, { id: id }),
251
- React.createElement(Popover, __assign({ placement: 'bottom-start', visible: isOpen, onClickOutside: function (_, _a) {
252
- var _b;
253
- var target = _a.target;
254
- if (!((_b = toggleButtonRef.current) === null || _b === void 0 ? void 0 : _b.contains(target))) {
255
- setIsOpen(false);
256
- }
257
- }, animation: 'shift-away', duration: 200 }, dropdownMenuProps, { content: React.createElement(Menu, { id: id + "-list", className: 'iui-scroll', style: {
258
- minWidth: minWidth,
259
- maxWidth: "min(" + minWidth * 2 + "px, 90vw)",
260
- maxHeight: 300,
261
- }, setFocus: false, role: 'listbox', ref: menuRef }, menuItems), onHide: function (instance) {
262
- var _a;
263
- var selectedIndex = options.findIndex(function (_a) {
264
- var value = _a.value;
265
- return value === selectedValue;
266
- });
267
- setFocusedIndex(selectedIndex);
268
- if (selectedIndex > -1) {
269
- setInputValue(options[selectedIndex].label); // update input value to be same as selected value
270
- }
271
- (_a = dropdownMenuProps === null || dropdownMenuProps === void 0 ? void 0 : dropdownMenuProps.onHide) === null || _a === void 0 ? void 0 : _a.call(dropdownMenuProps, instance);
272
- } }),
273
- React.createElement(Input, __assign({ ref: inputRef, onKeyDown: onKeyDown, onFocus: function () { return setIsOpen(true); }, onChange: onInput, value: inputValue, "aria-activedescendant": isOpen && focusedIndex > -1 ? getOptionId(focusedIndex) : undefined, role: 'combobox', "aria-controls": isOpen ? id + "-list" : undefined, "aria-autocomplete": 'list', spellCheck: false, autoCapitalize: 'none', autoCorrect: 'off' }, inputProps)))));
271
+ return (React.createElement(InputContainer, __assign({ className: className, isIconInline: true }, rest, { id: id }),
272
+ React.createElement("div", { className: 'iui-input-with-icon' },
273
+ React.createElement(Popover, __assign({ placement: 'bottom-start', visible: isOpen, onClickOutside: function (_, _a) {
274
+ var _b;
275
+ var target = _a.target;
276
+ if (!((_b = toggleButtonRef.current) === null || _b === void 0 ? void 0 : _b.contains(target))) {
277
+ setIsOpen(false);
278
+ }
279
+ }, animation: 'shift-away', duration: 200 }, dropdownMenuProps, { content: React.createElement(Menu, { id: id + "-list", className: 'iui-scroll', style: {
280
+ minWidth: minWidth,
281
+ maxWidth: "min(" + minWidth * 2 + "px, 90vw)",
282
+ maxHeight: 300,
283
+ }, setFocus: false, role: 'listbox', ref: menuRef }, menuItems), onHide: function (instance) {
284
+ var _a;
285
+ var selectedIndex = options.findIndex(function (_a) {
286
+ var value = _a.value;
287
+ return value === selectedValue;
288
+ });
289
+ setFocusedIndex(selectedIndex);
290
+ if (selectedIndex > -1) {
291
+ setInputValue(options[selectedIndex].label); // update input value to be same as selected value
292
+ }
293
+ (_a = dropdownMenuProps === null || dropdownMenuProps === void 0 ? void 0 : dropdownMenuProps.onHide) === null || _a === void 0 ? void 0 : _a.call(dropdownMenuProps, instance);
294
+ } }),
295
+ React.createElement(Input, __assign({ ref: inputRef, onKeyDown: onKeyDown, onFocus: function () { return setIsOpen(true); }, onChange: onInput, value: inputValue, "aria-activedescendant": isOpen && focusedIndex > -1
296
+ ? getOptionId(focusedIndex)
297
+ : undefined, role: 'combobox', "aria-controls": isOpen ? id + "-list" : undefined, "aria-autocomplete": 'list', spellCheck: false, autoCapitalize: 'none', autoCorrect: 'off' }, inputProps))),
298
+ React.createElement("span", { ref: toggleButtonRef, className: cx('iui-end-icon', {
299
+ 'iui-actionable': !(inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled),
300
+ 'iui-disabled': inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled,
301
+ 'iui-open': isOpen,
302
+ }), onClick: function () {
303
+ var _a;
304
+ if (isOpen) {
305
+ setIsOpen(false);
306
+ }
307
+ else {
308
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
309
+ }
310
+ } },
311
+ React.createElement(SvgCaretDownSmall, { "aria-hidden": true })))));
274
312
  };
275
313
  export default ComboBox;
@@ -38,6 +38,12 @@ export declare type ExpandableBlockProps = {
38
38
  * @default 'default'
39
39
  */
40
40
  size?: 'default' | 'small';
41
+ /**
42
+ * Style of the ExpandableBlock.
43
+ * Use 'borderless' to hide outline.
44
+ * @default 'default'
45
+ */
46
+ styleType?: 'default' | 'borderless';
41
47
  } & Omit<CommonProps, 'title'>;
42
48
  /**
43
49
  * Container that allows content to be hidden behind a brief title and a caption.
@@ -40,10 +40,10 @@ import '@itwin/itwinui-css/css/expandable-block.css';
40
40
  */
41
41
  export var ExpandableBlock = function (props) {
42
42
  var _a;
43
- var caption = props.caption, children = props.children, className = props.className, title = props.title, onToggle = props.onToggle, style = props.style, _b = props.isExpanded, isExpanded = _b === void 0 ? false : _b, endIcon = props.endIcon, status = props.status, _c = props.size, size = _c === void 0 ? 'default' : _c, rest = __rest(props, ["caption", "children", "className", "title", "onToggle", "style", "isExpanded", "endIcon", "status", "size"]);
43
+ var caption = props.caption, children = props.children, className = props.className, title = props.title, onToggle = props.onToggle, style = props.style, _b = props.isExpanded, isExpanded = _b === void 0 ? false : _b, endIcon = props.endIcon, status = props.status, _c = props.size, size = _c === void 0 ? 'default' : _c, _d = props.styleType, styleType = _d === void 0 ? 'default' : _d, rest = __rest(props, ["caption", "children", "className", "title", "onToggle", "style", "isExpanded", "endIcon", "status", "size", "styleType"]);
44
44
  useTheme();
45
45
  var icon = endIcon !== null && endIcon !== void 0 ? endIcon : (status && StatusIconMap[status]());
46
- var _d = React.useState(isExpanded), expanded = _d[0], setExpanded = _d[1];
46
+ var _e = React.useState(isExpanded), expanded = _e[0], setExpanded = _e[1];
47
47
  React.useEffect(function () {
48
48
  setExpanded(isExpanded);
49
49
  }, [isExpanded]);
@@ -62,6 +62,7 @@ export var ExpandableBlock = function (props) {
62
62
  'iui-with-caption': !!caption,
63
63
  'iui-expanded': expanded,
64
64
  'iui-small': size === 'small',
65
+ 'iui-borderless': styleType === 'borderless',
65
66
  }, className), style: style }, rest),
66
67
  React.createElement("div", { "aria-expanded": expanded, className: 'iui-header', tabIndex: 0, onClick: handleToggle, onKeyDown: onKeyDown },
67
68
  React.createElement(SvgChevronRight, { className: 'iui-icon', "aria-hidden": true }),
@@ -37,6 +37,9 @@ export var Menu = React.forwardRef(function (props, ref) {
37
37
  var _c = React.useState(), focusedIndex = _c[0], setFocusedIndex = _c[1];
38
38
  var menuRef = React.useRef(null);
39
39
  var refs = useMergedRefs(menuRef, ref);
40
+ React.useEffect(function () {
41
+ setFocusedIndex(null);
42
+ }, [children]);
40
43
  React.useEffect(function () {
41
44
  var _a;
42
45
  var items = getFocusableElements(menuRef.current);
@@ -49,9 +52,6 @@ export var Menu = React.forwardRef(function (props, ref) {
49
52
  setFocusedIndex(selectedIndex > -1 ? selectedIndex : 0);
50
53
  }
51
54
  }, [setFocus, focusedIndex]);
52
- React.useEffect(function () {
53
- setFocusedIndex(null);
54
- }, [children]);
55
55
  var onKeyDown = function (event) {
56
56
  var items = getFocusableElements(menuRef.current);
57
57
  if (!(items === null || items === void 0 ? void 0 : items.length)) {
@@ -100,7 +100,7 @@ export var MenuItem = React.forwardRef(function (props, ref) {
100
100
  className: cx(badge.props.className, 'iui-icon'),
101
101
  })));
102
102
  return subMenuItems.length === 0 ? (listItem) : (React.createElement(MenuItemContext.Provider, { value: { ref: menuItemRef } },
103
- React.createElement(Popover, { placement: 'right-start', visible: isSubmenuVisible, content: React.createElement("div", { onMouseLeave: function () { return setIsSubmenuVisible(false); }, onBlur: function (e) {
103
+ React.createElement(Popover, { placement: 'right-start', visible: isSubmenuVisible, appendTo: 'parent', content: React.createElement("div", { onMouseLeave: function () { return setIsSubmenuVisible(false); }, onBlur: function (e) {
104
104
  var _a, _b;
105
105
  !!(e.relatedTarget instanceof Node) &&
106
106
  !((_a = subMenuRef.current) === null || _a === void 0 ? void 0 : _a.contains(e.relatedTarget)) &&
@@ -11,10 +11,16 @@ export declare type RadioProps = {
11
11
  status?: 'positive' | 'warning' | 'negative';
12
12
  /**
13
13
  * Custom CSS class name for the checkmark element.
14
+ *
15
+ * @deprecated As of 1.32.0, this is applied on the actual radio `<input>` element.
16
+ * The checkmark has been moved into a pseudo-element.
14
17
  */
15
18
  checkmarkClassName?: string;
16
19
  /**
17
20
  * Custom CSS Style for the checkmark element.
21
+ *
22
+ * @deprecated As of 1.32.0, this is applied on the actual radio `<input>` element.
23
+ * The checkmark has been moved into a pseudo-element.
18
24
  */
19
25
  checkmarkStyle?: React.CSSProperties;
20
26
  /**
@@ -26,6 +32,7 @@ export declare type RadioProps = {
26
32
  /**
27
33
  * Basic radio input component
28
34
  * @example
35
+ * <Radio />
29
36
  * <Radio label='Radio' />
30
37
  * <Radio disabled={true} label='Radio' />
31
38
  * <Radio status='positive' label='Positive' />
@@ -43,10 +50,16 @@ export declare const Radio: React.ForwardRefExoticComponent<{
43
50
  status?: "positive" | "warning" | "negative" | undefined;
44
51
  /**
45
52
  * Custom CSS class name for the checkmark element.
53
+ *
54
+ * @deprecated As of 1.32.0, this is applied on the actual radio `<input>` element.
55
+ * The checkmark has been moved into a pseudo-element.
46
56
  */
47
57
  checkmarkClassName?: string | undefined;
48
58
  /**
49
59
  * Custom CSS Style for the checkmark element.
60
+ *
61
+ * @deprecated As of 1.32.0, this is applied on the actual radio `<input>` element.
62
+ * The checkmark has been moved into a pseudo-element.
50
63
  */
51
64
  checkmarkStyle?: React.CSSProperties | undefined;
52
65
  /**
@@ -31,6 +31,7 @@ import '@itwin/itwinui-css/css/inputs.css';
31
31
  /**
32
32
  * Basic radio input component
33
33
  * @example
34
+ * <Radio />
34
35
  * <Radio label='Radio' />
35
36
  * <Radio disabled={true} label='Radio' />
36
37
  * <Radio status='positive' label='Positive' />
@@ -38,8 +39,8 @@ import '@itwin/itwinui-css/css/inputs.css';
38
39
  * <Radio status='negative' label='Negative' />
39
40
  */
40
41
  export var Radio = React.forwardRef(function (props, ref) {
41
- var _a;
42
- var className = props.className, _b = props.disabled, disabled = _b === void 0 ? false : _b, label = props.label, status = props.status, style = props.style, checkmarkClassName = props.checkmarkClassName, checkmarkStyle = props.checkmarkStyle, _c = props.setFocus, setFocus = _c === void 0 ? false : _c, rest = __rest(props, ["className", "disabled", "label", "status", "style", "checkmarkClassName", "checkmarkStyle", "setFocus"]);
42
+ var _a, _b;
43
+ var className = props.className, _c = props.disabled, disabled = _c === void 0 ? false : _c, label = props.label, status = props.status, style = props.style, checkmarkClassName = props.checkmarkClassName, checkmarkStyle = props.checkmarkStyle, _d = props.setFocus, setFocus = _d === void 0 ? false : _d, rest = __rest(props, ["className", "disabled", "label", "status", "style", "checkmarkClassName", "checkmarkStyle", "setFocus"]);
43
44
  useTheme();
44
45
  var inputElementRef = React.useRef(null);
45
46
  var refs = useMergedRefs(inputElementRef, ref);
@@ -48,11 +49,9 @@ export var Radio = React.forwardRef(function (props, ref) {
48
49
  inputElementRef.current.focus();
49
50
  }
50
51
  }, [setFocus]);
51
- return (React.createElement("label", { className: cx('iui-radio', (_a = { 'iui-disabled': disabled }, _a["iui-" + status] = !!status, _a), className), style: style },
52
- React.createElement("input", __assign({ disabled: disabled, type: 'radio', ref: refs }, rest)),
53
- React.createElement("span", { className: cx('iui-radio-dot', checkmarkClassName), style: checkmarkStyle },
54
- React.createElement("svg", { viewBox: '0 0 16 16', "aria-hidden": 'true', focusable: 'false' },
55
- React.createElement("circle", { cx: '8', cy: '8', r: '4' }))),
56
- label && React.createElement("span", { className: 'iui-label' }, label)));
52
+ var radio = (React.createElement("input", __assign({ className: cx('iui-radio', className && (_a = {}, _a[className] = !label, _a), checkmarkClassName), style: __assign(__assign({}, (!label && style)), checkmarkStyle), disabled: disabled, type: 'radio', ref: refs }, rest)));
53
+ return !label ? (radio) : (React.createElement("label", { className: cx('iui-radio-wrapper', (_b = { 'iui-disabled': disabled }, _b["iui-" + status] = !!status, _b), className), style: style },
54
+ radio,
55
+ label && React.createElement("span", { className: 'iui-radio-label' }, label)));
57
56
  });
58
57
  export default Radio;
@@ -30,6 +30,7 @@ import { DropdownMenu } from '../DropdownMenu';
30
30
  import { MenuItem } from '../Menu/MenuItem';
31
31
  import { useTheme } from '../utils';
32
32
  import '@itwin/itwinui-css/css/inputs.css';
33
+ import SvgCaretDownSmall from '@itwin/itwinui-icons-react/cjs/icons/CaretDownSmall';
33
34
  /**
34
35
  * Select component to select value from options.
35
36
  * Generic type is used for value. It prevents you from mistakenly using other types in `options`, `value` and `onChange`.
@@ -91,6 +92,7 @@ export var Select = function (props) {
91
92
  var _f = React.useState(0), minWidth = _f[0], setMinWidth = _f[1];
92
93
  var toggle = function () { return setIsOpen(function (open) { return !open; }); };
93
94
  var selectRef = React.useRef(null);
95
+ var toggleButtonRef = React.useRef(null);
94
96
  var onShowHandler = React.useCallback(function (instance) {
95
97
  setIsOpen(true);
96
98
  onShow === null || onShow === void 0 ? void 0 : onShow(instance);
@@ -137,13 +139,20 @@ export var Select = function (props) {
137
139
  }
138
140
  return options.find(function (option) { return option.value === value; });
139
141
  }, [options, value]);
140
- return (React.createElement("div", __assign({ className: cx('iui-select', (_a = {}, _a["iui-" + size] = !!size, _a), className), "aria-expanded": isOpen, "aria-haspopup": 'listbox', style: style }, rest),
141
- React.createElement(DropdownMenu, __assign({ menuItems: menuItems, placement: 'bottom-start', className: cx('iui-scroll', menuClassName), style: __assign({ minWidth: minWidth, maxWidth: "min(" + minWidth * 2 + "px, 90vw)", maxHeight: "300px" }, menuStyle), role: 'listbox', onShow: onShowHandler, onHide: onHideHandler, disabled: disabled }, popoverProps, { visible: isOpen }),
142
- React.createElement("div", { ref: selectRef, className: cx('iui-select-button', {
143
- 'iui-placeholder': !selectedItem && !!placeholder,
144
- 'iui-disabled': disabled,
145
- 'iui-active': isOpen,
146
- }), onClick: function () { return !disabled && toggle(); }, onKeyDown: function (e) { return !disabled && onKeyDown(e, toggle); }, tabIndex: !disabled ? 0 : undefined },
142
+ return (React.createElement("div", __assign({ className: cx('iui-input-with-icon', className), "aria-expanded": isOpen, "aria-haspopup": 'listbox', style: style }, rest),
143
+ React.createElement(DropdownMenu, __assign({ menuItems: menuItems, placement: 'bottom-start', className: cx('iui-scroll', menuClassName), style: __assign({ minWidth: minWidth, maxWidth: "min(" + minWidth * 2 + "px, 90vw)", maxHeight: "300px" }, menuStyle), role: 'listbox', onShow: onShowHandler, onHide: onHideHandler, disabled: disabled }, popoverProps, { visible: isOpen, onClickOutside: function (_, _a) {
144
+ var _b;
145
+ var target = _a.target;
146
+ if (!((_b = toggleButtonRef.current) === null || _b === void 0 ? void 0 : _b.contains(target))) {
147
+ setIsOpen(false);
148
+ }
149
+ } }),
150
+ React.createElement("div", { ref: selectRef, className: cx('iui-select-button', (_a = {
151
+ 'iui-placeholder': !selectedItem && !!placeholder,
152
+ 'iui-disabled': disabled
153
+ },
154
+ _a["iui-" + size] = !!size,
155
+ _a)), onClick: function () { return !disabled && toggle(); }, onKeyDown: function (e) { return !disabled && onKeyDown(e, toggle); }, tabIndex: !disabled ? 0 : undefined },
147
156
  !selectedItem && React.createElement("span", { className: 'iui-content' }, placeholder),
148
157
  selectedItem &&
149
158
  selectedItemRenderer &&
@@ -153,6 +162,12 @@ export var Select = function (props) {
153
162
  React.cloneElement(selectedItem.icon, {
154
163
  className: cx(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.icon.props.className, 'iui-icon'),
155
164
  }),
156
- React.createElement("span", { className: 'iui-content' }, selectedItem.label)))))));
165
+ React.createElement("span", { className: 'iui-content' }, selectedItem.label))))),
166
+ React.createElement("span", { ref: toggleButtonRef, className: cx('iui-end-icon', {
167
+ 'iui-actionable': !disabled,
168
+ 'iui-disabled': disabled,
169
+ 'iui-open': isOpen,
170
+ }), onClick: function () { return !disabled && toggle(); } },
171
+ React.createElement(SvgCaretDownSmall, { "aria-hidden": true }))));
157
172
  };
158
173
  export default Select;
@@ -79,11 +79,10 @@ export var TablePaginator = function (props) {
79
79
  var buttonSize = size != 'default' ? 'small' : undefined;
80
80
  var pageButton = React.useCallback(function (index, tabIndex) {
81
81
  if (tabIndex === void 0) { tabIndex = index === focusedIndex ? 0 : -1; }
82
- return (React.createElement("div", { key: index },
83
- React.createElement("button", { className: cx('iui-paginator-page-button', {
84
- 'iui-active': index === currentPage,
85
- 'iui-paginator-page-button-small': buttonSize === 'small',
86
- }), onClick: function () { return onPageChange(index); }, "aria-current": index === currentPage, "aria-label": localization.goToPageLabel(index + 1), tabIndex: tabIndex }, index + 1)));
82
+ return (React.createElement("button", { key: index, className: cx('iui-paginator-page-button', {
83
+ 'iui-active': index === currentPage,
84
+ 'iui-paginator-page-button-small': buttonSize === 'small',
85
+ }), onClick: function () { return onPageChange(index); }, "aria-current": index === currentPage, "aria-label": localization.goToPageLabel(index + 1), tabIndex: tabIndex }, index + 1));
87
86
  }, [focusedIndex, currentPage, localization, buttonSize, onPageChange]);
88
87
  var totalPagesCount = Math.ceil(totalRowsCount / pageSize);
89
88
  var pageList = React.useMemo(function () {
@@ -145,10 +144,9 @@ export var TablePaginator = function (props) {
145
144
  var hasNoRows = totalPagesCount === 0;
146
145
  var showPagesList = totalPagesCount > 1 || isLoading;
147
146
  var showPageSizeList = pageSizeList && onPageSizeChange && !!totalRowsCount;
148
- var ellipsis = (React.createElement("div", null,
149
- React.createElement("span", { className: cx('iui-paginator-ellipsis', {
150
- 'iui-paginator-ellipsis-small': size === 'small',
151
- }) }, "\u2026")));
147
+ var ellipsis = (React.createElement("span", { className: cx('iui-paginator-ellipsis', {
148
+ 'iui-paginator-ellipsis-small': size === 'small',
149
+ }) }, "\u2026"));
152
150
  var noRowsContent = (React.createElement(React.Fragment, null, isLoading ? (React.createElement(ProgressRadial, { indeterminate: true, size: 'small' })) : (React.createElement(Button, { styleType: 'borderless', disabled: true, size: buttonSize }, "1"))));
153
151
  if (!showPagesList && !showPageSizeList) {
154
152
  return null;
@@ -63,7 +63,7 @@ var DatePickerInput = function (props) {
63
63
  if (!((_a = buttonRef.current) === null || _a === void 0 ? void 0 : _a.contains(e.target))) {
64
64
  close();
65
65
  }
66
- } },
66
+ }, appendTo: 'parent' },
67
67
  React.createElement(LabeledInput, __assign({ displayStyle: 'inline', value: inputValue, onChange: onInputChange, onClick: close, svgIcon: React.createElement(IconButton, { styleType: 'borderless', onClick: function () { return setIsVisible(function (v) { return !v; }); }, ref: buttonRef },
68
68
  React.createElement(SvgCalendar, null)) }, rest))));
69
69
  };
@@ -82,7 +82,6 @@ export var Tile = function (props) {
82
82
  moreOptions && (React.createElement(DropdownMenu, { onShow: showMenu, onHide: hideMenu, menuItems: function (close) {
83
83
  return moreOptions.map(function (option) {
84
84
  return React.cloneElement(option, {
85
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
85
  onClick: function (value) {
87
86
  var _a, _b;
88
87
  close();
@@ -91,10 +90,11 @@ export var Tile = function (props) {
91
90
  });
92
91
  });
93
92
  } },
94
- React.createElement(IconButton, { styleType: 'borderless', size: 'small', className: cx('iui-tile-more-options', {
93
+ React.createElement("div", { className: cx('iui-tile-more-options', {
95
94
  'iui-visible': isMenuVisible,
96
- }), "aria-label": 'More options' },
97
- React.createElement(SvgMore, null)))),
95
+ }) },
96
+ React.createElement(IconButton, { styleType: 'borderless', size: 'small', "aria-label": 'More options' },
97
+ React.createElement(SvgMore, null))))),
98
98
  children),
99
99
  buttons && React.createElement("div", { className: 'iui-tile-buttons' }, buttons)));
100
100
  };
@@ -0,0 +1,123 @@
1
+ /// <reference types="react" />
2
+ import { CommonProps } from '../utils';
3
+ import '@itwin/itwinui-css/css/tree.css';
4
+ export declare type NodeData<T> = {
5
+ /**
6
+ * Array of the child nodes contained in the node.
7
+ */
8
+ subNodes?: Array<T>;
9
+ /**
10
+ * Unique id of the node.
11
+ */
12
+ nodeId: string;
13
+ /**
14
+ * Custom type used to map type `T` to `NodeData`
15
+ */
16
+ node: T;
17
+ /**
18
+ * Flag whether the node is expanded.
19
+ */
20
+ isExpanded?: boolean;
21
+ /**
22
+ * Flag whether the node is disabled.
23
+ */
24
+ isDisabled?: boolean;
25
+ /**
26
+ * Flag whether the node is selected.
27
+ */
28
+ isSelected?: boolean;
29
+ /**
30
+ * Flag whether the node has sub-nodes.
31
+ * Used to determine if node should be expandable.
32
+ */
33
+ hasSubNodes: boolean;
34
+ };
35
+ export declare type NodeRenderProps<T> = Omit<NodeData<T>, 'subNodes'>;
36
+ export declare type TreeProps<T> = {
37
+ /**
38
+ * Render function that should return the node element.
39
+ * Recommended to use `TreeNode` component.
40
+ * Must be memoized.
41
+ * @example
42
+ * const nodeRenderer = React.useCallback(({ node, ...rest }: NodeRenderProps<DataType>) => (
43
+ * <TreeNode
44
+ * label={node.label}
45
+ * onNodeExpanded={onNodeExpanded}
46
+ * {...rest}
47
+ * />
48
+ * ), [onNodeExpanded])
49
+ */
50
+ nodeRenderer: (props: NodeRenderProps<T>) => JSX.Element;
51
+ /**
52
+ * Array of custom data used for `TreeNodes` inside `Tree`.
53
+ */
54
+ data: T[];
55
+ /**
56
+ * Function that maps your `data` entry to `NodeData` that has all info about the node state.
57
+ * It will be used to render a tree node in `nodeRenderer`.
58
+ * Must be memoized.
59
+ * @example
60
+ * const getNode = React.useCallback((node: DemoData): NodeData<DemoData> => {
61
+ * return {
62
+ * subNodes: node.subItems,
63
+ * nodeId: node.id,
64
+ * node,
65
+ * isExpanded: expandedNodes[node.id],
66
+ * hasSubNodes: node.subItems.length > 0,
67
+ * };
68
+ * }, [expandedNodes]);
69
+ */
70
+ getNode: (node: T) => NodeData<T>;
71
+ } & Omit<CommonProps, 'title'>;
72
+ /**
73
+ * Tree component used to display a hierarchical structure of `TreeNodes`.
74
+ * User should control state of expanded, selected and disabled nodes using `getNode` prop.
75
+ * @example
76
+ type DemoData = {
77
+ id: string;
78
+ label: string;
79
+ subItems: DemoData[];
80
+ };
81
+
82
+ const data: Array<DemoData> = [
83
+ {
84
+ id: 'Node-1',
85
+ label: 'Facility 1',
86
+ subItems: [{ id: 'Node-1-1', label: 'Unit 1', subItems: [] }],
87
+ },
88
+ {
89
+ id: 'Node-2',
90
+ label: 'Facility 2',
91
+ subItems: [{ id: 'Node-2-1', label: 'Unit 2', subItems: [] }],
92
+ },
93
+ ];
94
+
95
+ const [expandedNodes, setExpandedNodes] = React.useState<Record<string, boolean>>({});
96
+ const onNodeExpanded = React.useCallback((nodeId: string, isExpanded: boolean) => {
97
+ setExpandedNodes((oldExpanded) => ({ ...oldExpanded, [nodeId]: isExpanded }));
98
+ }, []);
99
+
100
+ const getNode = React.useCallback((node: DemoData): NodeData<DemoData> => {
101
+ return {
102
+ subNodes: node.subItems,
103
+ nodeId: node.id,
104
+ node,
105
+ isExpanded: expandedNodes[node.id],
106
+ hasSubNodes: node.subItems.length > 0,
107
+ };
108
+ }, [expandedNodes]);
109
+
110
+ <Tree<DemoData>
111
+ data={data}
112
+ getNode={getNode}
113
+ nodeRenderer={React.useCallback(({ node, ...rest }) => (
114
+ <TreeNode
115
+ label={node.label}
116
+ onNodeExpanded={onNodeExpanded}
117
+ {...rest}
118
+ />
119
+ ), [onNodeExpanded])}
120
+ />
121
+ */
122
+ export declare const Tree: <T>(props: TreeProps<T>) => JSX.Element;
123
+ export default Tree;