orcs-design-system 3.3.39 → 3.3.41
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/Divider/index.js +1 -1
- package/es/components/Icon/index.js +45 -27
- package/es/components/Popover/index.js +9 -1
- package/es/components/SideNavV2/NavItem.js +231 -0
- package/es/components/SideNavV2/SideNav.js +295 -0
- package/es/components/SideNavV2/SideNavV2.stories.js +382 -0
- package/es/components/SideNavV2/__tests__/resize.test.js +129 -0
- package/es/components/SideNavV2/__tests__/sections.test.js +333 -0
- package/es/components/SideNavV2/components/BaseSection.js +178 -0
- package/es/components/SideNavV2/components/CurrentViewSectionPortalTarget.js +35 -0
- package/es/components/SideNavV2/components/ExpandedPanel.js +161 -0
- package/es/components/SideNavV2/components/ItemSection.js +156 -0
- package/es/components/SideNavV2/components/PanelControl.js +128 -0
- package/es/components/SideNavV2/components/RenderCurrentViewSection.js +39 -0
- package/es/components/SideNavV2/components/index.js +4 -0
- package/es/components/SideNavV2/constants/sideNav.js +21 -0
- package/es/components/SideNavV2/context/SideNavStateProvider.js +57 -0
- package/es/components/SideNavV2/hooks/index.js +3 -0
- package/es/components/SideNavV2/hooks/useResize.js +70 -0
- package/es/components/SideNavV2/hooks/useResponsive.js +32 -0
- package/es/components/SideNavV2/hooks/useSideNavState.js +88 -0
- package/es/components/SideNavV2/index.js +3 -0
- package/es/components/SideNavV2/sections/SideNavCurrentViewSection.js +124 -0
- package/es/components/SideNavV2/sections/SideNavPinnedSection.js +125 -0
- package/es/components/SideNavV2/sections/SideNavTeamsSection.js +91 -0
- package/es/components/SideNavV2/styles/SideNavV2.styles.js +156 -0
- package/es/components/SideNavV2/types/sideNav.js +53 -0
- package/es/components/SideNavV2/utils/index.js +2 -0
- package/es/components/SideNavV2/utils/itemUtils.js +51 -0
- package/es/components/SideNavV2/utils/resizeUtils.js +57 -0
- package/es/components/StatusDot/StatusDot.stories.js +59 -72
- package/es/components/StatusDot/index.js +14 -5
- package/es/components.test.js +4 -0
- package/es/index.js +1 -0
- package/package.json +6 -2
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import Divider from "../../Divider";
|
|
4
|
+
import NavItem from "../NavItem";
|
|
5
|
+
import { filterVisibleItems, isItemActive } from "../utils/itemUtils";
|
|
6
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
|
+
const ItemSection = _ref => {
|
|
8
|
+
let {
|
|
9
|
+
items,
|
|
10
|
+
isExpanded,
|
|
11
|
+
isSmallScreen,
|
|
12
|
+
expandedItem,
|
|
13
|
+
handleItemClick,
|
|
14
|
+
navItemRefs,
|
|
15
|
+
showDivider = false,
|
|
16
|
+
dividerDisplay = ["none", "none", "none", "block"]
|
|
17
|
+
} = _ref;
|
|
18
|
+
const visibleItems = filterVisibleItems(items);
|
|
19
|
+
if (visibleItems.length === 0) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const renderItem = item => {
|
|
23
|
+
if (item.hide) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const Component = item.component;
|
|
27
|
+
return /*#__PURE__*/_jsx(NavItem, {
|
|
28
|
+
item: item,
|
|
29
|
+
isSmallScreen: isSmallScreen,
|
|
30
|
+
Component: Component,
|
|
31
|
+
isActive: isItemActive(item, expandedItem),
|
|
32
|
+
handleItemClick: handleItemClick,
|
|
33
|
+
navItemRefs: navItemRefs,
|
|
34
|
+
isExpanded: isExpanded
|
|
35
|
+
}, item.index);
|
|
36
|
+
};
|
|
37
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
38
|
+
children: [showDivider && !isSmallScreen && /*#__PURE__*/_jsx(Divider, {
|
|
39
|
+
light: true,
|
|
40
|
+
display: dividerDisplay
|
|
41
|
+
}), visibleItems.map(renderItem)]
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
ItemSection.propTypes = {
|
|
45
|
+
items: PropTypes.arrayOf(PropTypes.shape({
|
|
46
|
+
index: PropTypes.number,
|
|
47
|
+
hide: PropTypes.bool,
|
|
48
|
+
component: PropTypes.elementType,
|
|
49
|
+
actionType: PropTypes.string,
|
|
50
|
+
isActive: PropTypes.bool
|
|
51
|
+
})).isRequired,
|
|
52
|
+
isExpanded: PropTypes.bool,
|
|
53
|
+
isSmallScreen: PropTypes.bool,
|
|
54
|
+
expandedItem: PropTypes.number,
|
|
55
|
+
handleItemClick: PropTypes.func.isRequired,
|
|
56
|
+
navItemRefs: PropTypes.object,
|
|
57
|
+
showDivider: PropTypes.bool,
|
|
58
|
+
dividerDisplay: PropTypes.array
|
|
59
|
+
};
|
|
60
|
+
ItemSection.__docgenInfo = {
|
|
61
|
+
"description": "",
|
|
62
|
+
"methods": [],
|
|
63
|
+
"displayName": "ItemSection",
|
|
64
|
+
"props": {
|
|
65
|
+
"showDivider": {
|
|
66
|
+
"defaultValue": {
|
|
67
|
+
"value": "false",
|
|
68
|
+
"computed": false
|
|
69
|
+
},
|
|
70
|
+
"description": "",
|
|
71
|
+
"type": {
|
|
72
|
+
"name": "bool"
|
|
73
|
+
},
|
|
74
|
+
"required": false
|
|
75
|
+
},
|
|
76
|
+
"dividerDisplay": {
|
|
77
|
+
"defaultValue": {
|
|
78
|
+
"value": "[\"none\", \"none\", \"none\", \"block\"]",
|
|
79
|
+
"computed": false
|
|
80
|
+
},
|
|
81
|
+
"description": "",
|
|
82
|
+
"type": {
|
|
83
|
+
"name": "array"
|
|
84
|
+
},
|
|
85
|
+
"required": false
|
|
86
|
+
},
|
|
87
|
+
"items": {
|
|
88
|
+
"description": "",
|
|
89
|
+
"type": {
|
|
90
|
+
"name": "arrayOf",
|
|
91
|
+
"value": {
|
|
92
|
+
"name": "shape",
|
|
93
|
+
"value": {
|
|
94
|
+
"index": {
|
|
95
|
+
"name": "number",
|
|
96
|
+
"required": false
|
|
97
|
+
},
|
|
98
|
+
"hide": {
|
|
99
|
+
"name": "bool",
|
|
100
|
+
"required": false
|
|
101
|
+
},
|
|
102
|
+
"component": {
|
|
103
|
+
"name": "elementType",
|
|
104
|
+
"required": false
|
|
105
|
+
},
|
|
106
|
+
"actionType": {
|
|
107
|
+
"name": "string",
|
|
108
|
+
"required": false
|
|
109
|
+
},
|
|
110
|
+
"isActive": {
|
|
111
|
+
"name": "bool",
|
|
112
|
+
"required": false
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"required": true
|
|
118
|
+
},
|
|
119
|
+
"isExpanded": {
|
|
120
|
+
"description": "",
|
|
121
|
+
"type": {
|
|
122
|
+
"name": "bool"
|
|
123
|
+
},
|
|
124
|
+
"required": false
|
|
125
|
+
},
|
|
126
|
+
"isSmallScreen": {
|
|
127
|
+
"description": "",
|
|
128
|
+
"type": {
|
|
129
|
+
"name": "bool"
|
|
130
|
+
},
|
|
131
|
+
"required": false
|
|
132
|
+
},
|
|
133
|
+
"expandedItem": {
|
|
134
|
+
"description": "",
|
|
135
|
+
"type": {
|
|
136
|
+
"name": "number"
|
|
137
|
+
},
|
|
138
|
+
"required": false
|
|
139
|
+
},
|
|
140
|
+
"handleItemClick": {
|
|
141
|
+
"description": "",
|
|
142
|
+
"type": {
|
|
143
|
+
"name": "func"
|
|
144
|
+
},
|
|
145
|
+
"required": true
|
|
146
|
+
},
|
|
147
|
+
"navItemRefs": {
|
|
148
|
+
"description": "",
|
|
149
|
+
"type": {
|
|
150
|
+
"name": "object"
|
|
151
|
+
},
|
|
152
|
+
"required": false
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
export default ItemSection;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import Icon from "../../Icon";
|
|
4
|
+
import { PanelControlTooltip, PanelControl } from "../styles/SideNavV2.styles";
|
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
const PanelControlComponent = _ref => {
|
|
7
|
+
let {
|
|
8
|
+
isExpanded,
|
|
9
|
+
onClick,
|
|
10
|
+
ariaLabel,
|
|
11
|
+
tooltipText,
|
|
12
|
+
direction = "right",
|
|
13
|
+
position,
|
|
14
|
+
tabIndex = "-1",
|
|
15
|
+
onBlur
|
|
16
|
+
} = _ref;
|
|
17
|
+
return /*#__PURE__*/_jsx(PanelControlTooltip, {
|
|
18
|
+
display: ["none", "none", "none", "inline-block"],
|
|
19
|
+
width: "fit-content",
|
|
20
|
+
direction: direction,
|
|
21
|
+
tabIndex: tabIndex,
|
|
22
|
+
text: tooltipText,
|
|
23
|
+
position: position,
|
|
24
|
+
children: /*#__PURE__*/_jsx(PanelControl, {
|
|
25
|
+
onClick: onClick,
|
|
26
|
+
"aria-label": ariaLabel,
|
|
27
|
+
onBlur: onBlur,
|
|
28
|
+
children: /*#__PURE__*/_jsx(Icon, {
|
|
29
|
+
icon: ["fas", isExpanded ? "chevron-left" : "chevron-right"]
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
PanelControlComponent.propTypes = {
|
|
35
|
+
isExpanded: PropTypes.bool,
|
|
36
|
+
onClick: PropTypes.func.isRequired,
|
|
37
|
+
ariaLabel: PropTypes.string.isRequired,
|
|
38
|
+
tooltipText: PropTypes.string.isRequired,
|
|
39
|
+
direction: PropTypes.oneOf(["left", "right", "top", "bottom"]),
|
|
40
|
+
position: PropTypes.string,
|
|
41
|
+
tabIndex: PropTypes.string,
|
|
42
|
+
onBlur: PropTypes.func
|
|
43
|
+
};
|
|
44
|
+
PanelControlComponent.__docgenInfo = {
|
|
45
|
+
"description": "",
|
|
46
|
+
"methods": [],
|
|
47
|
+
"displayName": "PanelControlComponent",
|
|
48
|
+
"props": {
|
|
49
|
+
"direction": {
|
|
50
|
+
"defaultValue": {
|
|
51
|
+
"value": "\"right\"",
|
|
52
|
+
"computed": false
|
|
53
|
+
},
|
|
54
|
+
"description": "",
|
|
55
|
+
"type": {
|
|
56
|
+
"name": "enum",
|
|
57
|
+
"value": [{
|
|
58
|
+
"value": "\"left\"",
|
|
59
|
+
"computed": false
|
|
60
|
+
}, {
|
|
61
|
+
"value": "\"right\"",
|
|
62
|
+
"computed": false
|
|
63
|
+
}, {
|
|
64
|
+
"value": "\"top\"",
|
|
65
|
+
"computed": false
|
|
66
|
+
}, {
|
|
67
|
+
"value": "\"bottom\"",
|
|
68
|
+
"computed": false
|
|
69
|
+
}]
|
|
70
|
+
},
|
|
71
|
+
"required": false
|
|
72
|
+
},
|
|
73
|
+
"tabIndex": {
|
|
74
|
+
"defaultValue": {
|
|
75
|
+
"value": "\"-1\"",
|
|
76
|
+
"computed": false
|
|
77
|
+
},
|
|
78
|
+
"description": "",
|
|
79
|
+
"type": {
|
|
80
|
+
"name": "string"
|
|
81
|
+
},
|
|
82
|
+
"required": false
|
|
83
|
+
},
|
|
84
|
+
"isExpanded": {
|
|
85
|
+
"description": "",
|
|
86
|
+
"type": {
|
|
87
|
+
"name": "bool"
|
|
88
|
+
},
|
|
89
|
+
"required": false
|
|
90
|
+
},
|
|
91
|
+
"onClick": {
|
|
92
|
+
"description": "",
|
|
93
|
+
"type": {
|
|
94
|
+
"name": "func"
|
|
95
|
+
},
|
|
96
|
+
"required": true
|
|
97
|
+
},
|
|
98
|
+
"ariaLabel": {
|
|
99
|
+
"description": "",
|
|
100
|
+
"type": {
|
|
101
|
+
"name": "string"
|
|
102
|
+
},
|
|
103
|
+
"required": true
|
|
104
|
+
},
|
|
105
|
+
"tooltipText": {
|
|
106
|
+
"description": "",
|
|
107
|
+
"type": {
|
|
108
|
+
"name": "string"
|
|
109
|
+
},
|
|
110
|
+
"required": true
|
|
111
|
+
},
|
|
112
|
+
"position": {
|
|
113
|
+
"description": "",
|
|
114
|
+
"type": {
|
|
115
|
+
"name": "string"
|
|
116
|
+
},
|
|
117
|
+
"required": false
|
|
118
|
+
},
|
|
119
|
+
"onBlur": {
|
|
120
|
+
"description": "",
|
|
121
|
+
"type": {
|
|
122
|
+
"name": "func"
|
|
123
|
+
},
|
|
124
|
+
"required": false
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
export default PanelControlComponent;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
import { CURRENT_VIEW_SECTION_ID } from "../constants/sideNav";
|
|
4
|
+
import Divider from "../../Divider";
|
|
5
|
+
import SideNavCurrentViewSection from "../sections/SideNavCurrentViewSection";
|
|
6
|
+
import Flex from "../../Flex";
|
|
7
|
+
import { useSideNavStateContext } from "../context/SideNavStateProvider";
|
|
8
|
+
import useResponsive from "../hooks/useResponsive";
|
|
9
|
+
import PropTypes from "prop-types";
|
|
10
|
+
import { CurrentViewSectionShape } from "../types/sideNav";
|
|
11
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
12
|
+
const RenderCurrentViewSection = _ref => {
|
|
13
|
+
let {
|
|
14
|
+
viewingState
|
|
15
|
+
} = _ref;
|
|
16
|
+
const container = document.getElementById(CURRENT_VIEW_SECTION_ID);
|
|
17
|
+
const {
|
|
18
|
+
isExpanded
|
|
19
|
+
} = useSideNavStateContext();
|
|
20
|
+
const {
|
|
21
|
+
isSmallScreen
|
|
22
|
+
} = useResponsive();
|
|
23
|
+
return /*#__PURE__*/createPortal(/*#__PURE__*/_jsxs(Flex, {
|
|
24
|
+
flexDirection: "column",
|
|
25
|
+
gap: "s",
|
|
26
|
+
children: [/*#__PURE__*/_jsx(Divider, {
|
|
27
|
+
light: true,
|
|
28
|
+
display: ["none", "none", "none", "block"]
|
|
29
|
+
}), /*#__PURE__*/_jsx(SideNavCurrentViewSection, {
|
|
30
|
+
...viewingState,
|
|
31
|
+
isExpanded: isExpanded,
|
|
32
|
+
isSmallScreen: isSmallScreen
|
|
33
|
+
})]
|
|
34
|
+
}), container);
|
|
35
|
+
};
|
|
36
|
+
RenderCurrentViewSection.propTypes = {
|
|
37
|
+
viewingState: PropTypes.shape(CurrentViewSectionShape)
|
|
38
|
+
};
|
|
39
|
+
export default RenderCurrentViewSection;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const BREAKPOINTS = {
|
|
2
|
+
SMALL_SCREEN: 900
|
|
3
|
+
};
|
|
4
|
+
export const DEFAULT_WIDTHS = {
|
|
5
|
+
normal: 300,
|
|
6
|
+
large: 500,
|
|
7
|
+
min: 200,
|
|
8
|
+
max: 700
|
|
9
|
+
};
|
|
10
|
+
export const RESIZE_CONFIG = {
|
|
11
|
+
MOBILE_MIN_HEIGHT: 200,
|
|
12
|
+
MOBILE_MAX_HEIGHT_RATIO: 0.8,
|
|
13
|
+
RESIZE_DEBOUNCE_MS: 100
|
|
14
|
+
};
|
|
15
|
+
export const ITEM_TYPES = {
|
|
16
|
+
COMPONENT: "component",
|
|
17
|
+
LINK: "link",
|
|
18
|
+
BUTTON: "button"
|
|
19
|
+
};
|
|
20
|
+
export const BADGE_VARIANTS = ["success", "warning", "danger", "primaryLight", "primary", "primaryDark", "secondary"];
|
|
21
|
+
export const CURRENT_VIEW_SECTION_ID = "side-nav-current-view-section";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState, useMemo } from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
4
|
+
const SideNavStateContext = /*#__PURE__*/createContext();
|
|
5
|
+
export const useSideNavStateContext = () => {
|
|
6
|
+
const context = useContext(SideNavStateContext);
|
|
7
|
+
if (!context) {
|
|
8
|
+
throw new Error("useSideNavStateContext must be used within a SideNavStateProvider");
|
|
9
|
+
}
|
|
10
|
+
return context;
|
|
11
|
+
};
|
|
12
|
+
export const SideNavStateProvider = _ref => {
|
|
13
|
+
let {
|
|
14
|
+
children
|
|
15
|
+
} = _ref;
|
|
16
|
+
const [expandedItem, setExpandedItem] = useState(null);
|
|
17
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
18
|
+
const [expandedWidth, setExpandedWidth] = useState(null);
|
|
19
|
+
const value = useMemo(() => ({
|
|
20
|
+
expandedItem,
|
|
21
|
+
setExpandedItem,
|
|
22
|
+
isExpanded,
|
|
23
|
+
setIsExpanded,
|
|
24
|
+
expandedWidth,
|
|
25
|
+
setExpandedWidth
|
|
26
|
+
}), [expandedItem, isExpanded, expandedWidth]);
|
|
27
|
+
return /*#__PURE__*/_jsx(SideNavStateContext.Provider, {
|
|
28
|
+
value: value,
|
|
29
|
+
children: children
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
SideNavStateProvider.propTypes = {
|
|
33
|
+
children: PropTypes.node.isRequired,
|
|
34
|
+
initialViewing: PropTypes.any
|
|
35
|
+
};
|
|
36
|
+
export default SideNavStateContext;
|
|
37
|
+
SideNavStateProvider.__docgenInfo = {
|
|
38
|
+
"description": "",
|
|
39
|
+
"methods": [],
|
|
40
|
+
"displayName": "SideNavStateProvider",
|
|
41
|
+
"props": {
|
|
42
|
+
"children": {
|
|
43
|
+
"description": "",
|
|
44
|
+
"type": {
|
|
45
|
+
"name": "node"
|
|
46
|
+
},
|
|
47
|
+
"required": true
|
|
48
|
+
},
|
|
49
|
+
"initialViewing": {
|
|
50
|
+
"description": "",
|
|
51
|
+
"type": {
|
|
52
|
+
"name": "any"
|
|
53
|
+
},
|
|
54
|
+
"required": false
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { calculateDesktopWidth, calculateMobileHeight, applyResizeCursor, removeResizeCursor } from "../utils/resizeUtils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Custom hook to handle resize functionality
|
|
6
|
+
* @param {React.RefObject} expandedRef - Ref to the expanded panel
|
|
7
|
+
* @param {boolean} isSmallScreen - Whether we're on a small screen
|
|
8
|
+
* @param {number} expandedItem - Currently expanded item index
|
|
9
|
+
* @param {Function} onWidthChange - Callback to update width state
|
|
10
|
+
* @returns {Object} Object containing resize state and handlers
|
|
11
|
+
*/
|
|
12
|
+
const useResize = (expandedRef, isSmallScreen, expandedItem, onWidthChange) => {
|
|
13
|
+
const [isResizing, setIsResizing] = useState(false);
|
|
14
|
+
const [resizeStartY, setResizeStartY] = useState(0);
|
|
15
|
+
const [resizeStartHeight, setResizeStartHeight] = useState(0);
|
|
16
|
+
const handleResizeStart = e => {
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
setIsResizing(true);
|
|
19
|
+
applyResizeCursor(isSmallScreen);
|
|
20
|
+
if (isSmallScreen && expandedRef.current) {
|
|
21
|
+
setResizeStartY(e.clientY);
|
|
22
|
+
setResizeStartHeight(expandedRef.current.offsetHeight);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const handleResizeMove = useCallback(e => {
|
|
26
|
+
if (!isResizing || !expandedRef.current) return;
|
|
27
|
+
if (isSmallScreen) {
|
|
28
|
+
// Vertical resizing for small screens
|
|
29
|
+
const newHeight = calculateMobileHeight(e.clientY, resizeStartY, resizeStartHeight);
|
|
30
|
+
expandedRef.current.style.height = `${newHeight}px`;
|
|
31
|
+
} else {
|
|
32
|
+
// Horizontal resizing for large screens
|
|
33
|
+
const parentContainer = expandedRef.current.parentElement;
|
|
34
|
+
const sideNavItems = parentContainer.querySelector('[data-testid="side-nav-items"]');
|
|
35
|
+
if (!sideNavItems) return;
|
|
36
|
+
const newWidth = calculateDesktopWidth(e.clientX, sideNavItems);
|
|
37
|
+
|
|
38
|
+
// Update the width state through the callback
|
|
39
|
+
if (onWidthChange) {
|
|
40
|
+
onWidthChange(newWidth);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}, [isResizing, expandedRef, isSmallScreen, resizeStartY, resizeStartHeight, onWidthChange]);
|
|
44
|
+
const handleResizeEnd = useCallback(() => {
|
|
45
|
+
setIsResizing(false);
|
|
46
|
+
removeResizeCursor();
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
// Add global event listeners for resize
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (isResizing) {
|
|
52
|
+
const handleMouseMove = e => {
|
|
53
|
+
handleResizeMove(e);
|
|
54
|
+
};
|
|
55
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
56
|
+
document.addEventListener("mouseup", handleResizeEnd);
|
|
57
|
+
return () => {
|
|
58
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
59
|
+
document.removeEventListener("mouseup", handleResizeEnd);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}, [isResizing, expandedItem, isSmallScreen, resizeStartY, resizeStartHeight, onWidthChange, handleResizeMove, handleResizeEnd]);
|
|
63
|
+
return {
|
|
64
|
+
isResizing,
|
|
65
|
+
handleResizeStart,
|
|
66
|
+
handleResizeMove,
|
|
67
|
+
handleResizeEnd
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
export default useResize;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { BREAKPOINTS, RESIZE_CONFIG } from "../constants/sideNav";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Custom hook to handle responsive behavior
|
|
6
|
+
* @param {number} breakpoint - Breakpoint for small screen detection
|
|
7
|
+
* @returns {Object} Object containing responsive state
|
|
8
|
+
*/
|
|
9
|
+
const useResponsive = function () {
|
|
10
|
+
let breakpoint = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : BREAKPOINTS.SMALL_SCREEN;
|
|
11
|
+
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
|
12
|
+
const isSmallScreen = windowWidth < breakpoint;
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
let timeoutId;
|
|
15
|
+
const handleResize = () => {
|
|
16
|
+
clearTimeout(timeoutId);
|
|
17
|
+
timeoutId = setTimeout(() => {
|
|
18
|
+
setWindowWidth(window.innerWidth);
|
|
19
|
+
}, RESIZE_CONFIG.RESIZE_DEBOUNCE_MS);
|
|
20
|
+
};
|
|
21
|
+
window.addEventListener("resize", handleResize);
|
|
22
|
+
return () => {
|
|
23
|
+
window.removeEventListener("resize", handleResize);
|
|
24
|
+
clearTimeout(timeoutId);
|
|
25
|
+
};
|
|
26
|
+
}, []);
|
|
27
|
+
return {
|
|
28
|
+
windowWidth,
|
|
29
|
+
isSmallScreen
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export default useResponsive;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useEffect, useRef, useMemo } from "react";
|
|
2
|
+
import { findFirstExpandedByDefault } from "../utils/itemUtils";
|
|
3
|
+
import { getInitialWidth as getInitialWidthUtil } from "../utils/resizeUtils";
|
|
4
|
+
import useResponsive from "./useResponsive";
|
|
5
|
+
import { useSideNavStateContext } from "../context/SideNavStateProvider";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Custom hook to manage SideNavV2 state
|
|
9
|
+
* @param {Array} items - Navigation items
|
|
10
|
+
* @returns {Object} Object containing all state and handlers
|
|
11
|
+
*/
|
|
12
|
+
const useSideNavState = items => {
|
|
13
|
+
const {
|
|
14
|
+
expandedItem,
|
|
15
|
+
setExpandedItem,
|
|
16
|
+
isExpanded,
|
|
17
|
+
setIsExpanded,
|
|
18
|
+
expandedWidth,
|
|
19
|
+
setExpandedWidth
|
|
20
|
+
} = useSideNavStateContext();
|
|
21
|
+
const expandedRef = useRef(null);
|
|
22
|
+
const navItemRefs = useRef({});
|
|
23
|
+
const firstExpandedItemByDefault = findFirstExpandedByDefault(items);
|
|
24
|
+
|
|
25
|
+
// Initialize expanded item by default
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (firstExpandedItemByDefault >= 0) {
|
|
28
|
+
setExpandedItem(firstExpandedItemByDefault);
|
|
29
|
+
}
|
|
30
|
+
}, [firstExpandedItemByDefault, setExpandedItem]);
|
|
31
|
+
|
|
32
|
+
// Set initial width when an item is expanded
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (expandedItem !== null) {
|
|
35
|
+
const currentItem = items[expandedItem];
|
|
36
|
+
const initialWidth = getInitialWidthUtil(currentItem);
|
|
37
|
+
setExpandedWidth(initialWidth);
|
|
38
|
+
}
|
|
39
|
+
}, [expandedItem, items, setExpandedWidth]);
|
|
40
|
+
|
|
41
|
+
// Focus on expanded item when it changes
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (expandedItem !== null && expandedRef.current) {
|
|
44
|
+
expandedRef.current.focus();
|
|
45
|
+
}
|
|
46
|
+
}, [expandedItem]);
|
|
47
|
+
const handleItemClick = item => {
|
|
48
|
+
const {
|
|
49
|
+
index: itemIndex,
|
|
50
|
+
actionType,
|
|
51
|
+
onClick: onButtonClick
|
|
52
|
+
} = item;
|
|
53
|
+
if (actionType === "link" || actionType === "button") {
|
|
54
|
+
setExpandedItem(null);
|
|
55
|
+
onButtonClick && onButtonClick(item);
|
|
56
|
+
} else {
|
|
57
|
+
setExpandedItem(itemIndex === expandedItem ? null : itemIndex);
|
|
58
|
+
onButtonClick && onButtonClick(item);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const handleBlur = item => {
|
|
62
|
+
handleItemClick(item);
|
|
63
|
+
};
|
|
64
|
+
const handleExpandToggle = () => {
|
|
65
|
+
setIsExpanded(!isExpanded);
|
|
66
|
+
};
|
|
67
|
+
const handleWidthChange = newWidth => {
|
|
68
|
+
setExpandedWidth(newWidth);
|
|
69
|
+
};
|
|
70
|
+
const {
|
|
71
|
+
isSmallScreen
|
|
72
|
+
} = useResponsive();
|
|
73
|
+
const isExpandedOnBigScreen = useMemo(() => isExpanded && !isSmallScreen, [isExpanded, isSmallScreen]);
|
|
74
|
+
return {
|
|
75
|
+
// State
|
|
76
|
+
expandedItem,
|
|
77
|
+
isExpanded: isExpandedOnBigScreen,
|
|
78
|
+
expandedWidth,
|
|
79
|
+
expandedRef,
|
|
80
|
+
navItemRefs,
|
|
81
|
+
// Handlers
|
|
82
|
+
handleItemClick,
|
|
83
|
+
handleBlur,
|
|
84
|
+
handleExpandToggle,
|
|
85
|
+
handleWidthChange
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
export default useSideNavState;
|