@carbon-labs/react-ui-shell 0.25.0 → 0.27.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.
- package/es/components/SharkFinIcon.d.ts +24 -0
- package/es/components/SharkFinIcon.js +59 -0
- package/es/components/SideNav.js +8 -4
- package/es/components/SideNavFlyoutMenu.d.ts +141 -0
- package/es/components/SideNavFlyoutMenu.js +365 -0
- package/es/components/SideNavMenu.d.ts +4 -0
- package/es/components/SideNavMenu.js +96 -75
- package/es/components/SideNavMenuItem.d.ts +4 -0
- package/es/components/SideNavMenuItem.js +17 -4
- package/es/components/SideNavToggle.d.ts +4 -0
- package/es/components/SideNavToggle.js +9 -1
- package/es/index.d.ts +1 -0
- package/es/index.js +1 -0
- package/es/internal/keyboard/keys.js +13 -1
- package/es/internal/setupGetInstanceId.d.ts +11 -0
- package/es/internal/setupGetInstanceId.js +19 -0
- package/es/internal/useId.d.ts +21 -0
- package/es/internal/useId.js +100 -0
- package/es/internal/useIdPrefix.d.ts +12 -0
- package/es/internal/useIdPrefix.js +19 -0
- package/es/internal/usePrefix.d.ts +5 -0
- package/es/internal/usePrefix.js +6 -0
- package/lib/components/SharkFinIcon.d.ts +24 -0
- package/lib/components/SharkFinIcon.js +61 -0
- package/lib/components/SideNav.js +8 -4
- package/lib/components/SideNavFlyoutMenu.d.ts +141 -0
- package/lib/components/SideNavFlyoutMenu.js +367 -0
- package/lib/components/SideNavMenu.d.ts +4 -0
- package/lib/components/SideNavMenu.js +96 -75
- package/lib/components/SideNavMenuItem.d.ts +4 -0
- package/lib/components/SideNavMenuItem.js +17 -4
- package/lib/components/SideNavToggle.d.ts +4 -0
- package/lib/components/SideNavToggle.js +9 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +2 -0
- package/lib/internal/keyboard/keys.js +14 -0
- package/lib/internal/setupGetInstanceId.d.ts +11 -0
- package/lib/internal/setupGetInstanceId.js +23 -0
- package/lib/internal/useId.d.ts +21 -0
- package/lib/internal/useId.js +103 -0
- package/lib/internal/useIdPrefix.d.ts +12 -0
- package/lib/internal/useIdPrefix.js +22 -0
- package/lib/internal/usePrefix.d.ts +5 -0
- package/lib/internal/usePrefix.js +6 -0
- package/package.json +2 -2
- package/scss/styles/_shark-fin-icon.scss +14 -0
- package/scss/styles/_side-nav.scss +158 -0
- package/scss/ui-shell.scss +1 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2024
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
var index = require('../_virtual/index.js');
|
|
11
|
+
var PropTypes = require('prop-types');
|
|
12
|
+
var React = require('react');
|
|
13
|
+
var usePrefix = require('../internal/usePrefix.js');
|
|
14
|
+
|
|
15
|
+
var _g, _defs;
|
|
16
|
+
const SharkFinIcon = _ref => {
|
|
17
|
+
let {
|
|
18
|
+
className: customClassName
|
|
19
|
+
} = _ref;
|
|
20
|
+
const prefix = usePrefix.usePrefix();
|
|
21
|
+
const className = index.default({
|
|
22
|
+
[`${prefix}--side-nav__icon`]: true,
|
|
23
|
+
[`${prefix}--shark-fin__icon`]: true,
|
|
24
|
+
[customClassName]: !!customClassName
|
|
25
|
+
});
|
|
26
|
+
return /*#__PURE__*/React.createElement("svg", {
|
|
27
|
+
className: className,
|
|
28
|
+
width: "4",
|
|
29
|
+
height: "4",
|
|
30
|
+
viewBox: "0 0 4 4",
|
|
31
|
+
fill: "none",
|
|
32
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
33
|
+
}, _g || (_g = /*#__PURE__*/React.createElement("g", {
|
|
34
|
+
"clip-path": "url(#clip0_519_52879)"
|
|
35
|
+
}, /*#__PURE__*/React.createElement("path", {
|
|
36
|
+
d: "M2 2L4 0V4H0L2 2Z"
|
|
37
|
+
}))), _defs || (_defs = /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("clipPath", {
|
|
38
|
+
id: "clip0_519_52879"
|
|
39
|
+
}, /*#__PURE__*/React.createElement("rect", {
|
|
40
|
+
width: "4",
|
|
41
|
+
height: "4",
|
|
42
|
+
fill: "white"
|
|
43
|
+
})))));
|
|
44
|
+
};
|
|
45
|
+
SharkFinIcon.propTypes = {
|
|
46
|
+
/**
|
|
47
|
+
* Provide a single icon as the child to `SharkfinIcon` to render in the
|
|
48
|
+
* container
|
|
49
|
+
*/
|
|
50
|
+
children: PropTypes.node,
|
|
51
|
+
/**
|
|
52
|
+
* Provide an optional class to be applied to the containing node
|
|
53
|
+
*/
|
|
54
|
+
className: PropTypes.string,
|
|
55
|
+
/**
|
|
56
|
+
* Specify whether the icon should be placed in a smaller bounding box
|
|
57
|
+
*/
|
|
58
|
+
small: PropTypes.bool
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
exports.SharkFinIcon = SharkFinIcon;
|
|
@@ -346,6 +346,11 @@ function SideNavRenderFunction(_ref, ref) {
|
|
|
346
346
|
setInternalIsTreeview(value);
|
|
347
347
|
}
|
|
348
348
|
};
|
|
349
|
+
const SideNavToggleButton = /*#__PURE__*/React.createElement(SideNavToggle.SideNavToggle, {
|
|
350
|
+
className: !expandedState ? `${prefix}--side-nav__toggle--collapsed` : '',
|
|
351
|
+
renderIcon: expandedState ? bucket15.SidePanelClose : bucket15.SidePanelOpen,
|
|
352
|
+
onClick: () => setExpandedState(!expandedState)
|
|
353
|
+
}, sideNavToggleText);
|
|
349
354
|
return /*#__PURE__*/React.createElement(SideNavContext.Provider, {
|
|
350
355
|
value: {
|
|
351
356
|
expanded,
|
|
@@ -366,10 +371,9 @@ function SideNavRenderFunction(_ref, ref) {
|
|
|
366
371
|
ref: navRef,
|
|
367
372
|
className: `${prefix}--side-nav__navigation ${className}`,
|
|
368
373
|
inert: !isRail && navType !== SIDE_NAV_TYPE.PANEL && !(expanded || isLg) ? -1 : undefined
|
|
369
|
-
}, accessibilityLabel, eventHandlers, other), childrenToRender, navType === SIDE_NAV_TYPE.PANEL && /*#__PURE__*/React.createElement(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}, sideNavToggleText)));
|
|
374
|
+
}, accessibilityLabel, eventHandlers, other), childrenToRender, navType === SIDE_NAV_TYPE.PANEL && (expandedState ? SideNavToggleButton : /*#__PURE__*/React.createElement("div", {
|
|
375
|
+
className: `${prefix}--side-nav__toggle-container`
|
|
376
|
+
}, SideNavToggleButton))));
|
|
373
377
|
}
|
|
374
378
|
const SideNav = /*#__PURE__*/React.forwardRef(SideNavRenderFunction);
|
|
375
379
|
SideNav.displayName = 'SideNav';
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2016, 2023
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import PropTypes from 'prop-types';
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { PopoverAlignment } from '@carbon/react';
|
|
10
|
+
import { type PolymorphicProps } from '../types/common';
|
|
11
|
+
interface TooltipBaseProps {
|
|
12
|
+
/**
|
|
13
|
+
* Specify how the trigger should align with the tooltip
|
|
14
|
+
*/
|
|
15
|
+
align?: PopoverAlignment;
|
|
16
|
+
/**
|
|
17
|
+
* Pass in the child to which the tooltip will be applied
|
|
18
|
+
*/
|
|
19
|
+
children?: React.ReactElement;
|
|
20
|
+
/**
|
|
21
|
+
* Specify an optional className to be applied to the container node
|
|
22
|
+
*/
|
|
23
|
+
className?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Specify whether the tooltip should be open when it first renders
|
|
26
|
+
*/
|
|
27
|
+
defaultOpen?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Provide the description to be rendered inside of the Tooltip. The
|
|
30
|
+
* description will use `aria-describedby` and will describe the child node
|
|
31
|
+
* in addition to the text rendered inside of the child. This means that if you
|
|
32
|
+
* have text in the child node, that it will be announced alongside the
|
|
33
|
+
* description to the screen reader.
|
|
34
|
+
*
|
|
35
|
+
* Note: if label and description are both provided, label will be used and
|
|
36
|
+
* description will not be used
|
|
37
|
+
*/
|
|
38
|
+
description?: React.ReactNode;
|
|
39
|
+
/**
|
|
40
|
+
* Specify whether a drop shadow should be rendered
|
|
41
|
+
*/
|
|
42
|
+
dropShadow?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Specify the duration in milliseconds to delay before displaying the tooltip
|
|
45
|
+
*/
|
|
46
|
+
enterDelayMs?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Provide the label to be rendered inside of the Tooltip. The label will use
|
|
49
|
+
* `aria-labelledby` and will fully describe the child node that is provided.
|
|
50
|
+
* This means that if you have text in the child node, that it will not be
|
|
51
|
+
* announced to the screen reader.
|
|
52
|
+
*
|
|
53
|
+
* Note: if label and description are both provided, description will not be
|
|
54
|
+
* used
|
|
55
|
+
*/
|
|
56
|
+
label?: React.ReactNode;
|
|
57
|
+
/**
|
|
58
|
+
* Specify the duration in milliseconds to delay before hiding the tooltip
|
|
59
|
+
*/
|
|
60
|
+
leaveDelayMs?: number;
|
|
61
|
+
/**
|
|
62
|
+
* The content to be rendered inside the popover menu.
|
|
63
|
+
*/
|
|
64
|
+
menuContent?: React.ReactNode;
|
|
65
|
+
/**
|
|
66
|
+
* The boolean to show the flyout menu has been selected.
|
|
67
|
+
*/
|
|
68
|
+
selected?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* The title for the overall menu name.
|
|
71
|
+
*/
|
|
72
|
+
title?: string;
|
|
73
|
+
}
|
|
74
|
+
export type TooltipProps<T extends React.ElementType> = PolymorphicProps<T, TooltipBaseProps>;
|
|
75
|
+
declare function SideNavFlyoutMenu<T extends React.ElementType>({ align, className: customClassName, children, label, description, enterDelayMs, leaveDelayMs, defaultOpen, dropShadow, highContrast, menuContent, selected, title, ...rest }: TooltipProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
76
|
+
declare namespace SideNavFlyoutMenu {
|
|
77
|
+
var propTypes: {
|
|
78
|
+
/**
|
|
79
|
+
* Specify how the trigger should align with the tooltip
|
|
80
|
+
*/
|
|
81
|
+
align: PropTypes.Requireable<string>;
|
|
82
|
+
/**
|
|
83
|
+
* Pass in the child to which the tooltip will be applied
|
|
84
|
+
*/
|
|
85
|
+
children: PropTypes.Requireable<PropTypes.ReactNodeLike>;
|
|
86
|
+
/**
|
|
87
|
+
* Specify an optional className to be applied to the container node
|
|
88
|
+
*/
|
|
89
|
+
className: PropTypes.Requireable<string>;
|
|
90
|
+
/**
|
|
91
|
+
* Specify whether the tooltip should be open when it first renders
|
|
92
|
+
*/
|
|
93
|
+
defaultOpen: PropTypes.Requireable<boolean>;
|
|
94
|
+
/**
|
|
95
|
+
* Provide the description to be rendered inside of the Tooltip. The
|
|
96
|
+
* description will use `aria-describedby` and will describe the child node
|
|
97
|
+
* in addition to the text rendered inside of the child. This means that if you
|
|
98
|
+
* have text in the child node, that it will be announced alongside the
|
|
99
|
+
* description to the screen reader.
|
|
100
|
+
*
|
|
101
|
+
* Note: if label and description are both provided, label will be used and
|
|
102
|
+
* description will not be used
|
|
103
|
+
*/
|
|
104
|
+
description: PropTypes.Requireable<PropTypes.ReactNodeLike>;
|
|
105
|
+
/**
|
|
106
|
+
* Specify whether a drop shadow should be rendered
|
|
107
|
+
*/
|
|
108
|
+
dropShadow: PropTypes.Requireable<boolean>;
|
|
109
|
+
/**
|
|
110
|
+
* Specify the duration in milliseconds to delay before displaying the tooltip
|
|
111
|
+
*/
|
|
112
|
+
enterDelayMs: PropTypes.Requireable<number>;
|
|
113
|
+
/**
|
|
114
|
+
* Provide the label to be rendered inside of the Tooltip. The label will use
|
|
115
|
+
* `aria-labelledby` and will fully describe the child node that is provided.
|
|
116
|
+
* This means that if you have text in the child node, that it will not be
|
|
117
|
+
* announced to the screen reader.
|
|
118
|
+
*
|
|
119
|
+
* Note: if label and description are both provided, description will not be
|
|
120
|
+
* used
|
|
121
|
+
*/
|
|
122
|
+
label: PropTypes.Requireable<PropTypes.ReactNodeLike>;
|
|
123
|
+
/**
|
|
124
|
+
* Specify the duration in milliseconds to delay before hiding the tooltip
|
|
125
|
+
*/
|
|
126
|
+
leaveDelayMs: PropTypes.Requireable<number>;
|
|
127
|
+
/**
|
|
128
|
+
* The content to be rendered inside the popover menu.
|
|
129
|
+
*/
|
|
130
|
+
menuContent: PropTypes.Requireable<PropTypes.ReactNodeLike>;
|
|
131
|
+
/**
|
|
132
|
+
* The boolean to show the flyout menu has been selected.
|
|
133
|
+
*/
|
|
134
|
+
selected: PropTypes.Requireable<boolean>;
|
|
135
|
+
/**
|
|
136
|
+
* The title for the overall menu name.
|
|
137
|
+
*/
|
|
138
|
+
title: PropTypes.Requireable<string>;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export { SideNavFlyoutMenu };
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright IBM Corp. 2024
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.js');
|
|
11
|
+
var index = require('../_virtual/index.js');
|
|
12
|
+
var PropTypes = require('prop-types');
|
|
13
|
+
var React = require('react');
|
|
14
|
+
var react = require('@carbon/react');
|
|
15
|
+
var keys = require('../internal/keyboard/keys.js');
|
|
16
|
+
var match = require('../internal/keyboard/match.js');
|
|
17
|
+
var useDelayedState = require('../internal/useDelayedState.js');
|
|
18
|
+
var useId = require('../internal/useId.js');
|
|
19
|
+
var usePrefix = require('../internal/usePrefix.js');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Event types that trigger a "drag" to stop.
|
|
23
|
+
*/
|
|
24
|
+
const DRAG_STOP_EVENT_TYPES = new Set(['mouseup', 'touchend', 'touchcancel']);
|
|
25
|
+
function SideNavFlyoutMenu(_ref) {
|
|
26
|
+
let {
|
|
27
|
+
align = 'right-start',
|
|
28
|
+
className: customClassName,
|
|
29
|
+
children,
|
|
30
|
+
label,
|
|
31
|
+
description,
|
|
32
|
+
enterDelayMs = 100,
|
|
33
|
+
leaveDelayMs = 300,
|
|
34
|
+
defaultOpen = false,
|
|
35
|
+
dropShadow = true,
|
|
36
|
+
highContrast = false,
|
|
37
|
+
menuContent,
|
|
38
|
+
selected = false,
|
|
39
|
+
title,
|
|
40
|
+
...rest
|
|
41
|
+
} = _ref;
|
|
42
|
+
const popoverRef = React.useRef(null);
|
|
43
|
+
const [open, setOpen] = useDelayedState.useDelayedState(defaultOpen);
|
|
44
|
+
const [isDragging, setIsDragging] = React.useState(false);
|
|
45
|
+
const [focusByMouse, setFocusByMouse] = React.useState(false);
|
|
46
|
+
const [isPointerIntersecting, setIsPointerIntersecting] = React.useState(false);
|
|
47
|
+
const id = useId.useId('tooltip');
|
|
48
|
+
const prefix = usePrefix.usePrefix();
|
|
49
|
+
const child = React.Children.only(children);
|
|
50
|
+
const menuButton = React.useRef();
|
|
51
|
+
const [clickMode, setClickMode] = React.useState(false);
|
|
52
|
+
const isFocusedInsideRef = React.useRef(false);
|
|
53
|
+
const popoverMenuLinks = React.useRef(null);
|
|
54
|
+
const [isButtonFocused, setIsButtonFocused] = React.useState(false);
|
|
55
|
+
const flyoutMenuItems = React.Children.map(menuContent, child => {
|
|
56
|
+
if (/*#__PURE__*/React.isValidElement(child)) {
|
|
57
|
+
return /*#__PURE__*/React.cloneElement(child, {
|
|
58
|
+
...{
|
|
59
|
+
isFlyoutMenuItem: true
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return child;
|
|
64
|
+
});
|
|
65
|
+
const triggerProps = {
|
|
66
|
+
onFocus: () => {
|
|
67
|
+
if (!focusByMouse) {
|
|
68
|
+
setOpen(true);
|
|
69
|
+
setIsButtonFocused(true);
|
|
70
|
+
setClickMode(false);
|
|
71
|
+
isFocusedInsideRef.current = false;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
onBlur: () => {
|
|
75
|
+
if (!isFocusedInsideRef.current && !focusByMouse) {
|
|
76
|
+
closeMenu();
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
onClick: () => {
|
|
80
|
+
setIsButtonFocused(false);
|
|
81
|
+
setClickMode(!clickMode);
|
|
82
|
+
setOpen(!clickMode);
|
|
83
|
+
},
|
|
84
|
+
// This should be placed on the trigger in case the element is disabled
|
|
85
|
+
onMouseEnter,
|
|
86
|
+
onMouseLeave,
|
|
87
|
+
onMouseDown,
|
|
88
|
+
onMouseMove: onMouseMove,
|
|
89
|
+
onTouchStart: onDragStart
|
|
90
|
+
};
|
|
91
|
+
function getChildEventHandlers(childProps) {
|
|
92
|
+
const eventHandlerFunctions = Object.keys(triggerProps).filter(prop => prop.startsWith('on'));
|
|
93
|
+
const eventHandlers = {};
|
|
94
|
+
eventHandlerFunctions.forEach(functionName => {
|
|
95
|
+
eventHandlers[functionName] = evt => {
|
|
96
|
+
triggerProps[functionName](evt);
|
|
97
|
+
if (childProps?.[functionName]) {
|
|
98
|
+
childProps?.[functionName](evt);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
return eventHandlers;
|
|
103
|
+
}
|
|
104
|
+
if (label) {
|
|
105
|
+
triggerProps['aria-labelledby'] = id;
|
|
106
|
+
} else {
|
|
107
|
+
triggerProps['aria-describedby'] = id;
|
|
108
|
+
}
|
|
109
|
+
function onKeyDown(event) {
|
|
110
|
+
if (open && match.match(event, keys.Escape)) {
|
|
111
|
+
event.stopPropagation();
|
|
112
|
+
if (!isButtonFocused) {
|
|
113
|
+
closeMenu({
|
|
114
|
+
revertFocus: true
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
setOpen(!isButtonFocused);
|
|
118
|
+
setIsButtonFocused(!isButtonFocused);
|
|
119
|
+
}
|
|
120
|
+
if (match.match(event, keys.Enter) || match.match(event, keys.Space)) {
|
|
121
|
+
setIsButtonFocused(false);
|
|
122
|
+
setOpen(true);
|
|
123
|
+
setFocusByMouse(false);
|
|
124
|
+
|
|
125
|
+
// focus on the first link
|
|
126
|
+
if (popoverMenuLinks?.current && !isFocusedInsideRef.current) {
|
|
127
|
+
const firstLink = popoverMenuLinks?.current?.[0];
|
|
128
|
+
setContentTabIndex('0');
|
|
129
|
+
if (firstLink) {
|
|
130
|
+
isFocusedInsideRef.current = true;
|
|
131
|
+
// avoid race condition
|
|
132
|
+
setTimeout(() => firstLink.focus(), 0);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function onMouseEnter() {
|
|
138
|
+
if (!clickMode) {
|
|
139
|
+
if (!rest?.onMouseEnter) {
|
|
140
|
+
setIsPointerIntersecting(true);
|
|
141
|
+
setOpen(true, enterDelayMs);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function onMouseDown() {
|
|
146
|
+
setFocusByMouse(true);
|
|
147
|
+
onDragStart();
|
|
148
|
+
}
|
|
149
|
+
function onMouseLeave() {
|
|
150
|
+
if (!clickMode && !isFocusedInsideRef.current) {
|
|
151
|
+
setIsPointerIntersecting(false);
|
|
152
|
+
if (isDragging) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
setIsButtonFocused(false);
|
|
156
|
+
setOpen(false, leaveDelayMs);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function onMouseMove(evt) {
|
|
160
|
+
if (evt.buttons === 1) {
|
|
161
|
+
setIsDragging(true);
|
|
162
|
+
} else {
|
|
163
|
+
setIsDragging(false);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function onDragStart() {
|
|
167
|
+
setIsDragging(true);
|
|
168
|
+
}
|
|
169
|
+
const onDragStop = React.useCallback(() => {
|
|
170
|
+
setIsDragging(false);
|
|
171
|
+
// Close the tooltip, unless the mouse pointer is within the bounds of the
|
|
172
|
+
// trigger.
|
|
173
|
+
if (!isPointerIntersecting) {
|
|
174
|
+
setIsButtonFocused(false);
|
|
175
|
+
setOpen(false, leaveDelayMs);
|
|
176
|
+
}
|
|
177
|
+
}, [isPointerIntersecting, leaveDelayMs, setOpen]);
|
|
178
|
+
React.useEffect(() => {
|
|
179
|
+
if (isDragging) {
|
|
180
|
+
// Register drag stop handlers.
|
|
181
|
+
DRAG_STOP_EVENT_TYPES.forEach(eventType => {
|
|
182
|
+
document.addEventListener(eventType, onDragStop);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return () => {
|
|
186
|
+
DRAG_STOP_EVENT_TYPES.forEach(eventType => {
|
|
187
|
+
document.removeEventListener(eventType, onDragStop);
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
}, [isDragging, onDragStop]);
|
|
191
|
+
const setContentTabIndex = value => {
|
|
192
|
+
if (popoverMenuLinks.current) {
|
|
193
|
+
popoverMenuLinks.current.forEach(e => e.setAttribute('tabindex', value));
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
function handleBlur(event) {
|
|
197
|
+
const isFocusOutside = !popoverRef.current?.contains(event.relatedTarget);
|
|
198
|
+
|
|
199
|
+
// tab outside the menu
|
|
200
|
+
if (open && isFocusedInsideRef.current && !focusByMouse && isFocusOutside) {
|
|
201
|
+
closeMenu();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// if button has been clicked, and new click is outside the menu
|
|
205
|
+
if (clickMode && isFocusOutside) {
|
|
206
|
+
closeMenu();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function closeMenu() {
|
|
210
|
+
let {
|
|
211
|
+
revertFocus = false
|
|
212
|
+
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
213
|
+
if (revertFocus) {
|
|
214
|
+
menuButton.current?.focus();
|
|
215
|
+
setOpen(true);
|
|
216
|
+
setIsButtonFocused(true);
|
|
217
|
+
} else {
|
|
218
|
+
setOpen(false);
|
|
219
|
+
setIsButtonFocused(false);
|
|
220
|
+
}
|
|
221
|
+
isFocusedInsideRef.current = false;
|
|
222
|
+
setFocusByMouse(false);
|
|
223
|
+
setIsPointerIntersecting(!clickMode);
|
|
224
|
+
setClickMode(false);
|
|
225
|
+
setContentTabIndex('-1');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// initiate menu content to be untabbable
|
|
229
|
+
React.useEffect(() => {
|
|
230
|
+
if (popoverRef.current) {
|
|
231
|
+
popoverMenuLinks.current = popoverRef.current.querySelectorAll(`.${prefix}--side-nav-menu--popover-content a`);
|
|
232
|
+
setContentTabIndex('-1');
|
|
233
|
+
menuButton.current = popoverRef.current.querySelector(`.${prefix}--side-nav__submenu`);
|
|
234
|
+
}
|
|
235
|
+
}, []);
|
|
236
|
+
|
|
237
|
+
// set menu content to be untabbable
|
|
238
|
+
React.useEffect(() => {
|
|
239
|
+
if (popoverRef.current && !popoverMenuLinks.current) {
|
|
240
|
+
popoverMenuLinks.current = popoverRef.current.querySelectorAll(`.${prefix}--side-nav-menu--popover-content a`);
|
|
241
|
+
setContentTabIndex('-1');
|
|
242
|
+
}
|
|
243
|
+
}, [open]);
|
|
244
|
+
return (
|
|
245
|
+
/*#__PURE__*/
|
|
246
|
+
// @ts-ignore-error Popover throws a TS error everytime is imported
|
|
247
|
+
React.createElement(react.Popover, _rollupPluginBabelHelpers.extends({
|
|
248
|
+
ref: popoverRef
|
|
249
|
+
}, rest, {
|
|
250
|
+
align: isButtonFocused ? 'right' : align,
|
|
251
|
+
className: index.default(customClassName, {
|
|
252
|
+
[`${prefix}--flyout-menu`]: true,
|
|
253
|
+
[`${prefix}--flyout-menu-clicked`]: clickMode,
|
|
254
|
+
[`${prefix}--flyout-menu-selected`]: selected,
|
|
255
|
+
[`${prefix}--flyout-menu-focused`]: isButtonFocused || open
|
|
256
|
+
}),
|
|
257
|
+
dropShadow: dropShadow,
|
|
258
|
+
highContrast: false,
|
|
259
|
+
onBlur: handleBlur,
|
|
260
|
+
onKeyDown: onKeyDown,
|
|
261
|
+
onMouseLeave: onMouseLeave,
|
|
262
|
+
open: open
|
|
263
|
+
}), child !== undefined ? /*#__PURE__*/React.cloneElement(child, {
|
|
264
|
+
...triggerProps,
|
|
265
|
+
...getChildEventHandlers(child.props)
|
|
266
|
+
}) : null, /*#__PURE__*/React.createElement(react.PopoverContent, {
|
|
267
|
+
"aria-hidden": open ? 'false' : 'true',
|
|
268
|
+
className: index.default({
|
|
269
|
+
[`${prefix}--side-nav-menu--popover-content`]: !isButtonFocused,
|
|
270
|
+
[`${prefix}--flyout-tooltip-content ${prefix}--tooltip-content`]: isButtonFocused
|
|
271
|
+
}),
|
|
272
|
+
id: id,
|
|
273
|
+
onMouseEnter: onMouseEnter,
|
|
274
|
+
role: "tooltip"
|
|
275
|
+
}, !isButtonFocused ? /*#__PURE__*/React.createElement(react.FormLabel, null, title) : title, /*#__PURE__*/React.createElement("div", {
|
|
276
|
+
style: {
|
|
277
|
+
display: isButtonFocused ? 'none' : 'block'
|
|
278
|
+
}
|
|
279
|
+
}, flyoutMenuItems)))
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
SideNavFlyoutMenu.propTypes = {
|
|
283
|
+
/**
|
|
284
|
+
* Specify how the trigger should align with the tooltip
|
|
285
|
+
*/
|
|
286
|
+
align: PropTypes.oneOf(['top', 'top-left',
|
|
287
|
+
// deprecated use top-start instead
|
|
288
|
+
'top-right',
|
|
289
|
+
// deprecated use top-end instead
|
|
290
|
+
|
|
291
|
+
'bottom', 'bottom-left',
|
|
292
|
+
// deprecated use bottom-start instead
|
|
293
|
+
'bottom-right',
|
|
294
|
+
// deprecated use bottom-end instead
|
|
295
|
+
|
|
296
|
+
'left', 'left-bottom',
|
|
297
|
+
// deprecated use left-end instead
|
|
298
|
+
'left-top',
|
|
299
|
+
// deprecated use left-start instead
|
|
300
|
+
|
|
301
|
+
'right', 'right-bottom',
|
|
302
|
+
// deprecated use right-end instead
|
|
303
|
+
'right-top',
|
|
304
|
+
// deprecated use right-start instead
|
|
305
|
+
|
|
306
|
+
// new values to match floating-ui
|
|
307
|
+
'top-start', 'top-end', 'bottom-start', 'bottom-end', 'left-end', 'left-start', 'right-end', 'right-start']),
|
|
308
|
+
/**
|
|
309
|
+
* Pass in the child to which the tooltip will be applied
|
|
310
|
+
*/
|
|
311
|
+
children: PropTypes.node,
|
|
312
|
+
/**
|
|
313
|
+
* Specify an optional className to be applied to the container node
|
|
314
|
+
*/
|
|
315
|
+
className: PropTypes.string,
|
|
316
|
+
/**
|
|
317
|
+
* Specify whether the tooltip should be open when it first renders
|
|
318
|
+
*/
|
|
319
|
+
defaultOpen: PropTypes.bool,
|
|
320
|
+
/**
|
|
321
|
+
* Provide the description to be rendered inside of the Tooltip. The
|
|
322
|
+
* description will use `aria-describedby` and will describe the child node
|
|
323
|
+
* in addition to the text rendered inside of the child. This means that if you
|
|
324
|
+
* have text in the child node, that it will be announced alongside the
|
|
325
|
+
* description to the screen reader.
|
|
326
|
+
*
|
|
327
|
+
* Note: if label and description are both provided, label will be used and
|
|
328
|
+
* description will not be used
|
|
329
|
+
*/
|
|
330
|
+
description: PropTypes.node,
|
|
331
|
+
/**
|
|
332
|
+
* Specify whether a drop shadow should be rendered
|
|
333
|
+
*/
|
|
334
|
+
dropShadow: PropTypes.bool,
|
|
335
|
+
/**
|
|
336
|
+
* Specify the duration in milliseconds to delay before displaying the tooltip
|
|
337
|
+
*/
|
|
338
|
+
enterDelayMs: PropTypes.number,
|
|
339
|
+
/**
|
|
340
|
+
* Provide the label to be rendered inside of the Tooltip. The label will use
|
|
341
|
+
* `aria-labelledby` and will fully describe the child node that is provided.
|
|
342
|
+
* This means that if you have text in the child node, that it will not be
|
|
343
|
+
* announced to the screen reader.
|
|
344
|
+
*
|
|
345
|
+
* Note: if label and description are both provided, description will not be
|
|
346
|
+
* used
|
|
347
|
+
*/
|
|
348
|
+
label: PropTypes.node,
|
|
349
|
+
/**
|
|
350
|
+
* Specify the duration in milliseconds to delay before hiding the tooltip
|
|
351
|
+
*/
|
|
352
|
+
leaveDelayMs: PropTypes.number,
|
|
353
|
+
/**
|
|
354
|
+
* The content to be rendered inside the popover menu.
|
|
355
|
+
*/
|
|
356
|
+
menuContent: PropTypes.node,
|
|
357
|
+
/**
|
|
358
|
+
* The boolean to show the flyout menu has been selected.
|
|
359
|
+
*/
|
|
360
|
+
selected: PropTypes.bool,
|
|
361
|
+
/**
|
|
362
|
+
* The title for the overall menu name.
|
|
363
|
+
*/
|
|
364
|
+
title: PropTypes.string
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
exports.SideNavFlyoutMenu = SideNavFlyoutMenu;
|
|
@@ -39,6 +39,10 @@ export interface SideNavMenuProps {
|
|
|
39
39
|
* Indicates if the side navigation container is expanded or collapsed.
|
|
40
40
|
*/
|
|
41
41
|
isSideNavExpanded?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* The boolean to show the flyout menu has been selected.
|
|
44
|
+
*/
|
|
45
|
+
selected?: boolean;
|
|
42
46
|
/**
|
|
43
47
|
* The tabIndex for the button element.
|
|
44
48
|
* If not specified, the default validation will be applied.
|