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.
Files changed (35) hide show
  1. package/es/components/Divider/index.js +1 -1
  2. package/es/components/Icon/index.js +45 -27
  3. package/es/components/Popover/index.js +9 -1
  4. package/es/components/SideNavV2/NavItem.js +231 -0
  5. package/es/components/SideNavV2/SideNav.js +295 -0
  6. package/es/components/SideNavV2/SideNavV2.stories.js +382 -0
  7. package/es/components/SideNavV2/__tests__/resize.test.js +129 -0
  8. package/es/components/SideNavV2/__tests__/sections.test.js +333 -0
  9. package/es/components/SideNavV2/components/BaseSection.js +178 -0
  10. package/es/components/SideNavV2/components/CurrentViewSectionPortalTarget.js +35 -0
  11. package/es/components/SideNavV2/components/ExpandedPanel.js +161 -0
  12. package/es/components/SideNavV2/components/ItemSection.js +156 -0
  13. package/es/components/SideNavV2/components/PanelControl.js +128 -0
  14. package/es/components/SideNavV2/components/RenderCurrentViewSection.js +39 -0
  15. package/es/components/SideNavV2/components/index.js +4 -0
  16. package/es/components/SideNavV2/constants/sideNav.js +21 -0
  17. package/es/components/SideNavV2/context/SideNavStateProvider.js +57 -0
  18. package/es/components/SideNavV2/hooks/index.js +3 -0
  19. package/es/components/SideNavV2/hooks/useResize.js +70 -0
  20. package/es/components/SideNavV2/hooks/useResponsive.js +32 -0
  21. package/es/components/SideNavV2/hooks/useSideNavState.js +88 -0
  22. package/es/components/SideNavV2/index.js +3 -0
  23. package/es/components/SideNavV2/sections/SideNavCurrentViewSection.js +124 -0
  24. package/es/components/SideNavV2/sections/SideNavPinnedSection.js +125 -0
  25. package/es/components/SideNavV2/sections/SideNavTeamsSection.js +91 -0
  26. package/es/components/SideNavV2/styles/SideNavV2.styles.js +156 -0
  27. package/es/components/SideNavV2/types/sideNav.js +53 -0
  28. package/es/components/SideNavV2/utils/index.js +2 -0
  29. package/es/components/SideNavV2/utils/itemUtils.js +51 -0
  30. package/es/components/SideNavV2/utils/resizeUtils.js +57 -0
  31. package/es/components/StatusDot/StatusDot.stories.js +59 -72
  32. package/es/components/StatusDot/index.js +14 -5
  33. package/es/components.test.js +4 -0
  34. package/es/index.js +1 -0
  35. package/package.json +6 -2
@@ -0,0 +1,124 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import styled from "styled-components";
4
+ import { H5, H6 } from "../../Typography";
5
+ import Avatar from "../../Avatar";
6
+ import { NavLink } from "react-router-dom";
7
+ import themeGet from "@styled-system/theme-get";
8
+ import Flex from "../../Flex";
9
+ import Popover from "../../Popover";
10
+ import { CurrentViewSectionShape } from "../types/sideNav";
11
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
+ const SubNavList = styled.ul.withConfig({
13
+ displayName: "SideNavCurrentViewSection__SubNavList",
14
+ componentId: "sc-12clz4a-0"
15
+ })(["list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:", ";width:100%;"], props => themeGet("space.s")(props));
16
+ const StyledNavLink = styled(NavLink).withConfig({
17
+ displayName: "SideNavCurrentViewSection__StyledNavLink",
18
+ componentId: "sc-12clz4a-1"
19
+ })(["font-size:", ";border-left:2px solid transparent;padding-left:", ";transition:border-left 0.2s ease;color:", ";text-decoration:none;span{display:block;padding:2px 0;}&:hover,&:focus{outline:none;color:", ";}&.active{border-left:2px solid ", ";color:", ";}"], themeGet("fontSizes.1"), themeGet("space.s"), themeGet("colors.greyDarkest"), themeGet("colors.primary"), themeGet("colors.primary"), themeGet("colors.primary"));
20
+ const CurrentViewContent = _ref => {
21
+ let {
22
+ name,
23
+ badge,
24
+ subNav,
25
+ isExpanded
26
+ } = _ref;
27
+ return /*#__PURE__*/_jsxs(Flex, {
28
+ flexDirection: "column",
29
+ gap: "s",
30
+ p: isExpanded ? "0" : "s",
31
+ children: [/*#__PURE__*/_jsx(H6, {
32
+ sizing: "small",
33
+ color: "greyDark",
34
+ children: "Currently Viewing"
35
+ }), /*#__PURE__*/_jsxs(Flex, {
36
+ flexWrap: "wrap",
37
+ gap: "s",
38
+ mt: isExpanded ? "0" : "s",
39
+ alignItems: "center",
40
+ children: [/*#__PURE__*/_jsx(H5, {
41
+ fontWeight: "bold",
42
+ children: name
43
+ }), badge]
44
+ }), /*#__PURE__*/_jsx(SubNavList, {
45
+ children: subNav?.map(_ref2 => {
46
+ let {
47
+ name,
48
+ link
49
+ } = _ref2;
50
+ return /*#__PURE__*/_jsx(StyledNavLink, {
51
+ to: link,
52
+ children: /*#__PURE__*/_jsx("span", {
53
+ children: name
54
+ })
55
+ }, name);
56
+ })
57
+ })]
58
+ });
59
+ };
60
+ CurrentViewContent.propTypes = {
61
+ ...CurrentViewSectionShape,
62
+ isExpanded: PropTypes.bool
63
+ };
64
+ const SideNavCurrentViewSection = _ref3 => {
65
+ let {
66
+ name,
67
+ badge,
68
+ subNav,
69
+ isExpanded,
70
+ avatar,
71
+ shape,
72
+ isSmallScreen
73
+ } = _ref3;
74
+ const currentViewMarkup = /*#__PURE__*/_jsx(CurrentViewContent, {
75
+ name: name,
76
+ badge: badge,
77
+ subNav: subNav,
78
+ isExpanded: isExpanded
79
+ });
80
+ return /*#__PURE__*/_jsxs(Flex, {
81
+ isExpanded: isExpanded,
82
+ flexDirection: "column",
83
+ gap: "s",
84
+ mt: isExpanded ? "s" : "0",
85
+ children: [(!isExpanded || isSmallScreen) && /*#__PURE__*/_jsx(Popover, {
86
+ direction: isSmallScreen ? "top" : "right",
87
+ width: "fit-content",
88
+ text: currentViewMarkup,
89
+ children: /*#__PURE__*/_jsx(Avatar, {
90
+ image: avatar,
91
+ customSize: "30px",
92
+ shape: shape
93
+ })
94
+ }), isExpanded && !isSmallScreen && currentViewMarkup]
95
+ });
96
+ };
97
+ SideNavCurrentViewSection.propTypes = {
98
+ ...CurrentViewSectionShape,
99
+ isExpanded: PropTypes.bool,
100
+ isSmallScreen: PropTypes.bool
101
+ };
102
+ SideNavCurrentViewSection.__docgenInfo = {
103
+ "description": "",
104
+ "methods": [],
105
+ "displayName": "SideNavCurrentViewSection",
106
+ "props": {
107
+ "isExpanded": {
108
+ "description": "",
109
+ "type": {
110
+ "name": "bool"
111
+ },
112
+ "required": false
113
+ },
114
+ "isSmallScreen": {
115
+ "description": "",
116
+ "type": {
117
+ "name": "bool"
118
+ },
119
+ "required": false
120
+ }
121
+ },
122
+ "composes": ["../types/sideNav"]
123
+ };
124
+ export default SideNavCurrentViewSection;
@@ -0,0 +1,125 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import Popover from "../../Popover";
4
+ import Icon from "../../Icon";
5
+ import Button from "../../Button";
6
+ import BaseSection, { AvatarLink, Item } from "../components/BaseSection";
7
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
8
+ const SideNavPinnedSection = _ref => {
9
+ let {
10
+ items,
11
+ isExpanded
12
+ } = _ref;
13
+ const renderPinnedItem = (item, isExpanded) => {
14
+ const {
15
+ avatar,
16
+ name,
17
+ link,
18
+ shape,
19
+ onUnpin
20
+ } = item;
21
+ return /*#__PURE__*/_jsx(Item, {
22
+ children: isExpanded ? /*#__PURE__*/_jsxs(_Fragment, {
23
+ children: [/*#__PURE__*/_jsx(AvatarLink, {
24
+ avatar: avatar,
25
+ name: name,
26
+ link: link,
27
+ shape: shape,
28
+ showName: true
29
+ }), /*#__PURE__*/_jsx(Popover, {
30
+ text: "Unpin item",
31
+ direction: "right",
32
+ width: "fit-content",
33
+ children: /*#__PURE__*/_jsx(Button, {
34
+ height: "20px",
35
+ width: "20px",
36
+ borderRadius: "20px",
37
+ variant: "borderGrey",
38
+ borderColor: "white",
39
+ onClick: () => onUnpin?.(item),
40
+ children: /*#__PURE__*/_jsx(Icon, {
41
+ icon: ["fas", "thumbtack"],
42
+ secondaryIcon: ["fas", "slash"],
43
+ color: "greyDarkest",
44
+ size: "sm"
45
+ })
46
+ })
47
+ })]
48
+ }) : /*#__PURE__*/_jsx(Popover, {
49
+ text: name,
50
+ direction: "right",
51
+ width: "fit-content",
52
+ children: /*#__PURE__*/_jsx(AvatarLink, {
53
+ avatar: avatar,
54
+ name: name,
55
+ link: link,
56
+ shape: shape,
57
+ showName: false
58
+ })
59
+ })
60
+ }, name);
61
+ };
62
+ return /*#__PURE__*/_jsx(BaseSection, {
63
+ title: "Pinned",
64
+ items: items,
65
+ isExpanded: isExpanded,
66
+ renderItem: renderPinnedItem
67
+ });
68
+ };
69
+ SideNavPinnedSection.propTypes = {
70
+ items: PropTypes.arrayOf(PropTypes.shape({
71
+ avatar: PropTypes.string,
72
+ name: PropTypes.string.isRequired,
73
+ link: PropTypes.string.isRequired,
74
+ shape: PropTypes.string,
75
+ onUnpin: PropTypes.func
76
+ })),
77
+ isExpanded: PropTypes.bool
78
+ };
79
+ SideNavPinnedSection.__docgenInfo = {
80
+ "description": "",
81
+ "methods": [],
82
+ "displayName": "SideNavPinnedSection",
83
+ "props": {
84
+ "items": {
85
+ "description": "",
86
+ "type": {
87
+ "name": "arrayOf",
88
+ "value": {
89
+ "name": "shape",
90
+ "value": {
91
+ "avatar": {
92
+ "name": "string",
93
+ "required": false
94
+ },
95
+ "name": {
96
+ "name": "string",
97
+ "required": true
98
+ },
99
+ "link": {
100
+ "name": "string",
101
+ "required": true
102
+ },
103
+ "shape": {
104
+ "name": "string",
105
+ "required": false
106
+ },
107
+ "onUnpin": {
108
+ "name": "func",
109
+ "required": false
110
+ }
111
+ }
112
+ }
113
+ },
114
+ "required": false
115
+ },
116
+ "isExpanded": {
117
+ "description": "",
118
+ "type": {
119
+ "name": "bool"
120
+ },
121
+ "required": false
122
+ }
123
+ }
124
+ };
125
+ export default SideNavPinnedSection;
@@ -0,0 +1,91 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import Popover from "../../Popover";
4
+ import BaseSection, { AvatarLink, Item } from "../components/BaseSection";
5
+ import { jsx as _jsx } from "react/jsx-runtime";
6
+ const SideNavTeamsSection = _ref => {
7
+ let {
8
+ teams,
9
+ isExpanded
10
+ } = _ref;
11
+ const renderTeamItem = (team, isExpanded) => {
12
+ const {
13
+ avatar,
14
+ name,
15
+ link
16
+ } = team;
17
+ return /*#__PURE__*/_jsx(Item, {
18
+ children: isExpanded ? /*#__PURE__*/_jsx(AvatarLink, {
19
+ avatar: avatar,
20
+ name: name,
21
+ link: link,
22
+ shape: "square",
23
+ showName: true
24
+ }) : /*#__PURE__*/_jsx(Popover, {
25
+ text: name,
26
+ direction: "right",
27
+ width: "fit-content",
28
+ children: /*#__PURE__*/_jsx(AvatarLink, {
29
+ avatar: avatar,
30
+ name: name,
31
+ link: link,
32
+ shape: "square",
33
+ showName: false
34
+ })
35
+ })
36
+ }, name);
37
+ };
38
+ return /*#__PURE__*/_jsx(BaseSection, {
39
+ title: "Your Teams",
40
+ items: teams,
41
+ isExpanded: isExpanded,
42
+ renderItem: renderTeamItem
43
+ });
44
+ };
45
+ SideNavTeamsSection.propTypes = {
46
+ teams: PropTypes.arrayOf(PropTypes.shape({
47
+ avatar: PropTypes.string,
48
+ name: PropTypes.string.isRequired,
49
+ link: PropTypes.string.isRequired
50
+ })),
51
+ isExpanded: PropTypes.bool
52
+ };
53
+ SideNavTeamsSection.__docgenInfo = {
54
+ "description": "",
55
+ "methods": [],
56
+ "displayName": "SideNavTeamsSection",
57
+ "props": {
58
+ "teams": {
59
+ "description": "",
60
+ "type": {
61
+ "name": "arrayOf",
62
+ "value": {
63
+ "name": "shape",
64
+ "value": {
65
+ "avatar": {
66
+ "name": "string",
67
+ "required": false
68
+ },
69
+ "name": {
70
+ "name": "string",
71
+ "required": true
72
+ },
73
+ "link": {
74
+ "name": "string",
75
+ "required": true
76
+ }
77
+ }
78
+ }
79
+ },
80
+ "required": false
81
+ },
82
+ "isExpanded": {
83
+ "description": "",
84
+ "type": {
85
+ "name": "bool"
86
+ },
87
+ "required": false
88
+ }
89
+ }
90
+ };
91
+ export default SideNavTeamsSection;
@@ -0,0 +1,156 @@
1
+ import styled from "styled-components";
2
+ import { css } from "@styled-system/css";
3
+ import { themeGet } from "@styled-system/theme-get";
4
+ import Popover from "../../Popover";
5
+ import { BREAKPOINTS, DEFAULT_WIDTHS } from "../constants/sideNav";
6
+ export const SideNavWrapper = styled("div").withConfig({
7
+ displayName: "SideNavV2styles__SideNavWrapper",
8
+ componentId: "sc-y5tsj2-0"
9
+ })(props => css({
10
+ bg: themeGet("colors.white")(props),
11
+ color: themeGet("colors.greyDarkest")(props),
12
+ minHeight: props.sideNavHeight || "100%",
13
+ height: props.sideNavHeight || "100%",
14
+ maxWidth: "max-content",
15
+ fontFamily: themeGet("fonts.main")(props),
16
+ borderRight: `solid 1px ${themeGet("colors.greyLighter")(props)}`,
17
+ display: "flex",
18
+ alignItems: "stretch",
19
+ alignContent: "stretch",
20
+ [`@media screen and (max-width: ${BREAKPOINTS.SMALL_SCREEN}px)`]: {
21
+ borderRight: "none",
22
+ borderTop: `solid 1px ${themeGet("colors.greyLighter")(props)}`,
23
+ width: "100%",
24
+ height: "auto",
25
+ minWidth: "auto",
26
+ maxWidth: "initial",
27
+ minHeight: "initial",
28
+ maxHeight: themeGet("appScale.sidebarMobileHeight", "100vh")(props),
29
+ position: "fixed",
30
+ bottom: "0",
31
+ left: "0",
32
+ zIndex: "6",
33
+ flexDirection: "column-reverse"
34
+ }
35
+ }));
36
+ export const SideNavItems = styled("div").withConfig({
37
+ displayName: "SideNavV2styles__SideNavItems",
38
+ componentId: "sc-y5tsj2-1"
39
+ })(props => css({
40
+ width: props.isExpanded ? "200px" : themeGet("appScale.sideNavSize")(props),
41
+ minHeight: "inherit",
42
+ height: "100%",
43
+ overflowX: "hidden",
44
+ overflowY: "auto",
45
+ display: "flex",
46
+ flexDirection: "column",
47
+ flexShrink: "0",
48
+ padding: "10px",
49
+ gap: "s",
50
+ alignItems: props.isExpanded ? "stretch" : "center",
51
+ justifyContent: "flex-start",
52
+ textAlign: props.isExpanded ? "left" : "center",
53
+ position: "relative",
54
+ [`@media screen and (max-width: ${BREAKPOINTS.SMALL_SCREEN}px)`]: {
55
+ height: "auto",
56
+ flexDirection: "row",
57
+ justifyContent: "space-around",
58
+ alignItems: "center",
59
+ width: "auto"
60
+ }
61
+ }));
62
+ export const PanelControlTooltip = styled(Popover).withConfig({
63
+ displayName: "SideNavV2styles__PanelControlTooltip",
64
+ componentId: "sc-y5tsj2-2"
65
+ })(["width:fit-content;margin:2px 0 4px 0;", ""], props => props.position === "absolute" && css({
66
+ position: "absolute",
67
+ right: themeGet("space.r")(props),
68
+ top: themeGet("space.r")(props)
69
+ }));
70
+ export const PanelControl = styled("button").withConfig({
71
+ displayName: "SideNavV2styles__PanelControl",
72
+ componentId: "sc-y5tsj2-3"
73
+ })(props => css({
74
+ borderRadius: "50%",
75
+ width: "20px",
76
+ height: "20px",
77
+ background: themeGet("colors.greyLighter")(props),
78
+ color: themeGet("colors.greyDarker")(props),
79
+ fontSize: "1.2rem",
80
+ display: "flex",
81
+ alignItems: "center",
82
+ justifyContent: "center",
83
+ cursor: "pointer",
84
+ border: "none"
85
+ }));
86
+ export const ResizeHandle = styled("div").withConfig({
87
+ displayName: "SideNavV2styles__ResizeHandle",
88
+ componentId: "sc-y5tsj2-4"
89
+ })(props => css({
90
+ position: "absolute",
91
+ right: "0",
92
+ top: "0",
93
+ bottom: "0",
94
+ width: "6px",
95
+ height: "100%",
96
+ cursor: "col-resize",
97
+ backgroundColor: "rgba(0, 0, 0, 0.05)",
98
+ zIndex: "1000",
99
+ "&:hover": {
100
+ backgroundColor: "rgba(0, 0, 0, 0.1)"
101
+ },
102
+ "&::before": {
103
+ content: '""',
104
+ position: "absolute",
105
+ right: "1px",
106
+ top: "50%",
107
+ transform: "translateY(-50%)",
108
+ width: "3px",
109
+ height: "40px",
110
+ backgroundColor: themeGet("colors.grey")(props),
111
+ borderRadius: "3px"
112
+ },
113
+ [`@media screen and (max-width: ${BREAKPOINTS.SMALL_SCREEN}px)`]: {
114
+ right: "auto",
115
+ top: "0",
116
+ left: "0",
117
+ bottom: "0",
118
+ width: "100%",
119
+ height: "6px",
120
+ cursor: "row-resize",
121
+ "&::before": {
122
+ right: "auto",
123
+ top: "50%",
124
+ left: "50%",
125
+ transform: "translate(-50%, -50%)",
126
+ width: "40px",
127
+ height: "3px"
128
+ }
129
+ }
130
+ }));
131
+ export const SideNavExpanded = styled("div").withConfig({
132
+ displayName: "SideNavV2styles__SideNavExpanded",
133
+ componentId: "sc-y5tsj2-5"
134
+ })(props => css({
135
+ position: "relative",
136
+ display: props.active ? "block" : "none",
137
+ width: props.width ? `${props.width}px` : "auto",
138
+ minWidth: `${DEFAULT_WIDTHS.min}px`,
139
+ maxWidth: `${DEFAULT_WIDTHS.max}px`,
140
+ height: "inherit",
141
+ overflowY: "auto",
142
+ overflowX: "hidden",
143
+ padding: "16px",
144
+ borderLeft: `solid 1px ${themeGet("colors.greyLighter")(props)}`,
145
+ "&:focus": {
146
+ outline: "0"
147
+ },
148
+ [`@media screen and (max-width: ${BREAKPOINTS.SMALL_SCREEN}px)`]: {
149
+ width: "100%",
150
+ minWidth: "initial",
151
+ maxWidth: "initial",
152
+ borderLeft: "none",
153
+ borderBottom: `solid 1px ${themeGet("colors.greyLighter")(props)}`,
154
+ height: "calc(" + themeGet("appScale.sidebarMobileHeight", "100vh")(props) + " - " + themeGet("appScale.newNavBarSize")(props) + ")"
155
+ }
156
+ }));
@@ -0,0 +1,53 @@
1
+ import PropTypes from "prop-types";
2
+ import { BADGE_VARIANTS, ITEM_TYPES } from "../constants/sideNav";
3
+ export const NavigationItemShape = {
4
+ iconName: PropTypes.string.isRequired,
5
+ name: PropTypes.string.isRequired,
6
+ badgeNumber: PropTypes.string,
7
+ badgeDot: PropTypes.bool,
8
+ hide: PropTypes.bool,
9
+ large: PropTypes.bool,
10
+ bottomAligned: PropTypes.bool,
11
+ actionType: PropTypes.oneOf(Object.values(ITEM_TYPES)).isRequired,
12
+ component: PropTypes.elementType,
13
+ link: PropTypes.string,
14
+ onClick: PropTypes.func,
15
+ isActive: PropTypes.bool,
16
+ pageSpecific: PropTypes.bool,
17
+ isExpandedByDefault: PropTypes.bool
18
+ };
19
+ export const ViewingSectionShape = {
20
+ teamName: PropTypes.string.isRequired,
21
+ avatar: PropTypes.string,
22
+ shape: PropTypes.string,
23
+ teamLink: PropTypes.string.isRequired,
24
+ teamType: PropTypes.string,
25
+ badgeVariant: PropTypes.oneOf(BADGE_VARIANTS),
26
+ subNav: PropTypes.arrayOf(PropTypes.shape({
27
+ name: PropTypes.string.isRequired,
28
+ link: PropTypes.string.isRequired
29
+ }))
30
+ };
31
+ export const TeamShape = {
32
+ avatar: PropTypes.string,
33
+ name: PropTypes.string.isRequired,
34
+ link: PropTypes.string.isRequired
35
+ };
36
+ export const PinnedItemShape = {
37
+ avatar: PropTypes.string,
38
+ name: PropTypes.string.isRequired,
39
+ link: PropTypes.string.isRequired,
40
+ shape: PropTypes.string,
41
+ onUnpin: PropTypes.func
42
+ };
43
+ export const SubNavItemShape = {
44
+ name: PropTypes.string.isRequired,
45
+ link: PropTypes.string.isRequired
46
+ };
47
+ export const CurrentViewSectionShape = {
48
+ name: PropTypes.string.isRequired,
49
+ avatar: PropTypes.string,
50
+ shape: PropTypes.string,
51
+ badge: PropTypes.node,
52
+ subNav: PropTypes.arrayOf(SubNavItemShape)
53
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./itemUtils";
2
+ export * from "./resizeUtils";
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Categorizes navigation items into different sections based on their properties
3
+ * @param {Array} items - Array of navigation items
4
+ * @returns {Object} Object containing categorized items
5
+ */
6
+ export const categorizeItems = items => {
7
+ const allItems = items.map((item, index) => ({
8
+ ...item,
9
+ index
10
+ }));
11
+ const topAlignedItems = allItems.filter(item => !item.bottomAligned && !item.pageSpecific);
12
+ const pageSpecificItems = allItems.filter(item => !item.bottomAligned && item.pageSpecific);
13
+ const bottomAlignedItems = allItems.filter(item => item.bottomAligned);
14
+ return {
15
+ topAlignedItems,
16
+ pageSpecificItems,
17
+ bottomAlignedItems,
18
+ allItems
19
+ };
20
+ };
21
+
22
+ /**
23
+ * Filters out hidden items from a list
24
+ * @param {Array} items - Array of navigation items
25
+ * @returns {Array} Filtered items without hidden ones
26
+ */
27
+ export const filterVisibleItems = items => {
28
+ return items.filter(item => !item.hide);
29
+ };
30
+
31
+ /**
32
+ * Finds the first item that should be expanded by default
33
+ * @param {Array} items - Array of navigation items
34
+ * @returns {number} Index of the first item to expand by default, or -1 if none
35
+ */
36
+ export const findFirstExpandedByDefault = items => {
37
+ return items.findIndex(item => item.isExpandedByDefault && !item.hide);
38
+ };
39
+
40
+ /**
41
+ * Determines if an item should be active based on its type and state
42
+ * @param {Object} item - Navigation item
43
+ * @param {number} expandedItem - Currently expanded item index
44
+ * @returns {boolean} Whether the item should be active
45
+ */
46
+ export const isItemActive = (item, expandedItem) => {
47
+ if (item.actionType === "link") {
48
+ return item.isActive;
49
+ }
50
+ return expandedItem === item.index;
51
+ };
@@ -0,0 +1,57 @@
1
+ import { DEFAULT_WIDTHS, RESIZE_CONFIG } from "../constants/sideNav";
2
+
3
+ /**
4
+ * Calculates the clamped width for desktop resize
5
+ * @param {number} clientX - Mouse X position
6
+ * @param {Element} sideNavItems - SideNavItems element
7
+ * @returns {number} Clamped width value
8
+ */
9
+ export const calculateDesktopWidth = (clientX, sideNavItems) => {
10
+ if (!sideNavItems) return DEFAULT_WIDTHS.normal;
11
+ const sideNavRect = sideNavItems.getBoundingClientRect();
12
+ const newWidth = clientX - sideNavRect.right;
13
+ return Math.max(DEFAULT_WIDTHS.min, Math.min(DEFAULT_WIDTHS.max, newWidth));
14
+ };
15
+
16
+ /**
17
+ * Calculates the clamped height for mobile resize
18
+ * @param {number} clientY - Mouse Y position
19
+ * @param {number} resizeStartY - Starting Y position
20
+ * @param {number} resizeStartHeight - Starting height
21
+ * @returns {number} Clamped height value
22
+ */
23
+ export const calculateMobileHeight = (clientY, resizeStartY, resizeStartHeight) => {
24
+ const deltaY = clientY - resizeStartY;
25
+ const newHeight = resizeStartHeight - deltaY; // Reversed direction
26
+
27
+ const minHeight = RESIZE_CONFIG.MOBILE_MIN_HEIGHT;
28
+ const maxHeight = window.innerHeight * RESIZE_CONFIG.MOBILE_MAX_HEIGHT_RATIO;
29
+ return Math.max(minHeight, Math.min(maxHeight, newHeight));
30
+ };
31
+
32
+ /**
33
+ * Gets the initial width for an expanded item
34
+ * @param {Object} item - Navigation item
35
+ * @returns {number} Initial width
36
+ */
37
+ export const getInitialWidth = item => {
38
+ const isLarge = item?.large;
39
+ return isLarge ? DEFAULT_WIDTHS.large : DEFAULT_WIDTHS.normal;
40
+ };
41
+
42
+ /**
43
+ * Applies resize cursor styles to document body
44
+ * @param {boolean} isSmallScreen - Whether we're on a small screen
45
+ */
46
+ export const applyResizeCursor = isSmallScreen => {
47
+ document.body.style.cursor = isSmallScreen ? "row-resize" : "col-resize";
48
+ document.body.style.userSelect = "none";
49
+ };
50
+
51
+ /**
52
+ * Removes resize cursor styles from document body
53
+ */
54
+ export const removeResizeCursor = () => {
55
+ document.body.style.cursor = "";
56
+ document.body.style.userSelect = "";
57
+ };