@itwin/itwinui-react 3.10.0 → 3.10.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.10.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#2031](https://github.com/iTwin/iTwinUI/pull/2031): Fixed an issue where popovers and dropdown menus used with `SidenavButton` were showing up inside a tooltip.
8
+ - [#2030](https://github.com/iTwin/iTwinUI/pull/2030): Fixed a visual bug where items in a `ButtonGroup` were displaying a double border when an associated `Popover` was opened.
9
+ - [#2026](https://github.com/iTwin/iTwinUI/pull/2026): Submenus within a `DropdownMenu` will now consistently require less precision when moving the mouse between the parent item and the submenu.
10
+
3
11
  ## 3.10.0
4
12
 
5
13
  ### Minor Changes
@@ -83,7 +83,12 @@ exports.MenuItem = React.forwardRef((props, forwardedRef) => {
83
83
  const popover = (0, Popover_js_1.usePopover)({
84
84
  nodeId,
85
85
  visible: isSubmenuVisible,
86
- onVisibleChange: setIsSubmenuVisible,
86
+ onVisibleChange: React.useCallback((visible) => {
87
+ if (!visible) {
88
+ setHasFocusedNodeInSubmenu(false);
89
+ }
90
+ setIsSubmenuVisible(visible);
91
+ }, []),
87
92
  placement: 'right-start',
88
93
  interactions: !disabled
89
94
  ? {
@@ -128,7 +133,11 @@ exports.MenuItem = React.forwardRef((props, forwardedRef) => {
128
133
  }
129
134
  };
130
135
  const onFocus = () => {
131
- parent.setHasFocusedNodeInSubmenu?.(true);
136
+ // Set hasFocusedNodeInSubmenu in a microtask to ensure the submenu stays open reliably.
137
+ // E.g. Even when hovering into it rapidly.
138
+ queueMicrotask(() => {
139
+ parent.setHasFocusedNodeInSubmenu?.(true);
140
+ });
132
141
  tree?.events.emit('onNodeFocused', {
133
142
  nodeId,
134
143
  parentId,
@@ -90,7 +90,10 @@ const usePopover = (options) => {
90
90
  (0, react_1.useHover)(floating.context, {
91
91
  enabled: !!mergedInteractions.hover,
92
92
  delay: 100,
93
- handleClose: (0, react_1.safePolygon)({ buffer: 1, requireIntent: false }),
93
+ handleClose: (0, react_1.safePolygon)({
94
+ buffer: 1,
95
+ blockPointerEvents: true,
96
+ }),
94
97
  move: false,
95
98
  ...mergedInteractions.hover,
96
99
  }),
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
3
+ export declare const SidenavExpandedContext: React.Context<boolean | undefined>;
3
4
  type SideNavigationProps = {
4
5
  /**
5
6
  * Buttons shown in the top portion of sidenav.
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.SideNavigation = void 0;
29
+ exports.SideNavigation = exports.SidenavExpandedContext = void 0;
30
30
  /*---------------------------------------------------------------------------------------------
31
31
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
32
32
  * See LICENSE.md in the project root for license terms and full copyright notice.
@@ -35,7 +35,8 @@ const React = __importStar(require("react"));
35
35
  const classnames_1 = __importDefault(require("classnames"));
36
36
  const index_js_1 = require("../../utils/index.js");
37
37
  const IconButton_js_1 = require("../Buttons/IconButton.js");
38
- const Tooltip_js_1 = require("../Tooltip/Tooltip.js");
38
+ // ----------------------------------------------------------------------------
39
+ exports.SidenavExpandedContext = React.createContext(undefined);
39
40
  /**
40
41
  * Left side navigation menu component.
41
42
  * @example
@@ -51,25 +52,23 @@ const Tooltip_js_1 = require("../Tooltip/Tooltip.js");
51
52
  * />
52
53
  */
53
54
  exports.SideNavigation = React.forwardRef((props, forwardedRef) => {
54
- const { items, secondaryItems, expanderPlacement = 'top', className, isExpanded = false, onExpanderClick, submenu, isSubmenuOpen = false, wrapperProps, contentProps, topProps, bottomProps, ...rest } = props;
55
- const [_isExpanded, _setIsExpanded] = React.useState(isExpanded);
56
- React.useEffect(() => {
57
- _setIsExpanded(isExpanded);
58
- }, [isExpanded]);
59
- const ExpandButton = (React.createElement(IconButton_js_1.IconButton, { label: 'Toggle icon labels', "aria-expanded": _isExpanded, className: 'iui-sidenav-button iui-expand', size: 'small', onClick: React.useCallback(() => {
60
- _setIsExpanded((expanded) => !expanded);
55
+ const { items, secondaryItems, expanderPlacement = 'top', className, isExpanded: isExpandedProp, onExpanderClick, submenu, isSubmenuOpen = false, wrapperProps, contentProps, topProps, bottomProps, ...rest } = props;
56
+ const [isExpanded, setIsExpanded] = (0, index_js_1.useControlledState)(false, isExpandedProp);
57
+ const ExpandButton = (React.createElement(IconButton_js_1.IconButton, { label: 'Toggle icon labels', "aria-expanded": isExpanded, className: 'iui-sidenav-button iui-expand', size: 'small', onClick: React.useCallback(() => {
58
+ setIsExpanded((expanded) => !expanded);
61
59
  onExpanderClick?.();
62
- }, [onExpanderClick]) },
60
+ }, [onExpanderClick, setIsExpanded]) },
63
61
  React.createElement(index_js_1.SvgChevronRight, null)));
64
- return (React.createElement(index_js_1.Box, { ...wrapperProps, className: (0, classnames_1.default)('iui-side-navigation-wrapper', wrapperProps?.className), ref: forwardedRef },
65
- React.createElement(index_js_1.Box, { as: 'div', className: (0, classnames_1.default)('iui-side-navigation', {
66
- 'iui-expanded': _isExpanded,
67
- 'iui-collapsed': !_isExpanded,
68
- }, className), ...rest },
69
- expanderPlacement === 'top' && ExpandButton,
70
- React.createElement(index_js_1.Box, { as: 'div', ...contentProps, className: (0, classnames_1.default)('iui-sidenav-content', contentProps?.className) },
71
- React.createElement(index_js_1.Box, { as: 'div', ...topProps, className: (0, classnames_1.default)('iui-top', topProps?.className) }, items.map((sidenavButton, index) => !_isExpanded ? (React.createElement(Tooltip_js_1.Tooltip, { content: sidenavButton.props.children, placement: 'right', key: index }, sidenavButton)) : (sidenavButton))),
72
- React.createElement(index_js_1.Box, { as: 'div', ...bottomProps, className: (0, classnames_1.default)('iui-bottom', bottomProps?.className) }, secondaryItems?.map((sidenavButton, index) => !_isExpanded ? (React.createElement(Tooltip_js_1.Tooltip, { content: sidenavButton.props.children, placement: 'right', key: index }, sidenavButton)) : (sidenavButton)))),
73
- expanderPlacement === 'bottom' && ExpandButton),
74
- submenu && (React.createElement(index_js_1.WithCSSTransition, { in: isSubmenuOpen, dimension: 'width', timeout: 200, classNames: 'iui' }, submenu))));
62
+ return (React.createElement(exports.SidenavExpandedContext.Provider, { value: isExpanded },
63
+ React.createElement(index_js_1.Box, { ...wrapperProps, className: (0, classnames_1.default)('iui-side-navigation-wrapper', wrapperProps?.className), ref: forwardedRef },
64
+ React.createElement(index_js_1.Box, { as: 'div', className: (0, classnames_1.default)('iui-side-navigation', {
65
+ 'iui-expanded': isExpanded,
66
+ 'iui-collapsed': !isExpanded,
67
+ }, className), ...rest },
68
+ expanderPlacement === 'top' && ExpandButton,
69
+ React.createElement(index_js_1.Box, { as: 'div', ...contentProps, className: (0, classnames_1.default)('iui-sidenav-content', contentProps?.className) },
70
+ React.createElement(index_js_1.Box, { as: 'div', ...topProps, className: (0, classnames_1.default)('iui-top', topProps?.className) }, items),
71
+ React.createElement(index_js_1.Box, { as: 'div', ...bottomProps, className: (0, classnames_1.default)('iui-bottom', bottomProps?.className) }, secondaryItems)),
72
+ expanderPlacement === 'bottom' && ExpandButton),
73
+ submenu && (React.createElement(index_js_1.WithCSSTransition, { in: isSubmenuOpen, dimension: 'width', timeout: 200, classNames: 'iui' }, submenu)))));
75
74
  });
@@ -34,11 +34,15 @@ exports.SidenavButton = void 0;
34
34
  const classnames_1 = __importDefault(require("classnames"));
35
35
  const React = __importStar(require("react"));
36
36
  const Button_js_1 = require("../Buttons/Button.js");
37
+ const Tooltip_js_1 = require("../Tooltip/Tooltip.js");
38
+ const SideNavigation_js_1 = require("./SideNavigation.js");
37
39
  /**
38
40
  * Wrapper around Button to be used as SideNavigation items.
39
41
  * Label is hidden when sidenav is collapsed.
40
42
  */
41
43
  exports.SidenavButton = React.forwardRef((props, ref) => {
42
44
  const { className, children, isActive = false, disabled = false, isSubmenuOpen = false, ...rest } = props;
43
- return (React.createElement(Button_js_1.Button, { className: (0, classnames_1.default)('iui-sidenav-button', { 'iui-submenu-open': isSubmenuOpen }, className), "data-iui-active": isActive, size: 'large', disabled: disabled, ref: ref, ...rest }, children));
45
+ const isExpanded = React.useContext(SideNavigation_js_1.SidenavExpandedContext) === true;
46
+ const sidenavButton = (React.createElement(Button_js_1.Button, { className: (0, classnames_1.default)('iui-sidenav-button', { 'iui-submenu-open': isSubmenuOpen }, className), "data-iui-active": isActive, size: 'large', disabled: disabled, ref: ref, ...rest }, children));
47
+ return !isExpanded ? (React.createElement(Tooltip_js_1.Tooltip, { content: children, placement: 'right', ariaStrategy: 'none' }, sidenavButton)) : (sidenavButton);
44
48
  });
@@ -57,7 +57,12 @@ export const MenuItem = React.forwardRef((props, forwardedRef) => {
57
57
  const popover = usePopover({
58
58
  nodeId,
59
59
  visible: isSubmenuVisible,
60
- onVisibleChange: setIsSubmenuVisible,
60
+ onVisibleChange: React.useCallback((visible) => {
61
+ if (!visible) {
62
+ setHasFocusedNodeInSubmenu(false);
63
+ }
64
+ setIsSubmenuVisible(visible);
65
+ }, []),
61
66
  placement: 'right-start',
62
67
  interactions: !disabled
63
68
  ? {
@@ -102,7 +107,11 @@ export const MenuItem = React.forwardRef((props, forwardedRef) => {
102
107
  }
103
108
  };
104
109
  const onFocus = () => {
105
- parent.setHasFocusedNodeInSubmenu?.(true);
110
+ // Set hasFocusedNodeInSubmenu in a microtask to ensure the submenu stays open reliably.
111
+ // E.g. Even when hovering into it rapidly.
112
+ queueMicrotask(() => {
113
+ parent.setHasFocusedNodeInSubmenu?.(true);
114
+ });
106
115
  tree?.events.emit('onNodeFocused', {
107
116
  nodeId,
108
117
  parentId,
@@ -61,7 +61,10 @@ export const usePopover = (options) => {
61
61
  useHover(floating.context, {
62
62
  enabled: !!mergedInteractions.hover,
63
63
  delay: 100,
64
- handleClose: safePolygon({ buffer: 1, requireIntent: false }),
64
+ handleClose: safePolygon({
65
+ buffer: 1,
66
+ blockPointerEvents: true,
67
+ }),
65
68
  move: false,
66
69
  ...mergedInteractions.hover,
67
70
  }),
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
3
+ export declare const SidenavExpandedContext: React.Context<boolean | undefined>;
3
4
  type SideNavigationProps = {
4
5
  /**
5
6
  * Buttons shown in the top portion of sidenav.
@@ -4,9 +4,10 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import * as React from 'react';
6
6
  import cx from 'classnames';
7
- import { WithCSSTransition, SvgChevronRight, Box } from '../../utils/index.js';
7
+ import { WithCSSTransition, SvgChevronRight, Box, useControlledState, } from '../../utils/index.js';
8
8
  import { IconButton } from '../Buttons/IconButton.js';
9
- import { Tooltip } from '../Tooltip/Tooltip.js';
9
+ // ----------------------------------------------------------------------------
10
+ export const SidenavExpandedContext = React.createContext(undefined);
10
11
  /**
11
12
  * Left side navigation menu component.
12
13
  * @example
@@ -22,25 +23,23 @@ import { Tooltip } from '../Tooltip/Tooltip.js';
22
23
  * />
23
24
  */
24
25
  export const SideNavigation = React.forwardRef((props, forwardedRef) => {
25
- const { items, secondaryItems, expanderPlacement = 'top', className, isExpanded = false, onExpanderClick, submenu, isSubmenuOpen = false, wrapperProps, contentProps, topProps, bottomProps, ...rest } = props;
26
- const [_isExpanded, _setIsExpanded] = React.useState(isExpanded);
27
- React.useEffect(() => {
28
- _setIsExpanded(isExpanded);
29
- }, [isExpanded]);
30
- const ExpandButton = (React.createElement(IconButton, { label: 'Toggle icon labels', "aria-expanded": _isExpanded, className: 'iui-sidenav-button iui-expand', size: 'small', onClick: React.useCallback(() => {
31
- _setIsExpanded((expanded) => !expanded);
26
+ const { items, secondaryItems, expanderPlacement = 'top', className, isExpanded: isExpandedProp, onExpanderClick, submenu, isSubmenuOpen = false, wrapperProps, contentProps, topProps, bottomProps, ...rest } = props;
27
+ const [isExpanded, setIsExpanded] = useControlledState(false, isExpandedProp);
28
+ const ExpandButton = (React.createElement(IconButton, { label: 'Toggle icon labels', "aria-expanded": isExpanded, className: 'iui-sidenav-button iui-expand', size: 'small', onClick: React.useCallback(() => {
29
+ setIsExpanded((expanded) => !expanded);
32
30
  onExpanderClick?.();
33
- }, [onExpanderClick]) },
31
+ }, [onExpanderClick, setIsExpanded]) },
34
32
  React.createElement(SvgChevronRight, null)));
35
- return (React.createElement(Box, { ...wrapperProps, className: cx('iui-side-navigation-wrapper', wrapperProps?.className), ref: forwardedRef },
36
- React.createElement(Box, { as: 'div', className: cx('iui-side-navigation', {
37
- 'iui-expanded': _isExpanded,
38
- 'iui-collapsed': !_isExpanded,
39
- }, className), ...rest },
40
- expanderPlacement === 'top' && ExpandButton,
41
- React.createElement(Box, { as: 'div', ...contentProps, className: cx('iui-sidenav-content', contentProps?.className) },
42
- React.createElement(Box, { as: 'div', ...topProps, className: cx('iui-top', topProps?.className) }, items.map((sidenavButton, index) => !_isExpanded ? (React.createElement(Tooltip, { content: sidenavButton.props.children, placement: 'right', key: index }, sidenavButton)) : (sidenavButton))),
43
- React.createElement(Box, { as: 'div', ...bottomProps, className: cx('iui-bottom', bottomProps?.className) }, secondaryItems?.map((sidenavButton, index) => !_isExpanded ? (React.createElement(Tooltip, { content: sidenavButton.props.children, placement: 'right', key: index }, sidenavButton)) : (sidenavButton)))),
44
- expanderPlacement === 'bottom' && ExpandButton),
45
- submenu && (React.createElement(WithCSSTransition, { in: isSubmenuOpen, dimension: 'width', timeout: 200, classNames: 'iui' }, submenu))));
33
+ return (React.createElement(SidenavExpandedContext.Provider, { value: isExpanded },
34
+ React.createElement(Box, { ...wrapperProps, className: cx('iui-side-navigation-wrapper', wrapperProps?.className), ref: forwardedRef },
35
+ React.createElement(Box, { as: 'div', className: cx('iui-side-navigation', {
36
+ 'iui-expanded': isExpanded,
37
+ 'iui-collapsed': !isExpanded,
38
+ }, className), ...rest },
39
+ expanderPlacement === 'top' && ExpandButton,
40
+ React.createElement(Box, { as: 'div', ...contentProps, className: cx('iui-sidenav-content', contentProps?.className) },
41
+ React.createElement(Box, { as: 'div', ...topProps, className: cx('iui-top', topProps?.className) }, items),
42
+ React.createElement(Box, { as: 'div', ...bottomProps, className: cx('iui-bottom', bottomProps?.className) }, secondaryItems)),
43
+ expanderPlacement === 'bottom' && ExpandButton),
44
+ submenu && (React.createElement(WithCSSTransition, { in: isSubmenuOpen, dimension: 'width', timeout: 200, classNames: 'iui' }, submenu)))));
46
45
  });
@@ -5,11 +5,15 @@
5
5
  import cx from 'classnames';
6
6
  import * as React from 'react';
7
7
  import { Button } from '../Buttons/Button.js';
8
+ import { Tooltip } from '../Tooltip/Tooltip.js';
9
+ import { SidenavExpandedContext } from './SideNavigation.js';
8
10
  /**
9
11
  * Wrapper around Button to be used as SideNavigation items.
10
12
  * Label is hidden when sidenav is collapsed.
11
13
  */
12
14
  export const SidenavButton = React.forwardRef((props, ref) => {
13
15
  const { className, children, isActive = false, disabled = false, isSubmenuOpen = false, ...rest } = props;
14
- return (React.createElement(Button, { className: cx('iui-sidenav-button', { 'iui-submenu-open': isSubmenuOpen }, className), "data-iui-active": isActive, size: 'large', disabled: disabled, ref: ref, ...rest }, children));
16
+ const isExpanded = React.useContext(SidenavExpandedContext) === true;
17
+ const sidenavButton = (React.createElement(Button, { className: cx('iui-sidenav-button', { 'iui-submenu-open': isSubmenuOpen }, className), "data-iui-active": isActive, size: 'large', disabled: disabled, ref: ref, ...rest }, children));
18
+ return !isExpanded ? (React.createElement(Tooltip, { content: children, placement: 'right', ariaStrategy: 'none' }, sidenavButton)) : (sidenavButton);
15
19
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itwin/itwinui-react",
3
- "version": "3.10.0",
3
+ "version": "3.10.1",
4
4
  "author": "Bentley Systems",
5
5
  "license": "MIT",
6
6
  "type": "module",