Skip to content

Commit 1891f0e

Browse files
authored
fix(prefix): make Sass and React components fully prefix-aware (#21058)
1 parent 7da22c2 commit 1891f0e

File tree

10 files changed

+49
-17
lines changed

10 files changed

+49
-17
lines changed

packages/react/src/components/ComposedModal/ComposedModal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ const ComposedModalDialog = React.forwardRef<
384384
if (
385385
shouldCloseOnOutsideClick &&
386386
target instanceof Node &&
387-
!elementOrParentIsFloatingMenu(target, selectorsFloatingMenus) &&
387+
!elementOrParentIsFloatingMenu(target, selectorsFloatingMenus, prefix) &&
388388
innerModal.current &&
389389
!innerModal.current.contains(target) &&
390390
!innerModal.current.contains(mouseDownTarget)
@@ -415,6 +415,7 @@ const ComposedModalDialog = React.forwardRef<
415415
currentActiveNode,
416416
oldActiveNode,
417417
selectorsFloatingMenus: selectorsFloatingMenus?.filter(Boolean),
418+
prefix,
418419
});
419420
}
420421

packages/react/src/components/DataTableSkeleton/DataTableSkeleton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
@@ -124,7 +124,7 @@ const DataTableSkeleton: FunctionComponent<DataTableSkeletonProps> = ({
124124
{columnsArray.map((i) => (
125125
<th key={i}>
126126
{headers ? (
127-
<div className="cds--table-header-label">
127+
<div className={`${prefix}--table-header-label`}>
128128
{headers[i]?.header}
129129
</div>
130130
) : (

packages/react/src/components/Modal/Modal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ const ModalDialog = React.forwardRef(function ModalDialog(
414414
if (
415415
shouldCloseOnOutsideClick &&
416416
target instanceof Node &&
417-
!elementOrParentIsFloatingMenu(target, selectorsFloatingMenus) &&
417+
!elementOrParentIsFloatingMenu(target, selectorsFloatingMenus, prefix) &&
418418
innerModal.current &&
419419
!innerModal.current.contains(target)
420420
) {
@@ -445,6 +445,7 @@ const ModalDialog = React.forwardRef(function ModalDialog(
445445
currentActiveNode,
446446
oldActiveNode,
447447
selectorsFloatingMenus,
448+
prefix,
448449
});
449450
if (wrapFocusTimeout.current) {
450451
clearTimeout(wrapFocusTimeout.current);

packages/react/src/components/Notification/Notification.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -974,7 +974,7 @@ export function ActionableNotification({
974974
useIsomorphicEffect(() => {
975975
if (hasFocus && role === 'alertdialog') {
976976
const button = document.querySelector(
977-
'button.cds--actionable-notification__action-button'
977+
`button.${prefix}--actionable-notification__action-button`
978978
) as HTMLButtonElement;
979979
button?.focus();
980980
}
@@ -999,6 +999,7 @@ export function ActionableNotification({
999999
endTrapNode,
10001000
currentActiveNode,
10011001
oldActiveNode,
1002+
prefix,
10021003
});
10031004
}
10041005
}

packages/react/src/components/Popover/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,9 +256,11 @@ export const Popover: PopoverComponent & {
256256
// If a value is not set via a custom property, provide a default value that matches the
257257
// default values defined in the sass style file
258258
const getStyle = window.getComputedStyle(popover.current, null);
259-
const offsetProperty = getStyle.getPropertyValue('--cds-popover-offset');
259+
const offsetProperty = getStyle.getPropertyValue(
260+
`--${prefix}-popover-offset`
261+
);
260262
const caretProperty = getStyle.getPropertyValue(
261-
'--cds-popover-caret-height'
263+
`--${prefix}-popover-caret-height`
262264
);
263265

264266
// Handle if the property values are in px or rem.

packages/react/src/internal/FloatingMenu.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ export const FloatingMenu = ({
476476
endTrapNode: endSentinelRef.current,
477477
currentActiveNode: relatedTarget,
478478
oldActiveNode: target,
479+
prefix,
479480
});
480481
}
481482
};

packages/react/src/internal/__tests__/wrapFocus-test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ describe('wrapFocus', () => {
1212
let spyInnerModal;
1313
let spyButton0;
1414
let spyButton2;
15+
let tooltip;
1516

1617
beforeEach(() => {
1718
node = document.createElement('div');
@@ -39,6 +40,7 @@ describe('wrapFocus', () => {
3940
<div class="cds--tooltip" tabindex="0"></div>
4041
`;
4142
document.body.appendChild(node);
43+
tooltip = node.querySelector('.cds--tooltip');
4244
spyInnerModal = jest.spyOn(node.querySelector('#inner-modal'), 'focus');
4345
spyButton0 = jest.spyOn(node.querySelector('#button-0'), 'focus');
4446
spyButton2 = jest.spyOn(node.querySelector('#button-2'), 'focus');
@@ -61,6 +63,7 @@ describe('wrapFocus', () => {
6163
node.parentNode.removeChild(node);
6264
node = null;
6365
}
66+
tooltip = null;
6467
});
6568

6669
it('runs forward focus-wrap when following outer node is focused on', () => {
@@ -120,6 +123,21 @@ describe('wrapFocus', () => {
120123
expect(spyButton2).not.toHaveBeenCalled();
121124
});
122125

126+
it('respects prefix overrides when checking floating menus', () => {
127+
tooltip.className = 'bx--tooltip';
128+
wrapFocus({
129+
bodyNode: node.querySelector('#inner-modal'),
130+
startSentinelNode: node.querySelector('#start-sentinel'),
131+
endSentinelNode: node.querySelector('#end-sentinel'),
132+
currentActiveNode: tooltip,
133+
oldActiveNode: node.querySelector('#button-2'),
134+
prefix: 'bx',
135+
});
136+
expect(spyInnerModal).not.toHaveBeenCalled();
137+
expect(spyButton0).not.toHaveBeenCalled();
138+
expect(spyButton2).not.toHaveBeenCalled();
139+
});
140+
123141
it('uses inner modal node as a escape hatch for focusing for forward focus-wrap', () => {
124142
node.querySelector('#inner-modal').innerHTML =
125143
`<div id="dummy-old-active-node"></div>`;

packages/react/src/internal/wrapFocus.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@ const DOCUMENT_POSITION_BROAD_FOLLOWING =
3838
*/
3939
export const elementOrParentIsFloatingMenu = (
4040
node: Node,
41-
selectorsFloatingMenus: string[] = []
41+
selectorsFloatingMenus: string[] = [],
42+
prefix = 'cds'
4243
): boolean => {
4344
if (node instanceof Element && typeof node.closest === 'function') {
4445
const allSelectorsFloatingMenus = [
45-
'.cds--overflow-menu-options',
46-
'.cds--tooltip',
46+
`.${prefix}--overflow-menu-options`,
47+
`.${prefix}--tooltip`,
4748
'.flatpickr-calendar',
4849
...selectorsFloatingMenus,
4950
];
@@ -67,6 +68,7 @@ export const wrapFocus = ({
6768
currentActiveNode,
6869
oldActiveNode,
6970
selectorsFloatingMenus,
71+
prefix = 'cds',
7072
}: {
7173
/** The DOM node of the container. */
7274
bodyNode: HTMLElement | null;
@@ -80,13 +82,19 @@ export const wrapFocus = ({
8082
oldActiveNode: HTMLElement;
8183
/** CSS selectors for floating menus. */
8284
selectorsFloatingMenus?: string[];
85+
/** Classname prefix for Carbon selectors. */
86+
prefix?: string;
8387
}) => {
8488
if (
8589
bodyNode &&
8690
currentActiveNode &&
8791
oldActiveNode &&
8892
!bodyNode.contains(currentActiveNode) &&
89-
!elementOrParentIsFloatingMenu(currentActiveNode, selectorsFloatingMenus)
93+
!elementOrParentIsFloatingMenu(
94+
currentActiveNode,
95+
selectorsFloatingMenus,
96+
prefix
97+
)
9098
) {
9199
const comparisonResult =
92100
oldActiveNode.compareDocumentPosition(currentActiveNode);

packages/styles/scss/components/multiselect/_multiselect.scss

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright IBM Corp. 2016, 2023
2+
// Copyright IBM Corp. 2016, 2025
33
//
44
// This source code is licensed under the Apache-2.0 license found in the
55
// LICENSE file in the root directory of this source tree.
@@ -90,13 +90,13 @@
9090

9191
.#{$prefix}--multi-select
9292
.#{$prefix}--list-box__menu-item__option
93-
.cds--checkbox:indeterminate
94-
+ .cds--checkbox-label::after {
93+
.#{$prefix}--checkbox:indeterminate
94+
+ .#{$prefix}--checkbox-label::after {
9595
inset-block-start: convert.to-rem(9px);
9696
}
9797
.#{$prefix}--multi-select
9898
.#{$prefix}--list-box__menu-item__option
99-
.cds--checkbox-label::after {
99+
.#{$prefix}--checkbox-label::after {
100100
inset-block-start: convert.to-rem(5px);
101101
}
102102
.#{$prefix}--multi-select

packages/styles/scss/components/treeview/_treeview.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright IBM Corp. 2016, 2023
2+
// Copyright IBM Corp. 2016, 2025
33
//
44
// This source code is licensed under the Apache-2.0 license found in the
55
// LICENSE file in the root directory of this source tree.
@@ -95,7 +95,7 @@
9595
li a.#{$prefix}--tree-node {
9696
text-decoration: none;
9797

98-
&:not(.cds--tree-node--disabled) {
98+
&:not(.#{$prefix}--tree-node--disabled) {
9999
color: $text-secondary;
100100
}
101101
}

0 commit comments

Comments
 (0)