orcs-design-system 3.3.48 → 3.3.54

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/README.md CHANGED
@@ -36,15 +36,7 @@ A new browser window will open with a random localhost port. ORCS runs [Storyboo
36
36
 
37
37
  All library components and files are located in `/lib`. Static files are located in `/assets`.
38
38
 
39
- ### Viewing changes in PM / TD.
40
-
41
- As an alternative to `npm link` you can run `npm run dist` and then copy the `es` folder directly into TD or PM:
42
-
43
- `cp -R es/ ../../../team-directory/node_modules/orcs-design-system/`
44
-
45
- **_This has now been been made easier with using Nodemon and a custom script. Read on for how to set this up._**
46
-
47
- ### Working with orcs locally (hot reloading).
39
+ ### Viewing local orcs changes in other projects (with hot reloading)
48
40
 
49
41
  1. `cp .env.example .env`
50
42
  2. OPTIONAL: Update `WORKING_DIR` in `.env` if orcs resides in a different working directory to your project. Else-wise Orcs will assume the project is one level up from itself.
@@ -63,39 +55,6 @@ snapshot: {
63
55
 
64
56
  Now you can make any changes in orcs and it will build and then copy the es from the build into your project's node_modules. Run `npm install` in your project dir if you want to revert to the npm installation.
65
57
 
66
- ### Symlinking with `npm link`
67
-
68
- ##### We haven't had much success with this recently
69
-
70
- If you need to do `npm link` in your local environment you might encounter the following issues:
71
-
72
- - https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate-react
73
- - https://github.com/styled-components/styled-components/issues/2379
74
-
75
- Both react and styled-components cause duplicate instance issue after npm link, to fix that we need to ensure both app project and lib project are sharing the same instance of them.
76
-
77
- In `orcs-design-system` folder:
78
-
79
- ```
80
- npm link {PATH_APP_REPO}/node_modules/react
81
- npm link {PATH_APP_REPO}/node_modules/styled-components
82
- npm link
83
- ```
84
-
85
- In `{PATH_APP_REPO}`:
86
-
87
- ```
88
- npm i orcs-design-system
89
- npm link orcs-design-system
90
- npm start
91
- ```
92
-
93
- ### Testing
94
-
95
- ```
96
- npm run test
97
- ```
98
-
99
58
  ### Publishing changes
100
59
 
101
60
  In order to publish a new version, you will have to patch and push your changes.
@@ -105,11 +64,11 @@ After your changes have been merged to master, from your master branch:
105
64
  npm version patch
106
65
  ```
107
66
 
108
- Then create a new pull request to master with the version update.
67
+ This will bump version patch number and make a commit to current branch.
109
68
 
110
69
  ### Deploying to GitHub Pages
111
70
 
112
- ORCS automatically deploys to GitHub Pages when a new version is published. To manually deploy:
71
+ ORCS automatically deploys to GitHub Pages when a new version is published and merged to master. To manually deploy:
113
72
 
114
73
  ```
115
74
  npm run deploy-storybook
@@ -138,17 +97,11 @@ import { Box } from "orcs-design-system"
138
97
 
139
98
  The [ORCS Storybook](https://orchestrated-io.github.io/orcs-design-system) contains documentation for each component, including code examples and props tables.
140
99
 
141
- For components with subcomponents, each subcomponent must be imported. For example, to use `Tabs`:
142
-
143
- ```
144
- import { TabsContainer, Tab } from "orcs-design-system"
145
- ```
146
-
147
100
  ### Using Styled System props
148
101
 
149
- The design system components are built with [Styled System](https://styled-system.com/) props. Generally, components can access the `space` and `layout` prop categories, with additional prop categories on a per-component basis. Check the _Properties_ section in a component's documentation to see what props it can use. Custom props will be listed in the props table.
102
+ The design system components are built with [Styled System](https://github.com/styled-system/styled-system) props. Generally, components can access the `space` and `layout` prop categories, with additional prop categories on a per-component basis. Check the _Properties_ section in a component's documentation to see what props it can use. Custom props will be listed in the props table.
150
103
 
151
- As a guide to using these props, see the [Styled System reference table](https://styled-system.com/table).
104
+ As a guide to using these props, see the [Styled System reference table](https://github.com/styled-system/styled-system/blob/master/docs/table.md).
152
105
 
153
106
  The design system's theme scales are contained in [systemtheme](https://github.com/orchestrated-io/orcs-design-system/blob/master/packages/orcs-design-system/lib/systemtheme.js).
154
107
 
@@ -341,4 +341,86 @@ 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
+ });
344
426
  });
@@ -195,7 +195,7 @@ describe("SideNavTeamsSection", () => {
195
195
  teams: mockTeams,
196
196
  isExpanded: false
197
197
  }));
198
- expect(screen.queryByText("Your Teams")).not.toBeInTheDocument();
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: expandedRef,
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": {
@@ -35,6 +35,11 @@ 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);
38
43
  const firstExpandedItemByDefault = findFirstExpandedByDefault(items);
39
44
 
40
45
  // Initialize expanded item by default
@@ -77,43 +82,61 @@ const useSideNavState = items => {
77
82
  // Extract common mouse event logic to reduce duplication
78
83
  const createMouseEventHandlers = useCallback(() => {
79
84
  const baseHandler = shouldExpand => {
80
- if (isLocked || isToggling) {
85
+ if (isToggling) {
81
86
  return;
82
87
  }
83
88
  isHoveringRef.current = true;
89
+ isHoveringInWrapperRef.current = true;
84
90
 
85
- // Only auto-expand if the user has manually collapsed it before
91
+ // Only auto-expand navigation if the user has manually collapsed it before
86
92
  // AND it wasn't collapsed while having an expanded panel
87
- if (shouldExpand && hasBeenManuallyCollapsedRef.current && !wasCollapsedWithExpandedPanelRef.current) {
93
+ // AND the navigation is not locked
94
+ if (shouldExpand && !isLocked && hasBeenManuallyCollapsedRef.current && !wasCollapsedWithExpandedPanelRef.current) {
88
95
  setIsExpanded(true);
89
96
  wasExpandedByHoverRef.current = true;
90
97
  }
98
+
99
+ // Auto-expand previously opened panel if hovering back in
100
+ // This should work regardless of navigation lock state
101
+ if (shouldExpand && previouslyOpenedPanelRef.current !== null && !wasPanelManuallyClosedRef.current && expandedItem === null) {
102
+ setExpandedItem(previouslyOpenedPanelRef.current);
103
+ // Mark that this panel was opened by hover (not manually)
104
+ wasPanelManuallyClosedRef.current = false;
105
+ }
91
106
  };
92
107
  return {
93
108
  handleEnter: () => baseHandler(true),
94
109
  handleLeave: () => {
95
- if (isLocked) return;
96
110
  isHoveringRef.current = false;
111
+ isHoveringInWrapperRef.current = false;
97
112
 
98
- // Only auto-collapse if the user has manually collapsed it before
113
+ // Only auto-collapse navigation if the user has manually collapsed it before
99
114
  // AND it was expanded by hover (not locked)
100
115
  // AND it wasn't collapsed while having an expanded panel
101
- if (hasBeenManuallyCollapsedRef.current && wasExpandedByHoverRef.current && !wasCollapsedWithExpandedPanelRef.current) {
116
+ if (!isLocked && hasBeenManuallyCollapsedRef.current && wasExpandedByHoverRef.current && !wasCollapsedWithExpandedPanelRef.current) {
102
117
  setIsExpanded(false);
103
118
  wasExpandedByHoverRef.current = false;
104
119
  }
105
120
 
121
+ // Auto-hide ExpandedPanel when hovering out (if it was opened by hover)
122
+ // This should work regardless of navigation lock state
123
+ if (expandedItem !== null && previouslyOpenedPanelRef.current === expandedItem && !wasPanelManuallyClosedRef.current) {
124
+ // Store the panel that was open before hiding it
125
+ previouslyOpenedPanelRef.current = expandedItem;
126
+ setExpandedItem(null);
127
+ }
128
+
106
129
  // Reset the wasCollapsedWithExpandedPanelRef flag when hovering out of the entire wrapper
107
130
  // This allows hover behavior to work again after hovering out and back in
108
131
  wasCollapsedWithExpandedPanelRef.current = false;
109
132
  // Don't reset hasBeenManuallyCollapsedRef here - it should persist until user manually expands again
110
133
  }
111
134
  };
112
- }, [isLocked, setIsExpanded, isToggling]);
135
+ }, [isLocked, setIsExpanded, isToggling, expandedItem, setExpandedItem]);
113
136
 
114
137
  // Mouse event handlers for hover state
115
138
  useEffect(() => {
116
- if (!wrapperRef.current || isLocked) return;
139
+ if (!wrapperRef.current) return;
117
140
  const wrapper = wrapperRef.current;
118
141
  const sideNavItems = wrapper.querySelector('[data-testid="side-nav-items"]');
119
142
  const toggleHandle = wrapper.querySelector(".toggle-popover");
@@ -126,12 +149,12 @@ const useSideNavState = items => {
126
149
  const handleSideNavItemsMouseEnter = handleEnter;
127
150
  const handleToggleMouseEnter = handleEnter;
128
151
  const handleSideNavItemsMouseLeave = () => {
129
- if (isLocked || isToggling) return;
152
+ if (isToggling) return;
130
153
  // Don't collapse immediately, let the wrapper handle it
131
154
  // This allows hovering over ExpandedPanel to keep it expanded
132
155
  };
133
156
  const handleToggleMouseLeave = () => {
134
- if (isLocked || isToggling) return;
157
+ if (isToggling) return;
135
158
  // Don't collapse immediately, let the wrapper handle it
136
159
  // This allows hovering over the toggle handle to keep it expanded
137
160
  };
@@ -159,7 +182,7 @@ const useSideNavState = items => {
159
182
  toggleHandle.removeEventListener("mouseleave", handleToggleMouseLeave);
160
183
  }
161
184
  };
162
- }, [isLocked, createMouseEventHandlers, isToggling]);
185
+ }, [createMouseEventHandlers, isToggling]);
163
186
  const handleItemClick = useCallback(item => {
164
187
  const {
165
188
  index: itemIndex,
@@ -175,26 +198,51 @@ const useSideNavState = items => {
175
198
  // If we're already on this panel, toggle it closed
176
199
  if (expandedItem === relatedPanelIndex) {
177
200
  setExpandedItem(null);
201
+ // Mark that this panel was manually closed
202
+ if (previouslyOpenedPanelRef.current === relatedPanelIndex) {
203
+ wasPanelManuallyClosedRef.current = true;
204
+ }
178
205
  } else {
179
206
  // Otherwise, open the related panel
180
207
  setExpandedItem(relatedPanelIndex);
208
+ // Reset manual close flag when opening a panel
209
+ wasPanelManuallyClosedRef.current = false;
181
210
  }
182
211
  } else {
183
212
  // No related panel, close any open panel
184
213
  setExpandedItem(null);
214
+ // Mark that any open panel was manually closed
215
+ if (expandedItem !== null) {
216
+ wasPanelManuallyClosedRef.current = true;
217
+ }
185
218
  }
186
219
  } else {
187
220
  // For button items, just close any open panel
221
+ if (expandedItem !== null) {
222
+ wasPanelManuallyClosedRef.current = true;
223
+ }
188
224
  setExpandedItem(null);
189
225
  }
190
226
  onButtonClick && onButtonClick(item);
191
227
  } else {
192
228
  const wasExpanded = expandedItem !== null;
193
- setExpandedItem(itemIndex === expandedItem ? null : itemIndex);
229
+ const wasClosingPanel = itemIndex === expandedItem;
230
+ if (wasClosingPanel) {
231
+ // Closing a panel - mark it as manually closed
232
+ wasPanelManuallyClosedRef.current = true;
233
+ // Store reference to the panel that was closed
234
+ previouslyOpenedPanelRef.current = itemIndex;
235
+ } else {
236
+ // Opening a panel - reset manual close flag and track that it was opened manually
237
+ wasPanelManuallyClosedRef.current = false;
238
+ // Store reference to the panel that was opened
239
+ previouslyOpenedPanelRef.current = itemIndex;
240
+ }
241
+ setExpandedItem(wasClosingPanel ? null : itemIndex);
194
242
  onButtonClick && onButtonClick(item);
195
243
 
196
244
  // If we just closed an expanded panel, reset the flags to enable hover behavior
197
- if (wasExpanded && itemIndex === expandedItem) {
245
+ if (wasExpanded && wasClosingPanel) {
198
246
  // Reset the wasCollapsedWithExpandedPanelRef flag to enable hover behavior again
199
247
  wasCollapsedWithExpandedPanelRef.current = false;
200
248
  // Don't reset hasBeenManuallyCollapsedRef - the user has still manually collapsed the nav
@@ -278,7 +326,10 @@ const useSideNavState = items => {
278
326
  handleExpandToggle,
279
327
  isLocked,
280
328
  wasExpandedByHover: wasExpandedByHoverRef.current,
281
- hasBeenManuallyCollapsed: hasBeenManuallyCollapsedRef.current
329
+ hasBeenManuallyCollapsed: hasBeenManuallyCollapsedRef.current,
330
+ previouslyOpenedPanel: previouslyOpenedPanelRef.current,
331
+ wasPanelManuallyClosed: wasPanelManuallyClosedRef.current,
332
+ isHoveringInWrapper: isHoveringInWrapperRef.current
282
333
  };
283
334
  };
284
335
  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
  }
@@ -11,7 +11,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  const TagWrapper = styled.div.withConfig({
12
12
  displayName: "Tag__TagWrapper",
13
13
  componentId: "sc-1dh2aa8-0"
14
- })(["", " ", " display:flex;align-items:stretch;height:100%;min-height:", ";justify-content:center;> button{margin:0;}"], space, layout, props => props.small ? themeGet("tagScale.tagHeightSmall") : themeGet("tagScale.tagHeightDefault"));
14
+ })(["", " ", " display:flex;align-items:stretch;height:auto;max-height:max-content;min-height:", ";justify-content:center;> button{margin:0;}"], space, layout, props => props.small ? themeGet("tagScale.tagHeightSmall") : themeGet("tagScale.tagHeightDefault"));
15
15
  const TagValue = styled.button.attrs(props => ({
16
16
  "aria-pressed": props.selected
17
17
  })).withConfig({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orcs-design-system",
3
- "version": "3.3.48",
3
+ "version": "3.3.54",
4
4
  "engines": {
5
5
  "node": "20.12.2"
6
6
  },