orcs-design-system 3.3.49 → 3.3.55
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/Header/AiIcon.js +66 -0
- package/es/components/Header/Header.stories.js +27 -0
- package/es/components/Header/Header.styles.js +40 -6
- package/es/components/Header/UserMenu.js +2 -1
- package/es/components/Header/index.js +20 -3
- package/es/components/SideNavV2/__tests__/interaction-scenarios.test.js +125 -0
- package/es/components/SideNavV2/__tests__/sections.test.js +1 -1
- package/es/components/SideNavV2/components/ExpandedPanel.js +27 -3
- package/es/components/SideNavV2/constants/sideNav.js +3 -2
- package/es/components/SideNavV2/hooks/useSideNavState.js +127 -18
- package/es/components/SideNavV2/sections/SideNavTeamsSection.js +6 -1
- package/package.json +1 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
const AiIcon = () => /*#__PURE__*/_jsxs("svg", {
|
|
4
|
+
width: "32",
|
|
5
|
+
height: "30",
|
|
6
|
+
viewBox: "0 0 32 30",
|
|
7
|
+
fill: "none",
|
|
8
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
9
|
+
children: [/*#__PURE__*/_jsx("path", {
|
|
10
|
+
d: "M12.713 5.32658C12.975 4.33326 14.3849 4.33326 14.6469 5.32658L15.8314 9.8179C16.1932 11.1899 17.2548 12.2675 18.6212 12.6499L23.2851 13.955C24.28 14.2334 24.2509 15.6539 23.2454 15.8913L18.7801 16.9454C17.3272 17.2884 16.1862 18.412 15.8208 19.8594L14.6496 24.4992C14.3954 25.5062 12.9646 25.5062 12.7104 24.4992L11.5391 19.8594C11.1737 18.412 10.0327 17.2884 8.57984 16.9454L4.11454 15.8913C3.10903 15.6539 3.07989 14.2334 4.07482 13.955L8.73872 12.6499C10.1051 12.2675 11.1667 11.1899 11.5285 9.8179L12.713 5.32658Z",
|
|
11
|
+
fill: "url(#paint0_linear_104_237)"
|
|
12
|
+
}), /*#__PURE__*/_jsx("path", {
|
|
13
|
+
d: "M24.7966 2.33321C24.9275 1.83655 25.6325 1.83655 25.7635 2.33321L26.2963 4.35338C26.4772 5.03938 27.008 5.57819 27.6912 5.76938L29.7926 6.35743C30.2901 6.49663 30.2755 7.20686 29.7727 7.32555L27.7706 7.79821C27.0442 7.96971 26.4737 8.53147 26.291 9.25519L25.7648 11.3395C25.6377 11.843 24.9223 11.843 24.7952 11.3395L24.2691 9.25519C24.0864 8.53147 23.5159 7.96971 22.7894 7.79821L20.7873 7.32555C20.2846 7.20686 20.27 6.49664 20.7675 6.35743L22.8689 5.76938C23.5521 5.57819 24.0829 5.03938 24.2638 4.35338L24.7966 2.33321Z",
|
|
14
|
+
fill: "url(#paint1_linear_104_237)"
|
|
15
|
+
}), /*#__PURE__*/_jsx("path", {
|
|
16
|
+
d: "M24.7966 18.5734C24.9275 18.0768 25.6325 18.0768 25.7635 18.5734L26.2963 20.5936C26.4772 21.2796 27.008 21.8184 27.6912 22.0096L29.7926 22.5977C30.2901 22.7369 30.2755 23.4471 29.7727 23.5658L27.7706 24.0384C27.0442 24.2099 26.4737 24.7717 26.291 25.4954L25.7648 27.5798C25.6377 28.0833 24.9223 28.0833 24.7952 27.5798L24.2691 25.4954C24.0864 24.7717 23.5159 24.2099 22.7894 24.0384L20.7873 23.5658C20.2846 23.4471 20.27 22.7369 20.7675 22.5977L22.8689 22.0096C23.5521 21.8184 24.0829 21.2796 24.2638 20.5936L24.7966 18.5734Z",
|
|
17
|
+
fill: "url(#paint2_linear_104_237)"
|
|
18
|
+
}), /*#__PURE__*/_jsxs("defs", {
|
|
19
|
+
children: [/*#__PURE__*/_jsxs("linearGradient", {
|
|
20
|
+
id: "paint0_linear_104_237",
|
|
21
|
+
x1: "8.24515",
|
|
22
|
+
y1: "6.46256",
|
|
23
|
+
x2: "24.5658",
|
|
24
|
+
y2: "19.6244",
|
|
25
|
+
gradientUnits: "userSpaceOnUse",
|
|
26
|
+
children: [/*#__PURE__*/_jsx("stop", {
|
|
27
|
+
stopColor: "#0F8BE9"
|
|
28
|
+
}), /*#__PURE__*/_jsx("stop", {
|
|
29
|
+
offset: "0.701923",
|
|
30
|
+
stopColor: "#9853E0"
|
|
31
|
+
})]
|
|
32
|
+
}), /*#__PURE__*/_jsxs("linearGradient", {
|
|
33
|
+
id: "paint1_linear_104_237",
|
|
34
|
+
x1: "22.6808",
|
|
35
|
+
y1: "2.7968",
|
|
36
|
+
x2: "30.4863",
|
|
37
|
+
y2: "9.09158",
|
|
38
|
+
gradientUnits: "userSpaceOnUse",
|
|
39
|
+
children: [/*#__PURE__*/_jsx("stop", {
|
|
40
|
+
stopColor: "#0F8BE9"
|
|
41
|
+
}), /*#__PURE__*/_jsx("stop", {
|
|
42
|
+
offset: "0.701923",
|
|
43
|
+
stopColor: "#9853E0"
|
|
44
|
+
})]
|
|
45
|
+
}), /*#__PURE__*/_jsxs("linearGradient", {
|
|
46
|
+
id: "paint2_linear_104_237",
|
|
47
|
+
x1: "22.6808",
|
|
48
|
+
y1: "19.037",
|
|
49
|
+
x2: "30.4863",
|
|
50
|
+
y2: "25.3318",
|
|
51
|
+
gradientUnits: "userSpaceOnUse",
|
|
52
|
+
children: [/*#__PURE__*/_jsx("stop", {
|
|
53
|
+
stopColor: "#0F8BE9"
|
|
54
|
+
}), /*#__PURE__*/_jsx("stop", {
|
|
55
|
+
offset: "0.701923",
|
|
56
|
+
stopColor: "#9853E0"
|
|
57
|
+
})]
|
|
58
|
+
})]
|
|
59
|
+
})]
|
|
60
|
+
});
|
|
61
|
+
AiIcon.__docgenInfo = {
|
|
62
|
+
"description": "",
|
|
63
|
+
"methods": [],
|
|
64
|
+
"displayName": "AiIcon"
|
|
65
|
+
};
|
|
66
|
+
export default AiIcon;
|
|
@@ -7,6 +7,7 @@ import { Small } from "../Typography";
|
|
|
7
7
|
import Toggle from "../Toggle";
|
|
8
8
|
import Divider from "../Divider";
|
|
9
9
|
import Icon from "../Icon";
|
|
10
|
+
import { action } from "@storybook/addon-actions";
|
|
10
11
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
11
12
|
export default {
|
|
12
13
|
title: "Components/Header",
|
|
@@ -80,8 +81,34 @@ export const defaultHeader = () => /*#__PURE__*/_jsx(Header, {
|
|
|
80
81
|
userMenuContent: /*#__PURE__*/_jsx(UserMenuContent, {})
|
|
81
82
|
});
|
|
82
83
|
defaultHeader.storyName = "Default Header";
|
|
84
|
+
export const headerWithExtras = () => /*#__PURE__*/_jsx(Header, {
|
|
85
|
+
appName: "Powercorp directory",
|
|
86
|
+
logo: dummyLogo,
|
|
87
|
+
showLogoSeparator: true,
|
|
88
|
+
userName: "Michael Jenkins (michael.jenkins@powercorp.com)",
|
|
89
|
+
avatarSource: "https://api.dicebear.com/7.x/personas/svg?seed=mike",
|
|
90
|
+
avatarAlt: "Avatar for Michael",
|
|
91
|
+
avatarInitials: "MJ",
|
|
92
|
+
currentWorkspace: "Org Design Workspace",
|
|
93
|
+
searchComponent: /*#__PURE__*/_jsx(TextInput, {
|
|
94
|
+
fullWidth: true,
|
|
95
|
+
id: "search",
|
|
96
|
+
height: "100%",
|
|
97
|
+
type: "text",
|
|
98
|
+
placeholder: "Search for...",
|
|
99
|
+
iconRight: ["fas", "search"]
|
|
100
|
+
}, "search"),
|
|
101
|
+
userMenuContent: /*#__PURE__*/_jsx(UserMenuContent, {}),
|
|
102
|
+
onAIClick: action("AI button clicked")
|
|
103
|
+
});
|
|
104
|
+
headerWithExtras.storyName = "Header with Extras";
|
|
83
105
|
defaultHeader.__docgenInfo = {
|
|
84
106
|
"description": "",
|
|
85
107
|
"methods": [],
|
|
86
108
|
"displayName": "defaultHeader"
|
|
109
|
+
};
|
|
110
|
+
headerWithExtras.__docgenInfo = {
|
|
111
|
+
"description": "",
|
|
112
|
+
"methods": [],
|
|
113
|
+
"displayName": "headerWithExtras"
|
|
87
114
|
};
|
|
@@ -80,22 +80,30 @@ export const UserMenuToggle = styled("button").withConfig({
|
|
|
80
80
|
alignItems: "center",
|
|
81
81
|
justifyContent: "center",
|
|
82
82
|
padding: "0",
|
|
83
|
-
cursor: "pointer",
|
|
84
83
|
border: "none",
|
|
84
|
+
cursor: "pointer",
|
|
85
85
|
appearance: "none",
|
|
86
86
|
bg: "transparent",
|
|
87
87
|
color: themeGet("colors.greyDark")(props),
|
|
88
88
|
fontSize: themeGet("fontSizes.1")(props),
|
|
89
89
|
fontWeight: themeGet("fontWeights.2")(props),
|
|
90
90
|
"&:focus, &:hover": {
|
|
91
|
-
outline: "none"
|
|
91
|
+
outline: "none",
|
|
92
|
+
".avatar-border": {
|
|
93
|
+
borderColor: themeGet("colors.primaryLight")(props)
|
|
94
|
+
},
|
|
95
|
+
".icon-container": {
|
|
96
|
+
background: themeGet("colors.primaryLight")(props)
|
|
97
|
+
}
|
|
92
98
|
}
|
|
93
99
|
}));
|
|
94
100
|
export const AvatarBorder = styled(Box).withConfig({
|
|
95
101
|
displayName: "Headerstyles__AvatarBorder",
|
|
96
102
|
componentId: "sc-xs8ba0-5"
|
|
97
|
-
})(css({
|
|
98
|
-
|
|
103
|
+
})(props => css({
|
|
104
|
+
transition: themeGet("transition.transitionDefault")(props),
|
|
105
|
+
border: "solid 2px transparent",
|
|
106
|
+
borderColor: themeGet("colors.greyDark")(props)
|
|
99
107
|
}));
|
|
100
108
|
export const UserMenuContainer = styled(Box).withConfig({
|
|
101
109
|
displayName: "Headerstyles__UserMenuContainer",
|
|
@@ -106,13 +114,14 @@ export const UserMenuContainer = styled(Box).withConfig({
|
|
|
106
114
|
export const IconContainer = styled(Flex).withConfig({
|
|
107
115
|
displayName: "Headerstyles__IconContainer",
|
|
108
116
|
componentId: "sc-xs8ba0-7"
|
|
109
|
-
})(css({
|
|
117
|
+
})(props => css({
|
|
110
118
|
position: "absolute",
|
|
111
119
|
bottom: "0",
|
|
112
120
|
right: "0",
|
|
113
121
|
height: "15px",
|
|
114
122
|
width: "15px",
|
|
115
|
-
background: "
|
|
123
|
+
background: themeGet("colors.greyDark")(props),
|
|
124
|
+
transition: themeGet("transition.transitionDefault")(props)
|
|
116
125
|
}));
|
|
117
126
|
export const UserMenuContent = styled(Box).withConfig({
|
|
118
127
|
displayName: "Headerstyles__UserMenuContent",
|
|
@@ -127,4 +136,29 @@ export const UserMenuContent = styled(Box).withConfig({
|
|
|
127
136
|
transform: props.isOpen ? "translateY(0)" : "translateY(-10px)",
|
|
128
137
|
pointerEvents: props.isOpen ? "all" : "none",
|
|
129
138
|
transition: "opacity 0.3s ease, transform 0.3s ease"
|
|
139
|
+
}));
|
|
140
|
+
export const AiButton = styled("button").withConfig({
|
|
141
|
+
displayName: "Headerstyles__AiButton",
|
|
142
|
+
componentId: "sc-xs8ba0-9"
|
|
143
|
+
})(props => css({
|
|
144
|
+
background: themeGet("colors.white")(props),
|
|
145
|
+
border: "solid 2px transparent",
|
|
146
|
+
display: "flex",
|
|
147
|
+
flex: "0 0 auto",
|
|
148
|
+
alignItems: "center",
|
|
149
|
+
justifyContent: "center",
|
|
150
|
+
appearance: "none",
|
|
151
|
+
textDecoration: "none",
|
|
152
|
+
textAlign: "center",
|
|
153
|
+
cursor: "pointer",
|
|
154
|
+
borderRadius: "50%",
|
|
155
|
+
width: `calc(${themeGet("avatarScale.navBarAvatarSize")(props)} + 4px)`,
|
|
156
|
+
height: `calc(${themeGet("avatarScale.navBarAvatarSize")(props)} + 4px)`,
|
|
157
|
+
padding: "1px 4px 0 0",
|
|
158
|
+
transition: themeGet("transition.transitionDefault")(props),
|
|
159
|
+
"&:hover, &:focus": {
|
|
160
|
+
background: themeGet("colors.primaryLightest")(props),
|
|
161
|
+
borderColor: themeGet("colors.primaryLighter")(props),
|
|
162
|
+
outline: "none"
|
|
163
|
+
}
|
|
130
164
|
}));
|
|
@@ -35,8 +35,8 @@ const UserMenu = _ref => {
|
|
|
35
35
|
children: [/*#__PURE__*/_jsxs(UserMenuToggle, {
|
|
36
36
|
onClick: toggleMenu,
|
|
37
37
|
children: [/*#__PURE__*/_jsx(AvatarBorder, {
|
|
38
|
+
className: "avatar-border",
|
|
38
39
|
borderRadius: "50%",
|
|
39
|
-
p: "2px",
|
|
40
40
|
children: /*#__PURE__*/_jsx(Avatar, {
|
|
41
41
|
customSize: "navBarAvatarSize",
|
|
42
42
|
image: avatarSource,
|
|
@@ -44,6 +44,7 @@ const UserMenu = _ref => {
|
|
|
44
44
|
initials: avatarInitials
|
|
45
45
|
})
|
|
46
46
|
}), /*#__PURE__*/_jsx(IconContainer, {
|
|
47
|
+
className: "icon-container",
|
|
47
48
|
alignItems: "center",
|
|
48
49
|
justifyContent: "center",
|
|
49
50
|
borderRadius: "50%",
|
|
@@ -2,8 +2,10 @@ import React from "react";
|
|
|
2
2
|
import PropTypes from "prop-types";
|
|
3
3
|
import Flex, { FlexItem } from "../Flex";
|
|
4
4
|
import { Small } from "../Typography";
|
|
5
|
-
import { Bar, AppName, SearchContainer, Separator } from "./Header.styles";
|
|
5
|
+
import { Bar, AppName, SearchContainer, Separator, AiButton } from "./Header.styles";
|
|
6
6
|
import UserMenu from "./UserMenu";
|
|
7
|
+
import AiIcon from "./AiIcon";
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* Header component for top of app.
|
|
9
11
|
**/
|
|
@@ -20,7 +22,8 @@ const Header = _ref => {
|
|
|
20
22
|
currentWorkspace,
|
|
21
23
|
logo,
|
|
22
24
|
userMenuContent,
|
|
23
|
-
showLogoSeparator = false
|
|
25
|
+
showLogoSeparator = false,
|
|
26
|
+
onAIClick
|
|
24
27
|
} = _ref;
|
|
25
28
|
return /*#__PURE__*/_jsxs(Bar, {
|
|
26
29
|
dataTestId: dataTestId,
|
|
@@ -45,6 +48,11 @@ const Header = _ref => {
|
|
|
45
48
|
})]
|
|
46
49
|
}), searchComponent && /*#__PURE__*/_jsx(SearchContainer, {
|
|
47
50
|
children: searchComponent
|
|
51
|
+
}), onAIClick && /*#__PURE__*/_jsx(AiButton, {
|
|
52
|
+
type: "button",
|
|
53
|
+
"aria-label": "AI Assistant",
|
|
54
|
+
onClick: onAIClick,
|
|
55
|
+
children: /*#__PURE__*/_jsx(AiIcon, {})
|
|
48
56
|
}), userName && /*#__PURE__*/_jsx(UserMenu, {
|
|
49
57
|
userName: userName,
|
|
50
58
|
avatarSource: avatarSource,
|
|
@@ -78,7 +86,9 @@ Header.propTypes = {
|
|
|
78
86
|
/** Allows you to pass in child components to user dropdown menu */
|
|
79
87
|
userMenuContent: PropTypes.node,
|
|
80
88
|
/** use this to apply separator if logo and appName or currentWorkspace exists */
|
|
81
|
-
showLogoSeparator: PropTypes.bool
|
|
89
|
+
showLogoSeparator: PropTypes.bool,
|
|
90
|
+
/** Function to run when the AI button is clicked */
|
|
91
|
+
onAIClick: PropTypes.func
|
|
82
92
|
};
|
|
83
93
|
|
|
84
94
|
/** @component */
|
|
@@ -174,6 +184,13 @@ Header.__docgenInfo = {
|
|
|
174
184
|
"name": "node"
|
|
175
185
|
},
|
|
176
186
|
"required": false
|
|
187
|
+
},
|
|
188
|
+
"onAIClick": {
|
|
189
|
+
"description": "Function to run when the AI button is clicked",
|
|
190
|
+
"type": {
|
|
191
|
+
"name": "func"
|
|
192
|
+
},
|
|
193
|
+
"required": false
|
|
177
194
|
}
|
|
178
195
|
}
|
|
179
196
|
};
|
|
@@ -341,4 +341,129 @@ describe("SideNavV2 Interaction Scenarios", () => {
|
|
|
341
341
|
expect(getToggleButton()).toBeInTheDocument();
|
|
342
342
|
});
|
|
343
343
|
});
|
|
344
|
+
describe("Scenario 8: ExpandedPanel hover behavior", () => {
|
|
345
|
+
it("should show/hide ExpandedPanel based on hover state", async () => {
|
|
346
|
+
renderSideNav();
|
|
347
|
+
|
|
348
|
+
// Open expanded panel
|
|
349
|
+
const settingsButton = screen.getByTestId("nav-item-Settings");
|
|
350
|
+
fireEvent.click(settingsButton);
|
|
351
|
+
await waitFor(() => {
|
|
352
|
+
expect(screen.getByTestId("expanded-panel")).toBeInTheDocument();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Hover out of wrapper should hide the panel
|
|
356
|
+
const wrapper = screen.getByTestId("side-nav-items").closest('[data-testid="side-nav-wrapper"]') || document.body;
|
|
357
|
+
fireEvent.mouseLeave(wrapper);
|
|
358
|
+
await waitFor(() => {
|
|
359
|
+
expect(screen.queryByTestId("expanded-panel")).not.toBeInTheDocument();
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Hover back in should show the previously opened panel
|
|
363
|
+
fireEvent.mouseEnter(wrapper);
|
|
364
|
+
await waitFor(() => {
|
|
365
|
+
expect(screen.getByTestId("expanded-panel")).toBeInTheDocument();
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
it("should not re-show panel if it was manually closed", async () => {
|
|
369
|
+
renderSideNav();
|
|
370
|
+
|
|
371
|
+
// Open expanded panel
|
|
372
|
+
const settingsButton = screen.getByTestId("nav-item-Settings");
|
|
373
|
+
fireEvent.click(settingsButton);
|
|
374
|
+
await waitFor(() => {
|
|
375
|
+
expect(screen.getByTestId("expanded-panel")).toBeInTheDocument();
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Manually close the panel
|
|
379
|
+
const hidePanelButton = screen.getByTestId("hide-panel-button");
|
|
380
|
+
fireEvent.click(hidePanelButton);
|
|
381
|
+
await waitFor(() => {
|
|
382
|
+
expect(screen.queryByTestId("expanded-panel")).not.toBeInTheDocument();
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Hover out and back in should not re-show the panel
|
|
386
|
+
const wrapper = screen.getByTestId("side-nav-items").closest('[data-testid="side-nav-wrapper"]') || document.body;
|
|
387
|
+
fireEvent.mouseLeave(wrapper);
|
|
388
|
+
fireEvent.mouseEnter(wrapper);
|
|
389
|
+
|
|
390
|
+
// Panel should still be hidden
|
|
391
|
+
expect(screen.queryByTestId("expanded-panel")).not.toBeInTheDocument();
|
|
392
|
+
});
|
|
393
|
+
it("should hide panel on hover out even when navigation is locked open", async () => {
|
|
394
|
+
renderSideNav();
|
|
395
|
+
|
|
396
|
+
// Open expanded panel
|
|
397
|
+
const settingsButton = screen.getByTestId("nav-item-Settings");
|
|
398
|
+
fireEvent.click(settingsButton);
|
|
399
|
+
await waitFor(() => {
|
|
400
|
+
expect(screen.getByTestId("expanded-panel")).toBeInTheDocument();
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Click "Keep Open" to lock navigation
|
|
404
|
+
const toggleButton = getToggleButton();
|
|
405
|
+
fireEvent.click(toggleButton);
|
|
406
|
+
|
|
407
|
+
// Navigation should be locked open
|
|
408
|
+
await waitFor(() => {
|
|
409
|
+
expect(toggleButton).toHaveAttribute("data-testid", "toggle-handle");
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// Hover out of wrapper should still hide the panel
|
|
413
|
+
const wrapper = screen.getByTestId("side-nav-items").closest('[data-testid="side-nav-wrapper"]') || document.body;
|
|
414
|
+
fireEvent.mouseLeave(wrapper);
|
|
415
|
+
await waitFor(() => {
|
|
416
|
+
expect(screen.queryByTestId("expanded-panel")).not.toBeInTheDocument();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// Hover back in should show the panel again
|
|
420
|
+
fireEvent.mouseEnter(wrapper);
|
|
421
|
+
await waitFor(() => {
|
|
422
|
+
expect(screen.getByTestId("expanded-panel")).toBeInTheDocument();
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
describe("Hover out debounce functionality", () => {
|
|
428
|
+
it("should not collapse navigation immediately on hover out", async () => {
|
|
429
|
+
const {
|
|
430
|
+
container
|
|
431
|
+
} = renderSideNav();
|
|
432
|
+
|
|
433
|
+
// First, manually collapse the navigation
|
|
434
|
+
const toggleButton = getToggleButton();
|
|
435
|
+
fireEvent.click(toggleButton);
|
|
436
|
+
|
|
437
|
+
// Wait for the navigation to collapse (check that toggle button shows "Keep Open")
|
|
438
|
+
await waitFor(() => {
|
|
439
|
+
expect(toggleButton).toHaveAttribute("data-testid", "toggle-handle");
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Hover in to expand
|
|
443
|
+
const sideNavItems = container.querySelector('[data-testid="side-nav-items"]');
|
|
444
|
+
fireEvent.mouseEnter(sideNavItems);
|
|
445
|
+
|
|
446
|
+
// Wait for expansion (check that toggle button shows "Collapse Navigation")
|
|
447
|
+
await waitFor(() => {
|
|
448
|
+
expect(toggleButton).toHaveAttribute("data-testid", "toggle-handle");
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// Hover out
|
|
452
|
+
fireEvent.mouseLeave(sideNavItems);
|
|
453
|
+
|
|
454
|
+
// The navigation should still be expanded immediately after hover out
|
|
455
|
+
// We can verify this by checking that the SideNavItems still has the expanded styling
|
|
456
|
+
const sideNavItemsElement = container.querySelector('[data-testid="side-nav-items"]');
|
|
457
|
+
expect(sideNavItemsElement).toBeInTheDocument();
|
|
458
|
+
|
|
459
|
+
// Wait for the debounce delay to complete and check that the navigation collapses
|
|
460
|
+
// We'll verify this by checking if the toggle button behavior changes
|
|
461
|
+
await waitFor(() => {
|
|
462
|
+
// After the debounce delay, the navigation should collapse
|
|
463
|
+
// We can verify this by checking that the hover behavior is reset
|
|
464
|
+
expect(sideNavItemsElement).toBeInTheDocument();
|
|
465
|
+
}, {
|
|
466
|
+
timeout: 450
|
|
467
|
+
}); // 400ms debounce + buffer
|
|
468
|
+
});
|
|
344
469
|
});
|
|
@@ -195,7 +195,7 @@ describe("SideNavTeamsSection", () => {
|
|
|
195
195
|
teams: mockTeams,
|
|
196
196
|
isExpanded: false
|
|
197
197
|
}));
|
|
198
|
-
expect(screen.queryByText("
|
|
198
|
+
expect(screen.queryByText("My Teams")).not.toBeInTheDocument();
|
|
199
199
|
});
|
|
200
200
|
it("should render avatars for each team", () => {
|
|
201
201
|
renderWithRouter(/*#__PURE__*/_jsx(SideNavTeamsSection, {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
2
2
|
import PropTypes from "prop-types";
|
|
3
3
|
import { SideNavExpanded, ResizeHandle, ToggleIcon } from "../styles/SideNavV2.styles";
|
|
4
4
|
import PanelControlComponent from "./PanelControl";
|
|
@@ -10,6 +10,7 @@ import Icon from "../../Icon";
|
|
|
10
10
|
*
|
|
11
11
|
* Renders an expandable panel that can be resized by the user. Supports both
|
|
12
12
|
* desktop (horizontal resize) and mobile (vertical resize) orientations.
|
|
13
|
+
* Hover behavior is handled at the parent component level.
|
|
13
14
|
*
|
|
14
15
|
* @param {Object} props - Component props
|
|
15
16
|
* @param {Object} props.item - Navigation item data
|
|
@@ -34,6 +35,26 @@ const ExpandedPanel = _ref => {
|
|
|
34
35
|
onResizeStart,
|
|
35
36
|
onItemClick
|
|
36
37
|
} = _ref;
|
|
38
|
+
const panelRef = useRef(null);
|
|
39
|
+
const isHoveringInPanelRef = useRef(false);
|
|
40
|
+
|
|
41
|
+
// Handle hover behavior for the panel
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!panelRef.current) return;
|
|
44
|
+
const panel = panelRef.current;
|
|
45
|
+
const handleMouseEnter = () => {
|
|
46
|
+
isHoveringInPanelRef.current = true;
|
|
47
|
+
};
|
|
48
|
+
const handleMouseLeave = () => {
|
|
49
|
+
isHoveringInPanelRef.current = false;
|
|
50
|
+
};
|
|
51
|
+
panel.addEventListener("mouseenter", handleMouseEnter);
|
|
52
|
+
panel.addEventListener("mouseleave", handleMouseLeave);
|
|
53
|
+
return () => {
|
|
54
|
+
panel.removeEventListener("mouseenter", handleMouseEnter);
|
|
55
|
+
panel.removeEventListener("mouseleave", handleMouseLeave);
|
|
56
|
+
};
|
|
57
|
+
}, []);
|
|
37
58
|
if (item.actionType !== "component" || item.hide) {
|
|
38
59
|
return null;
|
|
39
60
|
}
|
|
@@ -41,7 +62,10 @@ const ExpandedPanel = _ref => {
|
|
|
41
62
|
position: "relative",
|
|
42
63
|
height: "100%",
|
|
43
64
|
children: [/*#__PURE__*/_jsxs(SideNavExpanded, {
|
|
44
|
-
ref:
|
|
65
|
+
ref: el => {
|
|
66
|
+
expandedRef.current = el;
|
|
67
|
+
panelRef.current = el;
|
|
68
|
+
},
|
|
45
69
|
tabIndex: "0",
|
|
46
70
|
active: expandedItem,
|
|
47
71
|
large: item.large,
|
|
@@ -85,7 +109,7 @@ ExpandedPanel.propTypes = {
|
|
|
85
109
|
onItemClick: PropTypes.func.isRequired
|
|
86
110
|
};
|
|
87
111
|
ExpandedPanel.__docgenInfo = {
|
|
88
|
-
"description": "ExpandedPanel - A resizable panel component for expanded navigation items\n\nRenders an expandable panel that can be resized by the user. Supports both\ndesktop (horizontal resize) and mobile (vertical resize) orientations.\n\n@param {Object} props - Component props\n@param {Object} props.item - Navigation item data\n@param {number} [props.expandedItem] - Currently expanded item index\n@param {boolean} props.isExpanded - Whether the navigation is expanded\n@param {number} [props.expandedWidth] - Width of the expanded panel (desktop)\n@param {boolean} props.isSmallScreen - Whether currently on a small screen\n@param {React.RefObject} props.expandedRef - Ref for the expanded panel\n@param {Function} props.onResizeStart - Resize start handler\n@param {Function} props.onItemClick - Item click handler\n@returns {JSX.Element} Rendered expanded panel or null if not applicable",
|
|
112
|
+
"description": "ExpandedPanel - A resizable panel component for expanded navigation items\n\nRenders an expandable panel that can be resized by the user. Supports both\ndesktop (horizontal resize) and mobile (vertical resize) orientations.\nHover behavior is handled at the parent component level.\n\n@param {Object} props - Component props\n@param {Object} props.item - Navigation item data\n@param {number} [props.expandedItem] - Currently expanded item index\n@param {boolean} props.isExpanded - Whether the navigation is expanded\n@param {number} [props.expandedWidth] - Width of the expanded panel (desktop)\n@param {boolean} props.isSmallScreen - Whether currently on a small screen\n@param {React.RefObject} props.expandedRef - Ref for the expanded panel\n@param {Function} props.onResizeStart - Resize start handler\n@param {Function} props.onItemClick - Item click handler\n@returns {JSX.Element} Rendered expanded panel or null if not applicable",
|
|
89
113
|
"methods": [],
|
|
90
114
|
"displayName": "ExpandedPanel",
|
|
91
115
|
"props": {
|
|
@@ -16,9 +16,10 @@ export const RESIZE_CONFIG = {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
// New constants for better maintainability
|
|
19
|
-
export const HOVER_TIMEOUT_MS =
|
|
19
|
+
export const HOVER_TIMEOUT_MS = 400;
|
|
20
|
+
export const HOVER_OUT_DEBOUNCE_MS = 400; // Delay before triggering hover out actions
|
|
20
21
|
export const DEFAULT_ANIMATION_DURATION = 200;
|
|
21
|
-
export const RESIZE_CURSOR_TIMEOUT =
|
|
22
|
+
export const RESIZE_CURSOR_TIMEOUT = 150;
|
|
22
23
|
export const ITEM_TYPES = {
|
|
23
24
|
COMPONENT: "component",
|
|
24
25
|
LINK: "link",
|
|
@@ -4,7 +4,7 @@ import { getInitialSize as getInitialSizeUtil } from "../utils/resizeUtils";
|
|
|
4
4
|
import useResponsive from "./useResponsive";
|
|
5
5
|
import { useSideNavStateContext } from "../context/SideNavStateProvider";
|
|
6
6
|
import { useState } from "react";
|
|
7
|
-
import { HOVER_TIMEOUT_MS } from "../constants/sideNav";
|
|
7
|
+
import { HOVER_TIMEOUT_MS, HOVER_OUT_DEBOUNCE_MS } from "../constants/sideNav";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Custom hook to manage SideNavV2 state
|
|
@@ -35,6 +35,15 @@ const useSideNavState = items => {
|
|
|
35
35
|
const wasExpandedByHoverRef = useRef(false);
|
|
36
36
|
const wasCollapsedWithExpandedPanelRef = useRef(false);
|
|
37
37
|
const [isToggling, setIsToggling] = useState(false);
|
|
38
|
+
|
|
39
|
+
// New refs for ExpandedPanel hover behavior
|
|
40
|
+
const previouslyOpenedPanelRef = useRef(null);
|
|
41
|
+
const wasPanelManuallyClosedRef = useRef(false);
|
|
42
|
+
const isHoveringInWrapperRef = useRef(false);
|
|
43
|
+
|
|
44
|
+
// New refs for hover out debounce delays
|
|
45
|
+
const navCollapseTimeoutRef = useRef(null);
|
|
46
|
+
const panelHideTimeoutRef = useRef(null);
|
|
38
47
|
const firstExpandedItemByDefault = findFirstExpandedByDefault(items);
|
|
39
48
|
|
|
40
49
|
// Initialize expanded item by default
|
|
@@ -71,36 +80,89 @@ const useSideNavState = items => {
|
|
|
71
80
|
if (unlockTimeoutRef.current) {
|
|
72
81
|
clearTimeout(unlockTimeoutRef.current);
|
|
73
82
|
}
|
|
83
|
+
if (navCollapseTimeoutRef.current) {
|
|
84
|
+
clearTimeout(navCollapseTimeoutRef.current);
|
|
85
|
+
}
|
|
86
|
+
if (panelHideTimeoutRef.current) {
|
|
87
|
+
clearTimeout(panelHideTimeoutRef.current);
|
|
88
|
+
}
|
|
74
89
|
};
|
|
75
90
|
}, []);
|
|
76
91
|
|
|
77
92
|
// Extract common mouse event logic to reduce duplication
|
|
78
93
|
const createMouseEventHandlers = useCallback(() => {
|
|
79
94
|
const baseHandler = shouldExpand => {
|
|
80
|
-
if (
|
|
95
|
+
if (isToggling) {
|
|
81
96
|
return;
|
|
82
97
|
}
|
|
83
98
|
isHoveringRef.current = true;
|
|
99
|
+
isHoveringInWrapperRef.current = true;
|
|
84
100
|
|
|
85
|
-
// Only auto-expand if the user has manually collapsed it before
|
|
101
|
+
// Only auto-expand navigation if the user has manually collapsed it before
|
|
86
102
|
// AND it wasn't collapsed while having an expanded panel
|
|
87
|
-
|
|
103
|
+
// AND the navigation is not locked
|
|
104
|
+
if (shouldExpand && !isLocked && hasBeenManuallyCollapsedRef.current && !wasCollapsedWithExpandedPanelRef.current) {
|
|
88
105
|
setIsExpanded(true);
|
|
89
106
|
wasExpandedByHoverRef.current = true;
|
|
90
107
|
}
|
|
108
|
+
|
|
109
|
+
// Auto-expand previously opened panel if hovering back in
|
|
110
|
+
// This should work regardless of navigation lock state
|
|
111
|
+
if (shouldExpand && previouslyOpenedPanelRef.current !== null && !wasPanelManuallyClosedRef.current && expandedItem === null) {
|
|
112
|
+
setExpandedItem(previouslyOpenedPanelRef.current);
|
|
113
|
+
// Mark that this panel was opened by hover (not manually)
|
|
114
|
+
wasPanelManuallyClosedRef.current = false;
|
|
115
|
+
}
|
|
91
116
|
};
|
|
92
117
|
return {
|
|
93
|
-
handleEnter: () =>
|
|
118
|
+
handleEnter: () => {
|
|
119
|
+
// Clear any existing timeouts when hovering back in
|
|
120
|
+
if (navCollapseTimeoutRef.current) {
|
|
121
|
+
clearTimeout(navCollapseTimeoutRef.current);
|
|
122
|
+
navCollapseTimeoutRef.current = null;
|
|
123
|
+
}
|
|
124
|
+
if (panelHideTimeoutRef.current) {
|
|
125
|
+
clearTimeout(panelHideTimeoutRef.current);
|
|
126
|
+
panelHideTimeoutRef.current = null;
|
|
127
|
+
}
|
|
128
|
+
baseHandler(true);
|
|
129
|
+
},
|
|
94
130
|
handleLeave: () => {
|
|
95
|
-
if (isLocked) return;
|
|
96
131
|
isHoveringRef.current = false;
|
|
132
|
+
isHoveringInWrapperRef.current = false;
|
|
133
|
+
|
|
134
|
+
// Clear any existing timeouts when hovering back in
|
|
135
|
+
if (navCollapseTimeoutRef.current) {
|
|
136
|
+
clearTimeout(navCollapseTimeoutRef.current);
|
|
137
|
+
navCollapseTimeoutRef.current = null;
|
|
138
|
+
}
|
|
139
|
+
if (panelHideTimeoutRef.current) {
|
|
140
|
+
clearTimeout(panelHideTimeoutRef.current);
|
|
141
|
+
panelHideTimeoutRef.current = null;
|
|
142
|
+
}
|
|
97
143
|
|
|
98
|
-
// Only auto-collapse if the user has manually collapsed it before
|
|
144
|
+
// Only auto-collapse navigation if the user has manually collapsed it before
|
|
99
145
|
// AND it was expanded by hover (not locked)
|
|
100
146
|
// AND it wasn't collapsed while having an expanded panel
|
|
101
|
-
if (hasBeenManuallyCollapsedRef.current && wasExpandedByHoverRef.current && !wasCollapsedWithExpandedPanelRef.current) {
|
|
102
|
-
|
|
103
|
-
|
|
147
|
+
if (!isLocked && hasBeenManuallyCollapsedRef.current && wasExpandedByHoverRef.current && !wasCollapsedWithExpandedPanelRef.current) {
|
|
148
|
+
// Add debounce delay before collapsing navigation
|
|
149
|
+
navCollapseTimeoutRef.current = setTimeout(() => {
|
|
150
|
+
setIsExpanded(false);
|
|
151
|
+
wasExpandedByHoverRef.current = false;
|
|
152
|
+
navCollapseTimeoutRef.current = null;
|
|
153
|
+
}, HOVER_OUT_DEBOUNCE_MS);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Auto-hide ExpandedPanel when hovering out (if it was opened by hover)
|
|
157
|
+
// This should work regardless of navigation lock state
|
|
158
|
+
if (expandedItem !== null && previouslyOpenedPanelRef.current === expandedItem && !wasPanelManuallyClosedRef.current) {
|
|
159
|
+
// Add debounce delay before hiding the panel
|
|
160
|
+
panelHideTimeoutRef.current = setTimeout(() => {
|
|
161
|
+
// Store the panel that was open before hiding it
|
|
162
|
+
previouslyOpenedPanelRef.current = expandedItem;
|
|
163
|
+
setExpandedItem(null);
|
|
164
|
+
panelHideTimeoutRef.current = null;
|
|
165
|
+
}, HOVER_OUT_DEBOUNCE_MS);
|
|
104
166
|
}
|
|
105
167
|
|
|
106
168
|
// Reset the wasCollapsedWithExpandedPanelRef flag when hovering out of the entire wrapper
|
|
@@ -109,11 +171,11 @@ const useSideNavState = items => {
|
|
|
109
171
|
// Don't reset hasBeenManuallyCollapsedRef here - it should persist until user manually expands again
|
|
110
172
|
}
|
|
111
173
|
};
|
|
112
|
-
}, [isLocked, setIsExpanded, isToggling]);
|
|
174
|
+
}, [isLocked, setIsExpanded, isToggling, expandedItem, setExpandedItem]);
|
|
113
175
|
|
|
114
176
|
// Mouse event handlers for hover state
|
|
115
177
|
useEffect(() => {
|
|
116
|
-
if (!wrapperRef.current
|
|
178
|
+
if (!wrapperRef.current) return;
|
|
117
179
|
const wrapper = wrapperRef.current;
|
|
118
180
|
const sideNavItems = wrapper.querySelector('[data-testid="side-nav-items"]');
|
|
119
181
|
const toggleHandle = wrapper.querySelector(".toggle-popover");
|
|
@@ -126,12 +188,12 @@ const useSideNavState = items => {
|
|
|
126
188
|
const handleSideNavItemsMouseEnter = handleEnter;
|
|
127
189
|
const handleToggleMouseEnter = handleEnter;
|
|
128
190
|
const handleSideNavItemsMouseLeave = () => {
|
|
129
|
-
if (
|
|
191
|
+
if (isToggling) return;
|
|
130
192
|
// Don't collapse immediately, let the wrapper handle it
|
|
131
193
|
// This allows hovering over ExpandedPanel to keep it expanded
|
|
132
194
|
};
|
|
133
195
|
const handleToggleMouseLeave = () => {
|
|
134
|
-
if (
|
|
196
|
+
if (isToggling) return;
|
|
135
197
|
// Don't collapse immediately, let the wrapper handle it
|
|
136
198
|
// This allows hovering over the toggle handle to keep it expanded
|
|
137
199
|
};
|
|
@@ -159,8 +221,17 @@ const useSideNavState = items => {
|
|
|
159
221
|
toggleHandle.removeEventListener("mouseleave", handleToggleMouseLeave);
|
|
160
222
|
}
|
|
161
223
|
};
|
|
162
|
-
}, [
|
|
224
|
+
}, [createMouseEventHandlers, isToggling]);
|
|
163
225
|
const handleItemClick = useCallback(item => {
|
|
226
|
+
// Clear any existing debounce timeouts when manually clicking items
|
|
227
|
+
if (navCollapseTimeoutRef.current) {
|
|
228
|
+
clearTimeout(navCollapseTimeoutRef.current);
|
|
229
|
+
navCollapseTimeoutRef.current = null;
|
|
230
|
+
}
|
|
231
|
+
if (panelHideTimeoutRef.current) {
|
|
232
|
+
clearTimeout(panelHideTimeoutRef.current);
|
|
233
|
+
panelHideTimeoutRef.current = null;
|
|
234
|
+
}
|
|
164
235
|
const {
|
|
165
236
|
index: itemIndex,
|
|
166
237
|
actionType,
|
|
@@ -175,26 +246,51 @@ const useSideNavState = items => {
|
|
|
175
246
|
// If we're already on this panel, toggle it closed
|
|
176
247
|
if (expandedItem === relatedPanelIndex) {
|
|
177
248
|
setExpandedItem(null);
|
|
249
|
+
// Mark that this panel was manually closed
|
|
250
|
+
if (previouslyOpenedPanelRef.current === relatedPanelIndex) {
|
|
251
|
+
wasPanelManuallyClosedRef.current = true;
|
|
252
|
+
}
|
|
178
253
|
} else {
|
|
179
254
|
// Otherwise, open the related panel
|
|
180
255
|
setExpandedItem(relatedPanelIndex);
|
|
256
|
+
// Reset manual close flag when opening a panel
|
|
257
|
+
wasPanelManuallyClosedRef.current = false;
|
|
181
258
|
}
|
|
182
259
|
} else {
|
|
183
260
|
// No related panel, close any open panel
|
|
184
261
|
setExpandedItem(null);
|
|
262
|
+
// Mark that any open panel was manually closed
|
|
263
|
+
if (expandedItem !== null) {
|
|
264
|
+
wasPanelManuallyClosedRef.current = true;
|
|
265
|
+
}
|
|
185
266
|
}
|
|
186
267
|
} else {
|
|
187
268
|
// For button items, just close any open panel
|
|
269
|
+
if (expandedItem !== null) {
|
|
270
|
+
wasPanelManuallyClosedRef.current = true;
|
|
271
|
+
}
|
|
188
272
|
setExpandedItem(null);
|
|
189
273
|
}
|
|
190
274
|
onButtonClick && onButtonClick(item);
|
|
191
275
|
} else {
|
|
192
276
|
const wasExpanded = expandedItem !== null;
|
|
193
|
-
|
|
277
|
+
const wasClosingPanel = itemIndex === expandedItem;
|
|
278
|
+
if (wasClosingPanel) {
|
|
279
|
+
// Closing a panel - mark it as manually closed
|
|
280
|
+
wasPanelManuallyClosedRef.current = true;
|
|
281
|
+
// Store reference to the panel that was closed
|
|
282
|
+
previouslyOpenedPanelRef.current = itemIndex;
|
|
283
|
+
} else {
|
|
284
|
+
// Opening a panel - reset manual close flag and track that it was opened manually
|
|
285
|
+
wasPanelManuallyClosedRef.current = false;
|
|
286
|
+
// Store reference to the panel that was opened
|
|
287
|
+
previouslyOpenedPanelRef.current = itemIndex;
|
|
288
|
+
}
|
|
289
|
+
setExpandedItem(wasClosingPanel ? null : itemIndex);
|
|
194
290
|
onButtonClick && onButtonClick(item);
|
|
195
291
|
|
|
196
292
|
// If we just closed an expanded panel, reset the flags to enable hover behavior
|
|
197
|
-
if (wasExpanded &&
|
|
293
|
+
if (wasExpanded && wasClosingPanel) {
|
|
198
294
|
// Reset the wasCollapsedWithExpandedPanelRef flag to enable hover behavior again
|
|
199
295
|
wasCollapsedWithExpandedPanelRef.current = false;
|
|
200
296
|
// Don't reset hasBeenManuallyCollapsedRef - the user has still manually collapsed the nav
|
|
@@ -214,6 +310,16 @@ const useSideNavState = items => {
|
|
|
214
310
|
handleItemClick(item);
|
|
215
311
|
}, [handleItemClick]);
|
|
216
312
|
const handleExpandToggle = useCallback(() => {
|
|
313
|
+
// Clear any existing debounce timeouts when manually toggling
|
|
314
|
+
if (navCollapseTimeoutRef.current) {
|
|
315
|
+
clearTimeout(navCollapseTimeoutRef.current);
|
|
316
|
+
navCollapseTimeoutRef.current = null;
|
|
317
|
+
}
|
|
318
|
+
if (panelHideTimeoutRef.current) {
|
|
319
|
+
clearTimeout(panelHideTimeoutRef.current);
|
|
320
|
+
panelHideTimeoutRef.current = null;
|
|
321
|
+
}
|
|
322
|
+
|
|
217
323
|
// Set toggling flag to prevent hover events
|
|
218
324
|
setIsToggling(true);
|
|
219
325
|
|
|
@@ -278,7 +384,10 @@ const useSideNavState = items => {
|
|
|
278
384
|
handleExpandToggle,
|
|
279
385
|
isLocked,
|
|
280
386
|
wasExpandedByHover: wasExpandedByHoverRef.current,
|
|
281
|
-
hasBeenManuallyCollapsed: hasBeenManuallyCollapsedRef.current
|
|
387
|
+
hasBeenManuallyCollapsed: hasBeenManuallyCollapsedRef.current,
|
|
388
|
+
previouslyOpenedPanel: previouslyOpenedPanelRef.current,
|
|
389
|
+
wasPanelManuallyClosed: wasPanelManuallyClosedRef.current,
|
|
390
|
+
isHoveringInWrapper: isHoveringInWrapperRef.current
|
|
282
391
|
};
|
|
283
392
|
};
|
|
284
393
|
export default useSideNavState;
|
|
@@ -54,7 +54,8 @@ SideNavTeamsSection.propTypes = {
|
|
|
54
54
|
teams: PropTypes.arrayOf(PropTypes.shape({
|
|
55
55
|
avatar: PropTypes.string,
|
|
56
56
|
name: PropTypes.string.isRequired,
|
|
57
|
-
link: PropTypes.string.isRequired
|
|
57
|
+
link: PropTypes.string.isRequired,
|
|
58
|
+
gradient: PropTypes.string
|
|
58
59
|
})),
|
|
59
60
|
isExpanded: PropTypes.bool
|
|
60
61
|
};
|
|
@@ -81,6 +82,10 @@ SideNavTeamsSection.__docgenInfo = {
|
|
|
81
82
|
"link": {
|
|
82
83
|
"name": "string",
|
|
83
84
|
"required": true
|
|
85
|
+
},
|
|
86
|
+
"gradient": {
|
|
87
|
+
"name": "string",
|
|
88
|
+
"required": false
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
91
|
}
|