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