@carbon-labs/react-ui-shell 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/es/components/SharkFinIcon.d.ts +24 -0
  2. package/es/components/SharkFinIcon.js +59 -0
  3. package/es/components/SideNav.js +8 -4
  4. package/es/components/SideNavFlyoutMenu.d.ts +141 -0
  5. package/es/components/SideNavFlyoutMenu.js +365 -0
  6. package/es/components/SideNavMenu.d.ts +4 -0
  7. package/es/components/SideNavMenu.js +96 -75
  8. package/es/components/SideNavMenuItem.d.ts +4 -0
  9. package/es/components/SideNavMenuItem.js +17 -4
  10. package/es/components/SideNavToggle.d.ts +4 -0
  11. package/es/components/SideNavToggle.js +9 -1
  12. package/es/index.d.ts +1 -0
  13. package/es/index.js +1 -0
  14. package/es/internal/keyboard/keys.js +13 -1
  15. package/es/internal/setupGetInstanceId.d.ts +11 -0
  16. package/es/internal/setupGetInstanceId.js +19 -0
  17. package/es/internal/useId.d.ts +21 -0
  18. package/es/internal/useId.js +100 -0
  19. package/es/internal/useIdPrefix.d.ts +12 -0
  20. package/es/internal/useIdPrefix.js +19 -0
  21. package/es/internal/usePrefix.d.ts +5 -0
  22. package/es/internal/usePrefix.js +6 -0
  23. package/lib/components/SharkFinIcon.d.ts +24 -0
  24. package/lib/components/SharkFinIcon.js +61 -0
  25. package/lib/components/SideNav.js +8 -4
  26. package/lib/components/SideNavFlyoutMenu.d.ts +141 -0
  27. package/lib/components/SideNavFlyoutMenu.js +367 -0
  28. package/lib/components/SideNavMenu.d.ts +4 -0
  29. package/lib/components/SideNavMenu.js +96 -75
  30. package/lib/components/SideNavMenuItem.d.ts +4 -0
  31. package/lib/components/SideNavMenuItem.js +17 -4
  32. package/lib/components/SideNavToggle.d.ts +4 -0
  33. package/lib/components/SideNavToggle.js +9 -1
  34. package/lib/index.d.ts +1 -0
  35. package/lib/index.js +2 -0
  36. package/lib/internal/keyboard/keys.js +14 -0
  37. package/lib/internal/setupGetInstanceId.d.ts +11 -0
  38. package/lib/internal/setupGetInstanceId.js +23 -0
  39. package/lib/internal/useId.d.ts +21 -0
  40. package/lib/internal/useId.js +103 -0
  41. package/lib/internal/useIdPrefix.d.ts +12 -0
  42. package/lib/internal/useIdPrefix.js +22 -0
  43. package/lib/internal/usePrefix.d.ts +5 -0
  44. package/lib/internal/usePrefix.js +6 -0
  45. package/package.json +2 -2
  46. package/scss/styles/_shark-fin-icon.scss +14 -0
  47. package/scss/styles/_side-nav.scss +158 -0
  48. package/scss/ui-shell.scss +1 -0
@@ -17,9 +17,11 @@ var match = require('../internal/keyboard/match.js');
17
17
  var usePrefix = require('../internal/usePrefix.js');
18
18
  var SideNav = require('./SideNav.js');
19
19
  var useMergedRefs = require('../internal/useMergedRefs.js');
20
+ var SharkFinIcon = require('./SharkFinIcon.js');
21
+ var SideNavFlyoutMenu = require('./SideNavFlyoutMenu.js');
20
22
  var bucket3 = require('../node_modules/@carbon/icons-react/es/generated/bucket-3.js');
21
23
 
22
- var _ChevronDown;
24
+ var _SharkFinIcon, _ChevronDown;
23
25
  const SideNavMenu = /*#__PURE__*/React.forwardRef(function SideNavMenu(_ref, ref) {
24
26
  let {
25
27
  className: customClassName,
@@ -86,6 +88,42 @@ const SideNavMenu = /*#__PURE__*/React.forwardRef(function SideNavMenu(_ref, ref
86
88
  }
87
89
  return child;
88
90
  });
91
+
92
+ /**
93
+ Defining the children parameter with the type ReactNode | ReactNode[]. This allows for various possibilities:
94
+ a single element, an array of elements, or null or undefined.
95
+ **/
96
+ function hasActiveDescendant(children) {
97
+ if (Array.isArray(children)) {
98
+ return children.some(child => {
99
+ if (! /*#__PURE__*/React.isValidElement(child)) {
100
+ setActive(false);
101
+ return false;
102
+ }
103
+
104
+ /** Explicitly defining the expected prop types (isActive and 'aria-current) for the children to ensure type
105
+ safety when accessing their props.
106
+ **/
107
+ const props = child.props;
108
+ if (props.isActive === true || props['aria-current'] || props.children instanceof Array && hasActiveDescendant(props.children)) {
109
+ setActive(true);
110
+ return true;
111
+ }
112
+ return false;
113
+ });
114
+ }
115
+
116
+ // We use React.isValidElement(child) to check if the child is a valid React element before accessing its props
117
+
118
+ if (/*#__PURE__*/React.isValidElement(children)) {
119
+ const props = children.props;
120
+ if (props.isActive === true || props['aria-current']) {
121
+ setActive(true);
122
+ return true;
123
+ }
124
+ }
125
+ return false;
126
+ }
89
127
  React.useEffect(() => {
90
128
  if (navType == SideNav.SIDE_NAV_TYPE.PANEL) {
91
129
  // grab first link to redirect if clicked when not expanded
@@ -121,8 +159,9 @@ const SideNavMenu = /*#__PURE__*/React.forwardRef(function SideNavMenu(_ref, ref
121
159
  const parent = parentSideNavMenu(node);
122
160
  if (match.match(event, keys.ArrowLeft)) {
123
161
  event.stopPropagation();
124
- if (isMenu) {
125
- // collapse menu
162
+
163
+ // collapse menu
164
+ if (isMenu && sideNavExpanded) {
126
165
  if (isExpanded == 'true') {
127
166
  setIsExpanded(false);
128
167
 
@@ -145,8 +184,8 @@ const SideNavMenu = /*#__PURE__*/React.forwardRef(function SideNavMenu(_ref, ref
145
184
  if (match.match(event, keys.ArrowRight)) {
146
185
  event.stopPropagation();
147
186
 
148
- // expand menu
149
- if (isMenu) {
187
+ // expand menu when sidenav is expanded
188
+ if (isMenu && sideNavExpanded) {
150
189
  setIsExpanded(true);
151
190
 
152
191
  // if already expanded, focus on first element
@@ -165,7 +204,7 @@ const SideNavMenu = /*#__PURE__*/React.forwardRef(function SideNavMenu(_ref, ref
165
204
  // save expanded state before SideNav collapse
166
205
  const [lastExpandedState, setLastExpandedState] = React.useState(isExpanded);
167
206
 
168
- // reset when SideNav is panel
207
+ // reset to opened/collapsed menu state when Panel SideNav is toggled
169
208
  React.useEffect(() => {
170
209
  if (navType == SideNav.SIDE_NAV_TYPE.PANEL && !sideNavExpanded) {
171
210
  setLastExpandedState(isExpanded);
@@ -174,42 +213,53 @@ const SideNavMenu = /*#__PURE__*/React.forwardRef(function SideNavMenu(_ref, ref
174
213
  setIsExpanded(lastExpandedState);
175
214
  }
176
215
  }, [sideNavExpanded]);
177
- return (
178
- /*#__PURE__*/
179
- // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
180
- React.createElement("li", {
181
- role: isTreeview ? 'treeitem' : undefined,
182
- "aria-expanded": isExpanded,
183
- className: className,
184
- ref: listRef,
185
- onKeyDown: handleKeyDown
186
- }, /*#__PURE__*/React.createElement("button", {
187
- "aria-expanded": isExpanded,
188
- className: buttonClassName,
189
- onClick: () => {
190
- // only when sidenav is panel view
191
- if (navType == SideNav.SIDE_NAV_TYPE.PANEL && !isExpanded && firstLink.current && !sideNavExpanded) {
192
- window.location.href = firstLink.current;
193
- } else {
194
- setIsExpanded(!isExpanded);
195
- setLastExpandedState(!isExpanded);
196
- }
197
- },
198
- ref: menuRef,
199
- type: "button",
200
- tabIndex: isTreeview ? -1 : 0
201
- }, IconElement && /*#__PURE__*/React.createElement(react.SideNavIcon, null, /*#__PURE__*/React.createElement(IconElement, null)), /*#__PURE__*/React.createElement("span", {
202
- className: `${prefix}--side-nav__submenu-title`
203
- }, title), /*#__PURE__*/React.createElement(react.SideNavIcon, {
204
- className: `${prefix}--side-nav__submenu-chevron`,
205
- small: true
206
- }, _ChevronDown || (_ChevronDown = /*#__PURE__*/React.createElement(bucket3.ChevronDown, {
207
- size: 20
208
- })))), /*#__PURE__*/React.createElement("ul", {
209
- className: `${prefix}--side-nav__menu`,
210
- role: "group"
211
- }, childrenToRender))
212
- );
216
+ const [openPopover, setOpenPopover] = React.useState(false);
217
+ const content =
218
+ /*#__PURE__*/
219
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
220
+ React.createElement("li", {
221
+ role: isTreeview ? 'treeitem' : undefined,
222
+ "aria-expanded": isExpanded,
223
+ className: className,
224
+ ref: listRef,
225
+ onKeyDown: handleKeyDown
226
+ }, /*#__PURE__*/React.createElement("button", {
227
+ "aria-expanded": isExpanded,
228
+ className: buttonClassName,
229
+ onClick: () => {
230
+ // only when sidenav is panel view
231
+ if (navType == SideNav.SIDE_NAV_TYPE.PANEL && !isExpanded && firstLink.current && !sideNavExpanded) {
232
+ setOpenPopover(!openPopover);
233
+ // window.location.href = firstLink.current;
234
+ } else {
235
+ setIsExpanded(!isExpanded);
236
+ setLastExpandedState(!isExpanded);
237
+ }
238
+ },
239
+ ref: menuRef,
240
+ type: "button",
241
+ tabIndex: isTreeview ? -1 : 0
242
+ }, IconElement && /*#__PURE__*/React.createElement(react.SideNavIcon, null, /*#__PURE__*/React.createElement(IconElement, null)), !sideNavExpanded && /*#__PURE__*/React.createElement("div", {
243
+ className: `${prefix}--side-nav--panel-submenu-caret-container`
244
+ }, /*#__PURE__*/React.createElement("div", {
245
+ className: `${prefix}--side-nav--panel-submenu-caret`
246
+ }, _SharkFinIcon || (_SharkFinIcon = /*#__PURE__*/React.createElement(SharkFinIcon.SharkFinIcon, null)))), /*#__PURE__*/React.createElement("span", {
247
+ className: `${prefix}--side-nav__submenu-title`
248
+ }, title), /*#__PURE__*/React.createElement(react.SideNavIcon, {
249
+ className: `${prefix}--side-nav__submenu-chevron`,
250
+ small: true
251
+ }, _ChevronDown || (_ChevronDown = /*#__PURE__*/React.createElement(bucket3.ChevronDown, {
252
+ size: 20
253
+ })))), /*#__PURE__*/React.createElement("ul", {
254
+ className: `${prefix}--side-nav__menu`,
255
+ role: "group"
256
+ }, childrenToRender));
257
+ return navType == SideNav.SIDE_NAV_TYPE.PANEL && !sideNavExpanded ? /*#__PURE__*/React.createElement(SideNavFlyoutMenu.SideNavFlyoutMenu, {
258
+ selected: active,
259
+ className: `${prefix}--side-nav-flyout-menu`,
260
+ title: title,
261
+ menuContent: children
262
+ }, content) : content;
213
263
  });
214
264
  SideNavMenu.displayName = 'SideNavMenu';
215
265
  SideNavMenu.propTypes = {
@@ -251,6 +301,10 @@ SideNavMenu.propTypes = {
251
301
  */
252
302
  // @ts-expect-error - PropTypes are unable to cover this case.
253
303
  renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
304
+ /**
305
+ * The boolean to show the flyout menu has been selected.
306
+ */
307
+ selected: PropTypes.bool,
254
308
  /**
255
309
  * Optional prop to specify the tabIndex of the button. If undefined, it will be applied default validation
256
310
  */
@@ -261,37 +315,4 @@ SideNavMenu.propTypes = {
261
315
  title: PropTypes.string.isRequired
262
316
  };
263
317
 
264
- /**
265
- Defining the children parameter with the type ReactNode | ReactNode[]. This allows for various possibilities:
266
- a single element, an array of elements, or null or undefined.
267
- **/
268
- function hasActiveDescendant(children) {
269
- if (Array.isArray(children)) {
270
- return children.some(child => {
271
- if (! /*#__PURE__*/React.isValidElement(child)) {
272
- return false;
273
- }
274
-
275
- /** Explicitly defining the expected prop types (isActive and 'aria-current) for the children to ensure type
276
- safety when accessing their props.
277
- **/
278
- const props = child.props;
279
- if (props.isActive === true || props['aria-current'] || props.children instanceof Array && hasActiveDescendant(props.children)) {
280
- return true;
281
- }
282
- return false;
283
- });
284
- }
285
-
286
- // We use React.isValidElement(child) to check if the child is a valid React element before accessing its props
287
-
288
- if (/*#__PURE__*/React.isValidElement(children)) {
289
- const props = children.props;
290
- if (props.isActive === true || props['aria-current']) {
291
- return true;
292
- }
293
- }
294
- return false;
295
- }
296
-
297
318
  exports.SideNavMenu = SideNavMenu;
@@ -21,6 +21,10 @@ export interface SideNavMenuItemProps extends ComponentProps<typeof Link> {
21
21
  * `aria-current="page"`, as well.
22
22
  */
23
23
  isActive?: boolean;
24
+ /**
25
+ * Specify whether the link is part of a "SideNavMenu" in "flyout menu" mode.
26
+ */
27
+ isFlyoutMenuItem?: boolean;
24
28
  /**
25
29
  * Optionally provide an href for the underlying li`
26
30
  */
@@ -15,7 +15,9 @@ var react = require('@carbon/react');
15
15
  var Link = require('./Link.js');
16
16
  var usePrefix = require('../internal/usePrefix.js');
17
17
  var SideNav = require('./SideNav.js');
18
+ var bucket3 = require('../node_modules/@carbon/icons-react/es/generated/bucket-3.js');
18
19
 
20
+ var _SideNavIcon;
19
21
  const SideNavMenuItem = /*#__PURE__*/React.forwardRef(function SideNavMenuItem(props, ref) {
20
22
  const prefix = usePrefix.usePrefix();
21
23
  const {
@@ -23,16 +25,21 @@ const SideNavMenuItem = /*#__PURE__*/React.forwardRef(function SideNavMenuItem(p
23
25
  className: customClassName,
24
26
  as: Component = Link.default,
25
27
  isActive,
28
+ isFlyoutMenuItem,
26
29
  renderIcon: IconElement,
27
30
  ...rest
28
31
  } = props;
29
32
  const {
30
33
  isTreeview
31
34
  } = React.useContext(SideNav.SideNavContext);
32
- const className = index.default(`${prefix}--side-nav__menu-item`, customClassName);
35
+ const className = index.default({
36
+ [`${prefix}--side-nav__menu-item`]: true,
37
+ [`${prefix}--side-nav__menu-item--flyout-menu-item`]: isFlyoutMenuItem
38
+ }, customClassName);
33
39
  const linkClassName = index.default({
34
40
  [`${prefix}--side-nav__link`]: true,
35
- [`${prefix}--side-nav__link--current`]: isActive
41
+ [`${prefix}--side-nav__link--active`]: isActive && isFlyoutMenuItem,
42
+ [`${prefix}--side-nav__link--current`]: isActive && !isFlyoutMenuItem
36
43
  });
37
44
  return /*#__PURE__*/React.createElement("li", {
38
45
  className: className
@@ -42,9 +49,11 @@ const SideNavMenuItem = /*#__PURE__*/React.forwardRef(function SideNavMenuItem(p
42
49
  className: linkClassName,
43
50
  tabIndex: isTreeview ? -1 : 0,
44
51
  ref: ref
45
- }), IconElement && /*#__PURE__*/React.createElement(react.SideNavIcon, {
52
+ }), IconElement && !isFlyoutMenuItem && /*#__PURE__*/React.createElement(react.SideNavIcon, {
46
53
  small: true
47
- }, /*#__PURE__*/React.createElement(IconElement, null)), /*#__PURE__*/React.createElement(react.SideNavLinkText, null, children)));
54
+ }, /*#__PURE__*/React.createElement(IconElement, null)), isActive && isFlyoutMenuItem && (_SideNavIcon || (_SideNavIcon = /*#__PURE__*/React.createElement(react.SideNavIcon, {
55
+ small: true
56
+ }, /*#__PURE__*/React.createElement(bucket3.Checkmark, null)))), /*#__PURE__*/React.createElement(react.SideNavLinkText, null, children)));
48
57
  });
49
58
  SideNavMenuItem.displayName = 'SideNavMenuItem';
50
59
  SideNavMenuItem.propTypes = {
@@ -70,6 +79,10 @@ SideNavMenuItem.propTypes = {
70
79
  * `aria-current="page"`, as well.
71
80
  */
72
81
  isActive: PropTypes.bool,
82
+ /**
83
+ * Specify whether the link is part of a "SideNavMenu" in "flyout menu" mode.
84
+ */
85
+ isFlyoutMenuItem: PropTypes.bool,
73
86
  /**
74
87
  * Provide an icon to render in the side navigation link. Should be a React class.
75
88
  */
@@ -6,6 +6,10 @@
6
6
  */
7
7
  import React, { ReactNode } from 'react';
8
8
  interface SideNavToggleProps {
9
+ /**
10
+ * Specify an optional className to be applied to the button node
11
+ */
12
+ className?: string;
9
13
  /**
10
14
  * Specify the text content for the link
11
15
  */
@@ -10,6 +10,7 @@
10
10
  Object.defineProperty(exports, '__esModule', { value: true });
11
11
 
12
12
  var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.js');
13
+ var index = require('../_virtual/index.js');
13
14
  var PropTypes = require('prop-types');
14
15
  var React = require('react');
15
16
  var react = require('@carbon/react');
@@ -17,6 +18,7 @@ var usePrefix = require('../internal/usePrefix.js');
17
18
 
18
19
  const SideNavToggle = /*#__PURE__*/React.forwardRef(function SideNavToggle(_ref, ref) {
19
20
  let {
21
+ className: customClassName,
20
22
  renderIcon: IconElement,
21
23
  tabIndex,
22
24
  children,
@@ -24,7 +26,9 @@ const SideNavToggle = /*#__PURE__*/React.forwardRef(function SideNavToggle(_ref,
24
26
  } = _ref;
25
27
  const prefix = usePrefix.usePrefix();
26
28
  return /*#__PURE__*/React.createElement("button", _rollupPluginBabelHelpers.extends({
27
- className: `${prefix}--side-nav__toggle`,
29
+ className: index.default(customClassName, {
30
+ [`${prefix}--side-nav__toggle`]: true
31
+ }),
28
32
  ref: ref,
29
33
  type: "button",
30
34
  tabIndex: tabIndex ?? 0
@@ -38,6 +42,10 @@ SideNavToggle.propTypes = {
38
42
  * Specify the text content for the toggle
39
43
  */
40
44
  children: PropTypes.node,
45
+ /**
46
+ * Specify an optional className to be applied to the button node
47
+ */
48
+ className: PropTypes.string,
41
49
  /**
42
50
  * Provide an optional function to be called when clicked
43
51
  */
package/lib/index.d.ts CHANGED
@@ -14,3 +14,4 @@ export { SideNavMenu } from './components/SideNavMenu.js';
14
14
  export { SideNavMenuItem } from './components/SideNavMenuItem.js';
15
15
  export { HeaderContainer } from './components/HeaderContainer';
16
16
  export { HeaderPanel } from './components/HeaderPanel';
17
+ export { SharkFinIcon } from './components/SharkFinIcon';
package/lib/index.js CHANGED
@@ -15,6 +15,7 @@ var SideNavMenu = require('./components/SideNavMenu.js');
15
15
  var SideNavMenuItem = require('./components/SideNavMenuItem.js');
16
16
  var HeaderContainer = require('./components/HeaderContainer.js');
17
17
  var HeaderPanel = require('./components/HeaderPanel.js');
18
+ var SharkFinIcon = require('./components/SharkFinIcon.js');
18
19
 
19
20
 
20
21
 
@@ -27,3 +28,4 @@ exports.SideNavMenu = SideNavMenu.SideNavMenu;
27
28
  exports.SideNavMenuItem = SideNavMenuItem.SideNavMenuItem;
28
29
  exports.HeaderContainer = HeaderContainer.HeaderContainer;
29
30
  exports.HeaderPanel = HeaderPanel.HeaderPanel;
31
+ exports.SharkFinIcon = SharkFinIcon.SharkFinIcon;
@@ -21,6 +21,12 @@ const Tab = {
21
21
  keyCode: 9,
22
22
  code: 'Tab'
23
23
  };
24
+ const Enter = {
25
+ key: 'Enter',
26
+ which: 13,
27
+ keyCode: 13,
28
+ code: 'Enter'
29
+ };
24
30
  const Escape = {
25
31
  key: ['Escape',
26
32
  // IE11 Escape
@@ -29,6 +35,12 @@ const Escape = {
29
35
  keyCode: 27,
30
36
  code: 'Esc'
31
37
  };
38
+ const Space = {
39
+ key: ' ',
40
+ which: 32,
41
+ keyCode: 32,
42
+ code: 'Space'
43
+ };
32
44
  const End = {
33
45
  key: 'End',
34
46
  which: 35,
@@ -71,6 +83,8 @@ exports.ArrowLeft = ArrowLeft;
71
83
  exports.ArrowRight = ArrowRight;
72
84
  exports.ArrowUp = ArrowUp;
73
85
  exports.End = End;
86
+ exports.Enter = Enter;
74
87
  exports.Escape = Escape;
75
88
  exports.Home = Home;
89
+ exports.Space = Space;
76
90
  exports.Tab = Tab;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2023
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ /**
8
+ * Generic utility to initialize a method that will return a unique instance id
9
+ * for a component.
10
+ */
11
+ export default function setupGetInstanceId(): () => number;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Copyright IBM Corp. 2024
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ Object.defineProperty(exports, '__esModule', { value: true });
11
+
12
+ /**
13
+ * Generic utility to initialize a method that will return a unique instance id
14
+ * for a component.
15
+ */
16
+ function setupGetInstanceId() {
17
+ let instanceId = 0;
18
+ return function getInstanceId() {
19
+ return ++instanceId;
20
+ };
21
+ }
22
+
23
+ exports.default = setupGetInstanceId;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Generate a unique ID for React <=17 with an optional prefix prepended to it.
3
+ * This is an internal utility, not intended for public usage.
4
+ * @param {string} [prefix] the optional prefix id
5
+ * @returns {string}
6
+ */
7
+ export function useCompatibleId(prefix?: string | undefined): string;
8
+ /**
9
+ * Generate a unique id if a given `id` is not provided
10
+ * This is an internal utility, not intended for public usage.
11
+ * @param {string|undefined} id the provided id
12
+ * @returns {string}
13
+ */
14
+ export function useFallbackId(id: string | undefined): string;
15
+ /**
16
+ * Generate a unique ID for React >=18 with an optional prefix prepended to it.
17
+ * This is an internal utility, not intended for public usage.
18
+ * @param {string} [prefix] the optional prefix id
19
+ * @returns {string}
20
+ */
21
+ export function useId(prefix?: string | undefined): string;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Copyright IBM Corp. 2024
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ var React = require('react');
11
+ var setupGetInstanceId = require('./setupGetInstanceId.js');
12
+ var useIdPrefix = require('./useIdPrefix.js');
13
+ var environment = require('./environment.js');
14
+
15
+ // This file was heavily inspired by:
16
+ //
17
+ // 1. Reach UI and their work on their auto-id package:
18
+ // https://github.com/reach/reach-ui/blob/86a046f54d53b6420e392b3fa56dd991d9d4e458/packages/auto-id/src/index.ts
19
+ //
20
+ // 2. Floating UI and their work on react >=18 compatibility
21
+ // https://github.com/floating-ui/floating-ui/blob/%40floating-ui/utils%400.2.5/packages/react/src/hooks/useId.ts
22
+ //
23
+ // The problem that this solves is an id mismatch when auto-generating
24
+ // ids on both the server and the client. When using server-side rendering,
25
+ // there can be the chance of a mismatch between what the server renders and
26
+ // what the client renders when the id value is auto-generated.
27
+ //
28
+ // To get around this, we set the initial value of the `id` to `null` and then
29
+ // conditionally use `useLayoutEffect` on the client and `useEffect` on the
30
+ // server. On the client, `useLayoutEffect` will patch up the id to the correct
31
+ // value. On the server, `useEffect` will not run.
32
+ //
33
+ // This ensures that we won't encounter a mismatch in ids between server and
34
+ // client, at the cost of runtime patching of the id value in
35
+ // `useLayoutEffect`
36
+ //
37
+ // React 18 introduced a new hook called `useId` that takes care of hydration
38
+ // mismatches. If the user is running React 18 or higher, the native hook is
39
+ // used via the `useReactId` function. If the user is running React 17 or
40
+ // lower, `useCompatibleId` is used.
41
+
42
+
43
+ // This tricks bundlers so they can't statically analyze this and produce
44
+ // compilation warnings/errors.
45
+ // https://github.com/webpack/webpack/issues/14814
46
+ // https://github.com/mui/material-ui/issues/41190
47
+ const _React = {
48
+ ...React
49
+ };
50
+ const instanceId = setupGetInstanceId.default();
51
+ const useIsomorphicLayoutEffect = environment.canUseDOM ? React.useLayoutEffect : React.useEffect;
52
+ let serverHandoffCompleted = false;
53
+ const defaultId = 'id';
54
+
55
+ /**
56
+ * Generate a unique ID for React <=17 with an optional prefix prepended to it.
57
+ * This is an internal utility, not intended for public usage.
58
+ * @param {string} [prefix] the optional prefix id
59
+ * @returns {string}
60
+ */
61
+ function useCompatibleId() {
62
+ let prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultId;
63
+ const contextPrefix = useIdPrefix.useIdPrefix();
64
+ const [id, setId] = React.useState(() => {
65
+ if (serverHandoffCompleted) {
66
+ return `${contextPrefix ? `${contextPrefix}-` : ``}${prefix}-${instanceId()}`;
67
+ }
68
+ return null;
69
+ });
70
+ useIsomorphicLayoutEffect(() => {
71
+ if (id === null) {
72
+ setId(`${contextPrefix ? `${contextPrefix}-` : ``}${prefix}-${instanceId()}`);
73
+ }
74
+ }, [instanceId]);
75
+ React.useEffect(() => {
76
+ if (serverHandoffCompleted === false) {
77
+ serverHandoffCompleted = true;
78
+ }
79
+ }, []);
80
+ return id;
81
+ }
82
+
83
+ /**
84
+ * Generate a unique ID for React >=18 with an optional prefix prepended to it.
85
+ * This is an internal utility, not intended for public usage.
86
+ * @param {string} [prefix] the optional prefix id
87
+ * @returns {string}
88
+ */
89
+ function useReactId() {
90
+ let prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultId;
91
+ const contextPrefix = useIdPrefix.useIdPrefix();
92
+ return `${contextPrefix ? `${contextPrefix}-` : ``}${prefix}-${_React.useId()}`;
93
+ }
94
+
95
+ /**
96
+ * Uses React 18's built-in `useId()` when available, or falls back to a
97
+ * slightly less performant (requiring a double render) implementation for
98
+ * earlier React versions.
99
+ */
100
+ const useId = _React.useId ? useReactId : useCompatibleId;
101
+
102
+ exports.useCompatibleId = useCompatibleId;
103
+ exports.useId = useId;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2023
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import React from 'react';
8
+ export declare const IdPrefixContext: React.Context<string | null | undefined>;
9
+ /**
10
+ *
11
+ */
12
+ export declare function useIdPrefix(): string | null | undefined;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Copyright IBM Corp. 2024
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ var React = require('react');
11
+
12
+ const IdPrefixContext = /*#__PURE__*/React.createContext(null);
13
+
14
+ /**
15
+ *
16
+ */
17
+ function useIdPrefix() {
18
+ return React.useContext(IdPrefixContext);
19
+ }
20
+
21
+ exports.IdPrefixContext = IdPrefixContext;
22
+ exports.useIdPrefix = useIdPrefix;
@@ -6,4 +6,9 @@
6
6
  */
7
7
  import React from 'react';
8
8
  export declare const PrefixContext: React.Context<string>;
9
+ /**
10
+ * An internal function to return the prefix used in components and styles.
11
+ *
12
+ * @returns a react context including the prefix
13
+ */
9
14
  export declare function usePrefix(): string;
@@ -18,6 +18,12 @@ var React = require('react');
18
18
  */
19
19
 
20
20
  const PrefixContext = /*#__PURE__*/React.createContext('cds');
21
+
22
+ /**
23
+ * An internal function to return the prefix used in components and styles.
24
+ *
25
+ * @returns a react context including the prefix
26
+ */
21
27
  function usePrefix() {
22
28
  return React.useContext(PrefixContext);
23
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carbon-labs/react-ui-shell",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": true
@@ -33,5 +33,5 @@
33
33
  "dependencies": {
34
34
  "@ibm/telemetry-js": "^1.9.1"
35
35
  },
36
- "gitHead": "787747477de6fb9f6b8d775082cf9872b3317565"
36
+ "gitHead": "94522ab158f8e47f9d0312eccccd32ea35dd1ae6"
37
37
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Copyright IBM Corp. 2025
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ @use '@carbon/styles/scss/theme' as *;
9
+
10
+ $prefix: 'cds' !default;
11
+
12
+ .#{$prefix}--side-nav__icon.#{$prefix}--shark-fin__icon {
13
+ fill: $icon-secondary;
14
+ }