@itwin/itwinui-react 3.0.11 → 3.1.1

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 (56) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -1
  3. package/cjs/core/ButtonGroup/ButtonGroup.d.ts +2 -0
  4. package/cjs/core/ButtonGroup/ButtonGroup.js +26 -21
  5. package/cjs/core/Buttons/IconButton.d.ts +6 -0
  6. package/cjs/core/Buttons/IconButton.js +4 -2
  7. package/cjs/core/Carousel/Carousel.d.ts +110 -2
  8. package/cjs/core/Carousel/CarouselNavigation.d.ts +110 -2
  9. package/cjs/core/ComboBox/ComboBoxInput.js +17 -5
  10. package/cjs/core/FileUpload/FileUpload.d.ts +3 -1
  11. package/cjs/core/FileUpload/FileUpload.js +5 -8
  12. package/cjs/core/InputWithDecorations/InputWithDecorations.d.ts +55 -1
  13. package/cjs/core/Popover/Popover.d.ts +22 -14
  14. package/cjs/core/Popover/Popover.js +14 -2
  15. package/cjs/core/Table/Table.js +28 -2
  16. package/cjs/core/Tabs/Tabs.js +23 -3
  17. package/cjs/core/ThemeProvider/ThemeProvider.js +44 -21
  18. package/cjs/core/Tile/Tile.d.ts +61 -1
  19. package/cjs/core/Tooltip/Tooltip.js +6 -2
  20. package/cjs/core/Tree/Tree.d.ts +6 -0
  21. package/cjs/core/Tree/Tree.js +4 -3
  22. package/cjs/core/Tree/TreeContext.d.ts +4 -0
  23. package/cjs/core/Tree/TreeNodeExpander.js +4 -1
  24. package/cjs/core/utils/icons/SvgChevronRightSmall.d.ts +2 -0
  25. package/cjs/core/utils/icons/SvgChevronRightSmall.js +15 -0
  26. package/cjs/core/utils/icons/index.d.ts +1 -0
  27. package/cjs/core/utils/icons/index.js +1 -0
  28. package/cjs/styles.js +2 -2
  29. package/esm/core/ButtonGroup/ButtonGroup.d.ts +2 -0
  30. package/esm/core/ButtonGroup/ButtonGroup.js +25 -20
  31. package/esm/core/Buttons/IconButton.d.ts +6 -0
  32. package/esm/core/Buttons/IconButton.js +4 -2
  33. package/esm/core/Carousel/Carousel.d.ts +110 -2
  34. package/esm/core/Carousel/CarouselNavigation.d.ts +110 -2
  35. package/esm/core/ComboBox/ComboBoxInput.js +17 -5
  36. package/esm/core/FileUpload/FileUpload.d.ts +3 -1
  37. package/esm/core/FileUpload/FileUpload.js +6 -9
  38. package/esm/core/InputWithDecorations/InputWithDecorations.d.ts +55 -1
  39. package/esm/core/Popover/Popover.d.ts +22 -14
  40. package/esm/core/Popover/Popover.js +15 -3
  41. package/esm/core/Table/Table.js +28 -2
  42. package/esm/core/Tabs/Tabs.js +23 -3
  43. package/esm/core/ThemeProvider/ThemeProvider.js +45 -22
  44. package/esm/core/Tile/Tile.d.ts +61 -1
  45. package/esm/core/Tooltip/Tooltip.js +6 -2
  46. package/esm/core/Tree/Tree.d.ts +6 -0
  47. package/esm/core/Tree/Tree.js +4 -3
  48. package/esm/core/Tree/TreeContext.d.ts +4 -0
  49. package/esm/core/Tree/TreeNodeExpander.js +5 -2
  50. package/esm/core/utils/icons/SvgChevronRightSmall.d.ts +2 -0
  51. package/esm/core/utils/icons/SvgChevronRightSmall.js +10 -0
  52. package/esm/core/utils/icons/index.d.ts +1 -0
  53. package/esm/core/utils/icons/index.js +1 -0
  54. package/esm/styles.js +2 -2
  55. package/package.json +7 -5
  56. package/styles.css +14 -14
@@ -14,7 +14,8 @@ const Portal_js_1 = require("../utils/components/Portal.js");
14
14
  const ThemeProvider_js_1 = require("../ThemeProvider/ThemeProvider.js");
15
15
  // ----------------------------------------------------------------------------
16
16
  const usePopover = (options) => {
17
- const { placement = 'bottom-start', visible, onVisibleChange, closeOnOutsideClick, autoUpdateOptions, middleware = { flip: true, shift: true }, matchWidth, trigger = { click: true, hover: false, focus: false }, role, } = options;
17
+ const { placement = 'bottom-start', visible, onVisibleChange, closeOnOutsideClick, autoUpdateOptions, matchWidth, trigger = { click: true, hover: false, focus: false }, role, } = options;
18
+ const middleware = { flip: true, shift: true, ...options.middleware };
18
19
  const [open, onOpenChange] = (0, index_js_1.useControlledState)(false, visible, onVisibleChange);
19
20
  const floating = (0, react_1.useFloating)({
20
21
  placement,
@@ -86,7 +87,10 @@ exports.Popover = React.forwardRef((props, forwardedRef) => {
86
87
  const { portal = true,
87
88
  //
88
89
  // popover options
89
- visible, placement = 'bottom-start', onVisibleChange, closeOnOutsideClick = true,
90
+ visible, placement = 'bottom-start', onVisibleChange, closeOnOutsideClick = true, middleware,
91
+ //
92
+ // extra props
93
+ positionReference,
90
94
  //
91
95
  // dom props
92
96
  className, children, content, applyBackground = false, ...rest } = props;
@@ -96,11 +100,19 @@ exports.Popover = React.forwardRef((props, forwardedRef) => {
96
100
  onVisibleChange,
97
101
  closeOnOutsideClick,
98
102
  role: 'dialog',
103
+ middleware,
99
104
  });
100
105
  const [popoverElement, setPopoverElement] = React.useState();
101
106
  const popoverRef = (0, index_js_1.useMergedRefs)(popover.refs.setFloating, forwardedRef, setPopoverElement);
102
107
  const triggerId = `${(0, index_js_1.useId)()}-trigger`;
103
108
  const hasAriaLabel = !!props['aria-labelledby'] || !!props['aria-label'];
109
+ (0, index_js_1.useIsomorphicLayoutEffect)(() => {
110
+ if (!positionReference) {
111
+ return;
112
+ }
113
+ popover.refs.setPositionReference(positionReference);
114
+ return () => void popover.refs.setPositionReference(null);
115
+ }, [popover.refs, positionReference]);
104
116
  return (React.createElement(React.Fragment, null,
105
117
  (0, index_js_1.cloneElementWithRef)(children, (children) => ({
106
118
  id: children.props.id || triggerId,
@@ -7,6 +7,7 @@ const tslib_1 = require("tslib");
7
7
  * See LICENSE.md in the project root for license terms and full copyright notice.
8
8
  *--------------------------------------------------------------------------------------------*/
9
9
  const React = tslib_1.__importStar(require("react"));
10
+ const ReactDOM = tslib_1.__importStar(require("react-dom"));
10
11
  const classnames_1 = tslib_1.__importDefault(require("classnames"));
11
12
  const react_table_1 = require("react-table");
12
13
  const ProgressRadial_js_1 = require("../ProgressIndicators/ProgressRadial.js");
@@ -465,11 +466,19 @@ const Table = (props) => {
465
466
  updateStickyState();
466
467
  }
467
468
  }, tabIndex: -1, "aria-multiselectable": (isSelectable && selectionMode === 'multi') || undefined },
469
+ React.createElement(ShadowTemplate, null,
470
+ React.createElement("slot", null),
471
+ React.createElement("div", { "aria-hidden": true, style: {
472
+ // This ensures that the table-body is always the same width as the table-header,
473
+ // even if the table has no rows. See https://github.com/iTwin/iTwinUI/pull/1725
474
+ width: headerRef.current?.scrollWidth,
475
+ height: 0.1,
476
+ } })),
468
477
  data.length !== 0 && (React.createElement(React.Fragment, null, enableVirtualization ? (React.createElement(VirtualScroll_js_1.default, { itemsLength: page.length, itemRenderer: virtualizedItemRenderer, scrollToIndex: scrollToIndex })) : (page.map((_, index) => getPreparedRow(index))))),
469
478
  isLoading && data.length === 0 && (React.createElement(index_js_1.Box, { as: 'div', ...emptyTableContentProps, className: (0, classnames_1.default)('iui-table-empty', emptyTableContentProps?.className) },
470
479
  React.createElement(ProgressRadial_js_1.ProgressRadial, { indeterminate: true }))),
471
- isLoading && data.length !== 0 && (React.createElement(index_js_1.Box, { className: 'iui-table-row' },
472
- React.createElement(index_js_1.Box, { className: 'iui-table-cell', style: { justifyContent: 'center' } },
480
+ isLoading && data.length !== 0 && (React.createElement(index_js_1.Box, { className: 'iui-table-row', "data-iui-loading": 'true' },
481
+ React.createElement(index_js_1.Box, { className: 'iui-table-cell' },
473
482
  React.createElement(ProgressRadial_js_1.ProgressRadial, { indeterminate: true, size: 'small' })))),
474
483
  !isLoading && data.length === 0 && !areFiltersSet && (React.createElement(index_js_1.Box, { as: 'div', ...emptyTableContentProps, className: (0, classnames_1.default)('iui-table-empty', emptyTableContentProps?.className) },
475
484
  React.createElement("div", null, emptyTableContent))),
@@ -481,3 +490,20 @@ const Table = (props) => {
481
490
  };
482
491
  exports.Table = Table;
483
492
  exports.default = exports.Table;
493
+ // ----------------------------------------------------------------------------
494
+ /**
495
+ * Wrapper around `<template>` element that attaches shadow root to its parent
496
+ * and moves its children into the shadow root.
497
+ */
498
+ const ShadowTemplate = ({ children }) => {
499
+ const [shadowRoot, setShadowRoot] = React.useState();
500
+ const attachShadowRef = React.useCallback((template) => {
501
+ const parent = template?.parentElement;
502
+ if (!template || !parent || parent.shadowRoot) {
503
+ return;
504
+ }
505
+ setShadowRoot(parent.attachShadow({ mode: 'open' }));
506
+ template.remove();
507
+ }, []);
508
+ return (React.createElement("template", { ref: attachShadowRef }, shadowRoot && ReactDOM.createPortal(children, shadowRoot)));
509
+ };
@@ -37,7 +37,7 @@ const TabList = React.forwardRef((props, ref) => {
37
37
  const isClient = (0, index_js_1.useIsClient)();
38
38
  const tablistRef = React.useRef(null);
39
39
  const [tablistSizeRef, tabsWidth] = (0, index_js_1.useContainerWidth)(type !== 'default');
40
- const refs = (0, index_js_1.useMergedRefs)(ref, tablistRef, tablistSizeRef);
40
+ const refs = (0, index_js_1.useMergedRefs)(ref, tablistRef, tablistSizeRef, useScrollbarGutter());
41
41
  return (React.createElement(index_js_1.Box, { className: (0, classnames_1.default)('iui-tabs', `iui-${type}`, {
42
42
  'iui-green': color === 'green',
43
43
  'iui-animated': type !== 'default' && isClient,
@@ -138,7 +138,7 @@ const Tab = React.forwardRef((props, forwardedRef) => {
138
138
  setActiveValue(value);
139
139
  }
140
140
  }, [activeValue, setActiveValue, value]);
141
- return (React.createElement(index_js_1.ButtonBase, { className: (0, classnames_1.default)('iui-tab', className), role: 'tab', tabIndex: isActive ? 0 : -1, "aria-selected": isActive, "aria-controls": `${idPrefix}-panel-${value}`, ref: (0, index_js_1.useMergedRefs)(tabRef, forwardedRef, setInitialActiveRef), ...rest, id: `${idPrefix}-tab-${value}`, onClick: (0, index_js_1.mergeEventHandlers)(props.onClick, () => setActiveValue(value)), onKeyDown: (0, index_js_1.mergeEventHandlers)(props.onKeyDown, onKeyDown), onFocus: (0, index_js_1.mergeEventHandlers)(props.onFocus, () => {
141
+ return (React.createElement(index_js_1.ButtonBase, { className: (0, classnames_1.default)('iui-tab', className), role: 'tab', tabIndex: isActive ? 0 : -1, "aria-selected": isActive, "aria-controls": `${idPrefix}-panel-${value.replaceAll(' ', '-')}`, ref: (0, index_js_1.useMergedRefs)(tabRef, forwardedRef, setInitialActiveRef), ...rest, id: `${idPrefix}-tab-${value.replaceAll(' ', '-')}`, onClick: (0, index_js_1.mergeEventHandlers)(props.onClick, () => setActiveValue(value)), onKeyDown: (0, index_js_1.mergeEventHandlers)(props.onKeyDown, onKeyDown), onFocus: (0, index_js_1.mergeEventHandlers)(props.onFocus, () => {
142
142
  tabRef.current?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
143
143
  if (focusActivationMode === 'auto' && !props.disabled) {
144
144
  setActiveValue(value);
@@ -178,7 +178,7 @@ TabsActions.displayName = 'Tabs.Actions';
178
178
  const TabsPanel = React.forwardRef((props, ref) => {
179
179
  const { value, className, children, ...rest } = props;
180
180
  const { activeValue, idPrefix } = (0, index_js_1.useSafeContext)(TabsContext);
181
- return (React.createElement(index_js_1.Box, { className: (0, classnames_1.default)('iui-tabs-content', className), "aria-labelledby": `${idPrefix}-tab-${value}`, role: 'tabpanel', hidden: activeValue !== value ? true : undefined, ref: ref, ...rest, id: `${idPrefix}-panel-${value}` }, children));
181
+ return (React.createElement(index_js_1.Box, { className: (0, classnames_1.default)('iui-tabs-content', className), "aria-labelledby": `${idPrefix}-tab-${value.replaceAll(' ', '-')}`, role: 'tabpanel', hidden: activeValue !== value ? true : undefined, ref: ref, ...rest, id: `${idPrefix}-panel-${value.replaceAll(' ', '-')}` }, children));
182
182
  });
183
183
  TabsPanel.displayName = 'Tabs.Panel';
184
184
  const LegacyTabsComponent = React.forwardRef((props, forwardedRef) => {
@@ -323,3 +323,23 @@ exports.Tabs = Object.assign(LegacyTabsComponent, {
323
323
  const TabsContext = React.createContext(undefined);
324
324
  const TabListContext = React.createContext(undefined);
325
325
  exports.default = exports.Tabs;
326
+ // ----------------------------------------------------------------------------
327
+ /**
328
+ * This conditionally adds `scrollbar-gutter: stable` only if the content overflows.
329
+ * This is a workaround to prevent layout shift that happens because of scrollbar width.
330
+ *
331
+ * @see https://github.com/iTwin/iTwinUI/issues/1627
332
+ */
333
+ const useScrollbarGutter = () => {
334
+ return React.useCallback((element) => {
335
+ if (element) {
336
+ if (element.scrollHeight > element.clientHeight) {
337
+ element.style.scrollbarGutter = 'stable';
338
+ // Safari fallback
339
+ if (!CSS.supports('scrollbar-gutter: stable')) {
340
+ element.style.overflowY = 'scroll';
341
+ }
342
+ }
343
+ }
344
+ }, []);
345
+ };
@@ -42,27 +42,26 @@ const Toaster_js_1 = require("../Toast/Toaster.js");
42
42
  */
43
43
  exports.ThemeProvider = React.forwardRef((props, forwardedRef) => {
44
44
  const { theme: themeProp = 'inherit', children, themeOptions = {}, portalContainer: portalContainerProp, ...rest } = props;
45
- const [parentTheme, rootRef, parentContext] = useParentTheme();
46
- const theme = themeProp === 'inherit' ? parentTheme || 'light' : themeProp;
45
+ const [rootElement, setRootElement] = React.useState(null);
46
+ const parent = useParentThemeAndContext(rootElement);
47
+ const theme = themeProp === 'inherit' ? parent.theme || 'light' : themeProp;
47
48
  // default apply background only for topmost ThemeProvider
48
- themeOptions.applyBackground ?? (themeOptions.applyBackground = !parentTheme);
49
+ themeOptions.applyBackground ?? (themeOptions.applyBackground = !parent.theme);
49
50
  // default inherit highContrast option from parent if also inheriting base theme
50
- themeOptions.highContrast ?? (themeOptions.highContrast = themeProp === 'inherit'
51
- ? parentContext?.themeOptions?.highContrast
52
- : undefined);
51
+ themeOptions.highContrast ?? (themeOptions.highContrast = themeProp === 'inherit' ? parent.highContrast : undefined);
53
52
  /**
54
53
  * We will portal our portal container into `portalContainer` prop (if specified),
55
54
  * or inherit `portalContainer` from context (if also inheriting theme).
56
55
  */
57
56
  const portaledPortalContainer = portalContainerProp ||
58
- (themeProp === 'inherit' ? parentContext?.portalContainer : undefined);
57
+ (themeProp === 'inherit' ? parent.context?.portalContainer : undefined);
59
58
  const [portalContainer, setPortalContainer] = (0, index_js_1.useControlledState)(null, portaledPortalContainer);
60
59
  const contextValue = React.useMemo(() => ({ theme, themeOptions, portalContainer }),
61
60
  // we do include all dependencies below, but we want to stringify the objects as they could be different on each render
62
61
  // eslint-disable-next-line react-hooks/exhaustive-deps
63
62
  [theme, JSON.stringify(themeOptions), portalContainer]);
64
63
  return (React.createElement(ThemeContext_js_1.ThemeContext.Provider, { value: contextValue },
65
- React.createElement(Root, { theme: theme, themeOptions: themeOptions, ref: (0, index_js_1.useMergedRefs)(forwardedRef, rootRef), ...rest },
64
+ React.createElement(Root, { theme: theme, themeOptions: themeOptions, ref: (0, index_js_1.useMergedRefs)(forwardedRef, setRootElement), ...rest },
66
65
  React.createElement(Toaster_js_1.ToastProvider, null,
67
66
  children,
68
67
  portaledPortalContainer ? (ReactDOM.createPortal(React.createElement(Toaster_js_1.Toaster, null), portaledPortalContainer)) : (React.createElement("div", { ref: setPortalContainer, style: { display: 'contents' } },
@@ -81,22 +80,46 @@ const Root = React.forwardRef((props, forwardedRef) => {
81
80
  });
82
81
  // ----------------------------------------------------------------------------
83
82
  /**
84
- * Returns theme from either parent context or by reading the closest
83
+ * Returns theme information from either parent ThemeContext or by reading the closest
85
84
  * data-iui-theme attribute if context is not found.
85
+ *
86
+ * Also returns the ThemeContext itself (if found).
86
87
  */
87
- const useParentTheme = () => {
88
+ const useParentThemeAndContext = (rootElement) => {
88
89
  const parentContext = React.useContext(ThemeContext_js_1.ThemeContext);
89
- const rootRef = React.useRef(null);
90
90
  const [parentThemeState, setParentTheme] = React.useState(parentContext?.theme);
91
+ const [parentHighContrastState, setParentHighContrastState] = React.useState(parentContext?.themeOptions?.highContrast);
92
+ const parentThemeRef = (0, index_js_1.useLatestRef)(parentContext?.theme);
91
93
  (0, index_js_1.useIsomorphicLayoutEffect)(() => {
92
- setParentTheme((old) => old ||
93
- rootRef.current?.parentElement
94
- ?.closest('[data-iui-theme]')
95
- ?.getAttribute('data-iui-theme'));
96
- }, []);
97
- return [
98
- parentContext?.theme ?? parentThemeState,
99
- rootRef,
100
- parentContext,
101
- ];
94
+ // bail if we already have theme from context
95
+ if (parentThemeRef.current) {
96
+ return;
97
+ }
98
+ // find parent theme from closest data-iui-theme attribute
99
+ const closestRoot = rootElement?.parentElement?.closest('[data-iui-theme]');
100
+ if (!closestRoot) {
101
+ return;
102
+ }
103
+ // helper function that updates state to match data attributes from closest root
104
+ const synchronizeTheme = () => {
105
+ setParentTheme(closestRoot?.getAttribute('data-iui-theme'));
106
+ setParentHighContrastState(closestRoot?.getAttribute('data-iui-contrast') === 'high');
107
+ };
108
+ // set theme for initial mount
109
+ synchronizeTheme();
110
+ // use mutation observers to listen to future updates to data attributes
111
+ const observer = new MutationObserver(() => synchronizeTheme());
112
+ observer.observe(closestRoot, {
113
+ attributes: true,
114
+ attributeFilter: ['data-iui-theme', 'data-iui-contrast'],
115
+ });
116
+ return () => {
117
+ observer.disconnect();
118
+ };
119
+ }, [rootElement, parentThemeRef]);
120
+ return {
121
+ theme: parentContext?.theme ?? parentThemeState,
122
+ highContrast: parentContext?.themeOptions?.highContrast ?? parentHighContrastState,
123
+ context: parentContext,
124
+ };
102
125
  };
@@ -249,9 +249,69 @@ export declare const Tile: PolymorphicForwardRefComponent<"div", TileLegacyProps
249
249
  */
250
250
  IconButton: PolymorphicForwardRefComponent<"button", Omit<Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
251
251
  ref?: ((instance: HTMLButtonElement | null) => void) | React.RefObject<HTMLButtonElement> | null | undefined;
252
- }, "label" | "as" | "size" | "htmlDisabled" | "styleType" | "isActive" | "iconProps"> & {
252
+ }, "label" | "as" | "size" | "htmlDisabled" | "styleType" | "labelProps" | "isActive" | "iconProps"> & {
253
253
  isActive?: boolean | undefined;
254
254
  label?: React.ReactNode;
255
+ labelProps?: Omit<Omit<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
256
+ ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject<HTMLDivElement> | null | undefined;
257
+ }, "as" | "children" | "content" | "portal" | keyof {
258
+ placement?: import("@floating-ui/utils").Placement | undefined;
259
+ visible?: boolean | undefined;
260
+ onVisibleChange?: ((visible: boolean) => void) | undefined;
261
+ autoUpdateOptions?: {
262
+ ancestorScroll?: boolean | undefined;
263
+ ancestorResize?: boolean | undefined;
264
+ elementResize?: boolean | undefined;
265
+ animationFrame?: boolean | undefined;
266
+ layoutShift?: boolean | undefined;
267
+ } | undefined;
268
+ middleware?: {
269
+ offset?: number | undefined;
270
+ flip?: boolean | undefined;
271
+ shift?: boolean | undefined;
272
+ size?: boolean | undefined;
273
+ autoPlacement?: boolean | undefined;
274
+ hide?: boolean | undefined;
275
+ inline?: boolean | undefined;
276
+ } | undefined;
277
+ reference?: HTMLElement | null | undefined;
278
+ ariaStrategy?: "label" | "description" | "none" | undefined; /**
279
+ * Default tile variant or the folder layout.
280
+ * @default 'default'
281
+ */
282
+ id?: string | undefined;
283
+ }> & {
284
+ content: React.ReactNode;
285
+ children?: React.ReactNode;
286
+ } & import("../utils/index.js").PortalProps & {
287
+ placement?: import("@floating-ui/utils").Placement | undefined;
288
+ visible?: boolean | undefined;
289
+ onVisibleChange?: ((visible: boolean) => void) | undefined;
290
+ autoUpdateOptions?: {
291
+ ancestorScroll?: boolean | undefined;
292
+ ancestorResize?: boolean | undefined;
293
+ elementResize?: boolean | undefined;
294
+ animationFrame?: boolean | undefined;
295
+ layoutShift?: boolean | undefined;
296
+ } | undefined;
297
+ middleware?: {
298
+ offset?: number | undefined;
299
+ flip?: boolean | undefined;
300
+ shift?: boolean | undefined;
301
+ size?: boolean | undefined;
302
+ autoPlacement?: boolean | undefined;
303
+ hide?: boolean | undefined;
304
+ inline?: boolean | undefined;
305
+ } | undefined;
306
+ reference?: HTMLElement | null | undefined;
307
+ ariaStrategy?: "label" | "description" | "none" | undefined; /**
308
+ * Default tile variant or the folder layout.
309
+ * @default 'default'
310
+ */
311
+ id?: string | undefined;
312
+ } & {
313
+ as?: "div" | undefined;
314
+ }, "ref">, "children" | "content" | "reference" | "ariaStrategy"> | undefined;
255
315
  iconProps?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> | undefined;
256
316
  } & Omit<import("../Buttons/Button.js").ButtonProps, "startIcon" | "endIcon" | "labelProps" | "startIconProps" | "endIconProps"> & {
257
317
  as?: "button" | undefined;
@@ -61,8 +61,12 @@ const useTooltip = (options = {}) => {
61
61
  ...interactions.getReferenceProps(),
62
62
  }).forEach(([key, value]) => {
63
63
  if (typeof value === 'function') {
64
- reference.addEventListener(domEventName(key), value);
65
- cleanupValues[key] = value;
64
+ // manually add `.nativeEvent` (react concept) because floating-ui needs it
65
+ const patchedHandler = (event) => {
66
+ value({ ...event, nativeEvent: event });
67
+ };
68
+ reference.addEventListener(domEventName(key), patchedHandler);
69
+ cleanupValues[key] = patchedHandler;
66
70
  }
67
71
  else if (value) {
68
72
  cleanupValues[key] = reference.getAttribute(key);
@@ -33,6 +33,12 @@ export type NodeData<T> = {
33
33
  };
34
34
  export type NodeRenderProps<T> = Omit<NodeData<T>, 'subNodes'>;
35
35
  export type TreeProps<T> = {
36
+ /**
37
+ * Modify size of the tree.
38
+ *
39
+ * @default 'default'
40
+ */
41
+ size?: 'default' | 'small';
36
42
  /**
37
43
  * Render function that should return the node element.
38
44
  * Recommended to use `TreeNode` component.
@@ -61,7 +61,7 @@ const TreeContext_js_1 = require("./TreeContext.js");
61
61
  />
62
62
  */
63
63
  const Tree = (props) => {
64
- const { data, className, nodeRenderer, getNode, enableVirtualization = false, style, ...rest } = props;
64
+ const { data, className, nodeRenderer, getNode, size = 'default', enableVirtualization = false, style, ...rest } = props;
65
65
  const treeRef = React.useRef(null);
66
66
  const focusedIndex = React.useRef(0);
67
67
  React.useEffect(() => {
@@ -146,8 +146,9 @@ const Tree = (props) => {
146
146
  setScrollToIndex(parentNodeIndex);
147
147
  }
148
148
  : undefined,
149
+ size,
149
150
  } }, nodeRenderer(node.nodeProps)));
150
- }, [firstLevelNodesList.length, flatNodesList, nodeRenderer]);
151
+ }, [firstLevelNodesList.length, flatNodesList, nodeRenderer, size]);
151
152
  const [scrollToIndex, setScrollToIndex] = React.useState();
152
153
  const flatNodesListRef = React.useRef(flatNodesList);
153
154
  React.useEffect(() => {
@@ -175,7 +176,7 @@ const Tree = (props) => {
175
176
  items[focusedIndex.current]?.focus();
176
177
  }
177
178
  };
178
- return (React.createElement(React.Fragment, null, enableVirtualization ? (React.createElement(VirtualizedTree, { flatNodesList: flatNodesList, itemRenderer: itemRenderer, scrollToIndex: scrollToIndex, onFocus: handleFocus, onKeyDown: handleKeyDown, ref: treeRef, className: className, style: style, ...rest })) : (React.createElement(TreeElement, { onKeyDown: handleKeyDown, onFocus: handleFocus, className: className, style: style, ref: treeRef, ...rest }, flatNodesList.map((_, i) => itemRenderer(i))))));
179
+ return (React.createElement(React.Fragment, null, enableVirtualization ? (React.createElement(VirtualizedTree, { flatNodesList: flatNodesList, itemRenderer: itemRenderer, scrollToIndex: scrollToIndex, onFocus: handleFocus, onKeyDown: handleKeyDown, ref: treeRef, className: className, style: style, ...rest })) : (React.createElement(TreeElement, { onKeyDown: handleKeyDown, onFocus: handleFocus, className: className, "data-iui-size": size === 'small' ? 'small' : undefined, style: style, ref: treeRef, ...rest }, flatNodesList.map((_, i) => itemRenderer(i))))));
179
180
  };
180
181
  exports.Tree = Tree;
181
182
  const TreeElement = index_js_1.polymorphic.ul('iui-tree', {
@@ -24,6 +24,10 @@ export type TreeContextProps = {
24
24
  * Function that scrolls to the node's parent node.
25
25
  */
26
26
  scrollToParent?: () => void;
27
+ /**
28
+ * Size of the tree.
29
+ */
30
+ size?: 'default' | 'small';
27
31
  };
28
32
  export declare const TreeContext: React.Context<TreeContextProps | undefined>;
29
33
  export declare const useTreeContext: () => TreeContextProps;
@@ -10,10 +10,13 @@ const React = tslib_1.__importStar(require("react"));
10
10
  const classnames_1 = tslib_1.__importDefault(require("classnames"));
11
11
  const index_js_1 = require("../utils/index.js");
12
12
  const IconButton_js_1 = require("../Buttons/IconButton.js");
13
+ const TreeContext_js_1 = require("./TreeContext.js");
13
14
  exports.TreeNodeExpander = React.forwardRef((props, ref) => {
14
15
  const { isExpanded, ...rest } = props;
16
+ const size = React.useContext(TreeContext_js_1.TreeContext)?.size ?? 'default';
17
+ const ChevronIcon = size === 'small' ? index_js_1.SvgChevronRightSmall : index_js_1.SvgChevronRight;
15
18
  return (React.createElement(IconButton_js_1.IconButton, { styleType: 'borderless', size: 'small', "aria-label": isExpanded ? 'Collapse' : 'Expand', ref: ref, ...rest },
16
- React.createElement(index_js_1.SvgChevronRight, { className: (0, classnames_1.default)('iui-tree-node-content-expander-icon', {
19
+ React.createElement(ChevronIcon, { className: (0, classnames_1.default)('iui-tree-node-content-expander-icon', {
17
20
  'iui-tree-node-content-expander-icon-expanded': isExpanded,
18
21
  }) })));
19
22
  });
@@ -0,0 +1,2 @@
1
+ import * as React from 'react';
2
+ export declare const SvgChevronRightSmall: (props: React.ComponentPropsWithoutRef<'svg'>) => React.JSX.Element;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SvgChevronRightSmall = void 0;
4
+ const tslib_1 = require("tslib");
5
+ /*---------------------------------------------------------------------------------------------
6
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
7
+ * See LICENSE.md in the project root for license terms and full copyright notice.
8
+ *--------------------------------------------------------------------------------------------*/
9
+ const React = tslib_1.__importStar(require("react"));
10
+ const Svg_js_1 = require("./Svg.js");
11
+ const SvgChevronRightSmall = (props) => {
12
+ return (React.createElement(Svg_js_1.Svg, { ...props },
13
+ React.createElement("path", { d: 'm5.525 2-1.05 1.05L9.425 8l-4.95 4.95L5.525 14l6-6-6-6Z' })));
14
+ };
15
+ exports.SvgChevronRightSmall = SvgChevronRightSmall;
@@ -7,6 +7,7 @@ export * from './SvgClose.js';
7
7
  export * from './SvgCloseSmall.js';
8
8
  export * from './SvgChevronLeft.js';
9
9
  export * from './SvgChevronRight.js';
10
+ export * from './SvgChevronRightSmall.js';
10
11
  export * from './SvgChevronLeftDouble.js';
11
12
  export * from './SvgChevronRightDouble.js';
12
13
  export * from './SvgCaretUpSmall.js';
@@ -14,6 +14,7 @@ tslib_1.__exportStar(require("./SvgClose.js"), exports);
14
14
  tslib_1.__exportStar(require("./SvgCloseSmall.js"), exports);
15
15
  tslib_1.__exportStar(require("./SvgChevronLeft.js"), exports);
16
16
  tslib_1.__exportStar(require("./SvgChevronRight.js"), exports);
17
+ tslib_1.__exportStar(require("./SvgChevronRightSmall.js"), exports);
17
18
  tslib_1.__exportStar(require("./SvgChevronLeftDouble.js"), exports);
18
19
  tslib_1.__exportStar(require("./SvgChevronRightDouble.js"), exports);
19
20
  tslib_1.__exportStar(require("./SvgCaretUpSmall.js"), exports);
package/cjs/styles.js CHANGED
@@ -208,9 +208,9 @@ const styles = {
208
208
  "iui-overlay-exiting": "_iui3-overlay-exiting",
209
209
  closeAnimation,
210
210
  "iui-progress-indicator-radial": "_iui3-progress-indicator-radial",
211
- "iui-uu9on85": "_iui3-uu9on85",
211
+ "iui-u0lnfla": "_iui3-u0lnfla",
212
212
  "iui-progress-indicator-linear-label": "_iui3-progress-indicator-linear-label",
213
- "iui-uu9on8i": "_iui3-uu9on8i",
213
+ "iui-u0lnflm": "_iui3-u0lnflm",
214
214
  "iui-radio": "_iui3-radio",
215
215
  "iui-radio-tile": "_iui3-radio-tile",
216
216
  "iui-radio-tile-icon": "_iui3-radio-tile-icon",
@@ -1,5 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import type { PolymorphicForwardRefComponent } from '../utils/index.js';
3
+ /** This context is used for letting descendant IconButtons know the ButtonGroup's orientation. */
4
+ export declare const ButtonGroupContext: React.Context<string | undefined>;
3
5
  type ButtonGroupProps = {
4
6
  /**
5
7
  * Buttons in the ButtonGroup.
@@ -6,6 +6,10 @@ import * as React from 'react';
6
6
  import cx from 'classnames';
7
7
  import { useOverflow, useMergedRefs, Box } from '../utils/index.js';
8
8
  import { FloatingDelayGroup } from '@floating-ui/react';
9
+ // ----------------------------------------------------------------------------
10
+ /** This context is used for letting descendant IconButtons know the ButtonGroup's orientation. */
11
+ export const ButtonGroupContext = React.createContext(undefined);
12
+ ButtonGroupContext.displayName = 'ButtonGroupContext';
9
13
  /**
10
14
  * Group buttons together for common actions.
11
15
  * Handles responsive overflow when the `overflowButton` prop is specified.
@@ -40,25 +44,26 @@ export const ButtonGroup = React.forwardRef((props, ref) => {
40
44
  const [overflowRef, visibleCount] = useOverflow(items, !overflowButton, orientation);
41
45
  const refs = useMergedRefs(overflowRef, ref);
42
46
  return (React.createElement(FloatingDelayGroup, { delay: { open: 50, close: 250 } },
43
- React.createElement(Box, { className: cx('iui-button-group', {
44
- 'iui-button-group-overflow-x': !!overflowButton && orientation === 'horizontal',
45
- }, className), "data-iui-orientation": orientation === 'vertical' ? orientation : undefined, ref: refs, ...rest }, (() => {
46
- if (!(visibleCount < items.length)) {
47
- return items;
48
- }
49
- const overflowStart = overflowPlacement === 'start'
50
- ? items.length - visibleCount
51
- : visibleCount - 1;
52
- return (React.createElement(React.Fragment, null,
53
- overflowButton &&
54
- overflowPlacement === 'start' &&
55
- overflowButton(overflowStart),
56
- overflowPlacement === 'start'
57
- ? items.slice(overflowStart + 1)
58
- : items.slice(0, Math.max(0, overflowStart)),
59
- overflowButton &&
60
- overflowPlacement === 'end' &&
61
- overflowButton(overflowStart)));
62
- })())));
47
+ React.createElement(ButtonGroupContext.Provider, { value: orientation },
48
+ React.createElement(Box, { className: cx('iui-button-group', {
49
+ 'iui-button-group-overflow-x': !!overflowButton && orientation === 'horizontal',
50
+ }, className), "data-iui-orientation": orientation === 'vertical' ? orientation : undefined, ref: refs, ...rest }, (() => {
51
+ if (!(visibleCount < items.length)) {
52
+ return items;
53
+ }
54
+ const overflowStart = overflowPlacement === 'start'
55
+ ? items.length - visibleCount
56
+ : visibleCount - 1;
57
+ return (React.createElement(React.Fragment, null,
58
+ overflowButton &&
59
+ overflowPlacement === 'start' &&
60
+ overflowButton(overflowStart),
61
+ overflowPlacement === 'start'
62
+ ? items.slice(overflowStart + 1)
63
+ : items.slice(0, Math.max(0, overflowStart)),
64
+ overflowButton &&
65
+ overflowPlacement === 'end' &&
66
+ overflowButton(overflowStart)));
67
+ })()))));
63
68
  });
64
69
  export default ButtonGroup;
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { Tooltip } from '../Tooltip/Tooltip.js';
2
3
  import type { ButtonProps } from './Button.js';
3
4
  import type { PolymorphicForwardRefComponent } from '../utils/index.js';
4
5
  export type IconButtonProps = {
@@ -11,6 +12,11 @@ export type IconButtonProps = {
11
12
  * Name of the button, shown in a tooltip and exposed to assistive technologies.
12
13
  */
13
14
  label?: React.ReactNode;
15
+ /**
16
+ * Props passed to the Tooltip that contains the `label`.
17
+ * Can be used for customizing the tooltip's `placement`, etc.
18
+ */
19
+ labelProps?: Omit<React.ComponentPropsWithoutRef<typeof Tooltip>, 'content' | 'reference' | 'ariaStrategy' | 'children'>;
14
20
  /**
15
21
  * Passes props to IconButton icon.
16
22
  */
@@ -7,6 +7,7 @@ import * as React from 'react';
7
7
  import { Box, ButtonBase } from '../utils/index.js';
8
8
  import { Tooltip } from '../Tooltip/Tooltip.js';
9
9
  import { VisuallyHidden } from '../VisuallyHidden/VisuallyHidden.js';
10
+ import { ButtonGroupContext } from '../ButtonGroup/ButtonGroup.js';
10
11
  /**
11
12
  * Icon button
12
13
  * @example
@@ -15,10 +16,11 @@ import { VisuallyHidden } from '../VisuallyHidden/VisuallyHidden.js';
15
16
  * <IconButton label='Add' styleType='borderless'><SvgAdd /></IconButton>
16
17
  */
17
18
  export const IconButton = React.forwardRef((props, ref) => {
18
- const { isActive, children, styleType = 'default', size, className, label, iconProps, ...rest } = props;
19
+ const { isActive, children, styleType = 'default', size, className, label, iconProps, labelProps, ...rest } = props;
20
+ const buttonGroupOrientation = React.useContext(ButtonGroupContext);
19
21
  const button = (React.createElement(ButtonBase, { ref: ref, className: cx('iui-button', className), "data-iui-variant": styleType !== 'default' ? styleType : undefined, "data-iui-size": size, "data-iui-active": isActive, "aria-pressed": isActive, ...rest },
20
22
  React.createElement(Box, { as: 'span', "aria-hidden": true, ...iconProps, className: cx('iui-button-icon', iconProps?.className) }, children),
21
23
  label ? React.createElement(VisuallyHidden, null, label) : null));
22
- return label ? (React.createElement(Tooltip, { content: label, ariaStrategy: 'none' }, button)) : (button);
24
+ return label ? (React.createElement(Tooltip, { placement: buttonGroupOrientation === 'vertical' ? 'right' : 'top', ...labelProps, content: label, ariaStrategy: 'none' }, button)) : (button);
23
25
  });
24
26
  export default IconButton;