Skip to content

Commit 6251c2f

Browse files
authored
#2805 Server side rendering (SSR) support (Remove UUID from overflow … (#2809)
Signed-off-by: CTomlyn <[email protected]>
1 parent 48addde commit 6251c2f

File tree

2 files changed

+119
-122
lines changed

2 files changed

+119
-122
lines changed

canvas_modules/common-canvas/src/toolbar/toolbar-overflow-item.jsx

Lines changed: 110 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2024 Elyra Authors
2+
* Copyright 2017-2025 Elyra Authors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,169 +14,166 @@
1414
* limitations under the License.
1515
*/
1616

17-
import React from "react";
17+
import React, { useState, useRef, useEffect, useImperativeHandle, forwardRef } from "react";
1818
import PropTypes from "prop-types";
1919

20-
import { v4 as uuid4 } from "uuid";
2120
import { Button } from "@carbon/react";
2221
import { OverflowMenuVertical } from "@carbon/react/icons";
2322
import KeyboardUtils from "../common-canvas/keyboard-utils.js";
2423
import ToolbarSubMenu from "./toolbar-sub-menu.jsx";
2524

26-
class ToolbarOverflowItem extends React.Component {
27-
constructor(props) {
28-
super(props);
25+
const ToolbarOverflowItem = forwardRef(({
26+
index,
27+
action,
28+
label,
29+
size,
30+
subMenuActions,
31+
setOverflowIndex,
32+
toolbarActionHandler,
33+
instanceId,
34+
containingDivId,
35+
toolbarFocusAction,
36+
setToolbarFocusAction,
37+
isFocusInToolbar,
38+
closeAnyOpenSubArea
39+
}, ref) => {
40+
41+
const [showExtendedMenu, setShowExtendedMenu] = useState(false);
42+
43+
const buttonRef = useRef(null);
44+
45+
// Manage button focus
46+
useEffect(() => {
47+
if (toolbarFocusAction === action && isFocusInToolbar && !showExtendedMenu) {
48+
buttonRef.current.focus();
49+
}
50+
}, [toolbarFocusAction, isFocusInToolbar, showExtendedMenu]);
51+
52+
53+
// Manage event listener for clicking outside the overflow menu.
54+
useEffect(() => {
55+
document.addEventListener("click", clickOutside, false);
2956

30-
this.state = {
31-
showExtendedMenu: false
57+
return () => {
58+
document.removeEventListener("click", clickOutside, false);
3259
};
3360

34-
this.buttonRef = React.createRef();
61+
}, [toolbarFocusAction, isFocusInToolbar, showExtendedMenu]);
3562

36-
this.uuid = uuid4();
37-
this.toggleExtendedMenu = this.toggleExtendedMenu.bind(this);
38-
this.clickOutside = this.clickOutside.bind(this);
39-
this.closeSubArea = this.closeSubArea.bind(this);
40-
this.onKeyDown = this.onKeyDown.bind(this);
41-
}
4263

43-
componentDidUpdate() {
44-
if (this.props.toolbarFocusAction === this.props.action && this.props.isFocusInToolbar && !this.state.showExtendedMenu) {
45-
this.buttonRef.current.focus();
46-
}
47-
}
64+
// Expose methods to toolbar.jsx
65+
useImperativeHandle(ref, () => ({
66+
getAction: () => action,
4867

49-
// We must remove the eventListener in case this class is unmounted due
50-
// to the toolbar getting redrawn.
51-
componentWillUnmount() {
52-
document.removeEventListener("click", this.clickOutside, false);
53-
}
68+
isSubAreaDisplayed: () => showExtendedMenu,
69+
70+
closeSubArea: () => closeSubArea()
71+
}));
5472

55-
onKeyDown(evt) {
73+
function onKeyDown(evt) {
5674
if (KeyboardUtils.closeSubArea(evt)) {
57-
this.closeSubArea();
75+
closeSubArea();
5876

5977
} else if (KeyboardUtils.openSubArea(evt)) {
60-
this.openSubArea();
78+
openSubArea();
6179
}
6280
// Left and Right arrow clicks are caught in the
6381
// toolbar.jsx onKeyDown method.
6482
}
6583

66-
67-
// Called by toolbar.jsx
68-
getAction() {
69-
return this.props.action;
70-
}
71-
72-
// Called by toolbar.jsx
73-
isSubAreaDisplayed() {
74-
return this.state.showExtendedMenu;
75-
}
76-
77-
// Called by toolbar.jsx and internally
78-
closeSubArea() {
79-
document.removeEventListener("click", this.clickOutside, false);
80-
this.props.setOverflowIndex(null); // Clear the indexes
81-
this.setState({ showExtendedMenu: false });
82-
this.props.setToolbarFocusAction(this.props.action); // This will not set focus on this item
83-
}
84-
85-
openSubArea() {
86-
document.addEventListener("click", this.clickOutside, false);
87-
this.props.closeAnyOpenSubArea();
88-
this.props.setOverflowIndex(this.props.index);
89-
this.setState({ showExtendedMenu: true });
90-
this.props.setToolbarFocusAction(this.props.action);
84+
function closeSubArea() {
85+
setOverflowIndex(null); // Clear the indexes
86+
setShowExtendedMenu(false);
87+
setToolbarFocusAction(action); // This will not set focus on this item
9188
}
9289

93-
genOverflowButtonClassName() {
94-
return "toolbar-overflow-container " + this.genIndexClassName() + " " + this.genUuidClassName();
90+
function openSubArea() {
91+
closeAnyOpenSubArea();
92+
setOverflowIndex(index);
93+
setShowExtendedMenu(true);
94+
setToolbarFocusAction(action);
9595
}
9696

97-
genIndexClassName() {
98-
return "toolbar-index-" + this.props.index;
97+
function genOverflowButtonClassName() {
98+
return "toolbar-overflow-container " + genIndexClassName();
9999
}
100100

101-
genUuidClassName() {
102-
return "toolbar-uuid-" + this.uuid;
101+
function genIndexClassName() {
102+
return "toolbar-index-" + index;
103103
}
104104

105105
// When the overflow item is clicked to open the overflow menu we must set the
106106
// index of the overflow items so the overflow menu can be correctly constructed.
107107
// The overflow index values are used to split out the overflow menu action items
108108
// from the left bar and right bar.
109109
// When the overflow menu is closed we set the overflow index values to null.
110-
toggleExtendedMenu() {
111-
if (this.state.showExtendedMenu) {
112-
this.closeSubArea();
110+
function toggleExtendedMenu() {
111+
if (showExtendedMenu) {
112+
closeSubArea();
113113

114114
} else {
115-
this.openSubArea();
115+
openSubArea();
116116
}
117117
}
118118

119-
clickOutside(evt) {
120-
if (this.state.showExtendedMenu) {
119+
function clickOutside(evt) {
120+
if (showExtendedMenu) {
121121
// Selector for the overflow-container that contains the overflow icon
122122
// and submenu (if submenu is open).
123-
const selector = "." + this.genIndexClassName();
123+
const selector = "." + genIndexClassName();
124124
const isClickInOverflowContainer = evt.target.closest(selector);
125125
if (!isClickInOverflowContainer) {
126-
this.setState({ showExtendedMenu: false });
126+
setShowExtendedMenu(false);
127127
}
128128
}
129129
}
130130

131-
render() {
132-
let overflowMenu = null;
133-
if (this.state.showExtendedMenu) {
134-
const actionItemRect = this.buttonRef.current.getBoundingClientRect();
135-
overflowMenu = (
136-
<ToolbarSubMenu
137-
ref={this.subMenuRef}
138-
subMenuActions={this.props.subMenuActions}
139-
instanceId={this.props.instanceId}
140-
toolbarActionHandler={this.props.toolbarActionHandler}
141-
closeSubArea={this.closeSubArea}
142-
setToolbarFocusAction={this.props.setToolbarFocusAction}
143-
actionItemRect={actionItemRect}
144-
expandDirection={"vertical"}
145-
containingDivId={this.props.containingDivId}
146-
parentSelector={".toolbar-overflow-container"}
147-
isOverflowMenu
148-
isCascadeMenu={false}
149-
size={this.props.size}
150-
/>
151-
);
152-
}
131+
let overflowMenu = null;
132+
if (showExtendedMenu) {
133+
const actionItemRect = buttonRef.current.getBoundingClientRect();
134+
overflowMenu = (
135+
<ToolbarSubMenu
136+
subMenuActions={subMenuActions}
137+
instanceId={instanceId}
138+
toolbarActionHandler={toolbarActionHandler}
139+
closeSubArea={closeSubArea}
140+
setToolbarFocusAction={setToolbarFocusAction}
141+
actionItemRect={actionItemRect}
142+
expandDirection={"vertical"}
143+
containingDivId={containingDivId}
144+
parentSelector={".toolbar-overflow-container"}
145+
isOverflowMenu
146+
isCascadeMenu={false}
147+
size={size}
148+
/>
149+
);
150+
}
153151

154-
const tabIndex = this.props.toolbarFocusAction === this.props.action ? 0 : -1;
155-
156-
return (
157-
<div className={this.genOverflowButtonClassName()} data-toolbar-action={this.props.action}>
158-
<div className={"toolbar-overflow-item"}>
159-
<Button
160-
ref={this.buttonRef}
161-
kind="ghost"
162-
tabIndex={tabIndex}
163-
onClick={this.toggleExtendedMenu}
164-
onKeyDown={this.onKeyDown}
165-
aria-label={this.props.label}
166-
size={this.props.size}
167-
>
168-
<div className="toolbar-item-content default">
169-
<div className="toolbar-icon overflow-item">
170-
<OverflowMenuVertical />
171-
</div>
152+
const tabIndex = toolbarFocusAction === action ? 0 : -1;
153+
154+
return (
155+
<div className={genOverflowButtonClassName()} data-toolbar-action={action}>
156+
<div className={"toolbar-overflow-item"}>
157+
<Button
158+
ref={buttonRef}
159+
kind="ghost"
160+
tabIndex={tabIndex}
161+
onClick={toggleExtendedMenu}
162+
onKeyDown={onKeyDown}
163+
aria-label={label}
164+
size={size}
165+
>
166+
<div className="toolbar-item-content default">
167+
<div className="toolbar-icon overflow-item">
168+
<OverflowMenuVertical />
172169
</div>
173-
</Button>
174-
</div>
175-
{overflowMenu}
170+
</div>
171+
</Button>
176172
</div>
177-
);
178-
}
179-
}
173+
{overflowMenu}
174+
</div>
175+
);
176+
});
180177

181178
ToolbarOverflowItem.propTypes = {
182179
index: PropTypes.number.isRequired,

canvas_modules/common-canvas/src/toolbar/toolbar.jsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class Toolbar extends React.Component {
9696
const index = focusableItems.findIndex((item) => this.getRefAction(item) === this.state.focusAction);
9797
if (focusableItems.length === 0 && this.state.focusAction !== "disabled") {
9898
this.setTabIndexOnDisabledToolbar();
99-
} else if (index === -1 || (!this.isFocusInToolbar && this.props.setInititalFocus)) {
99+
} else if (index === -1 || (!this.isFocusInToolbar && this.props.setInitialFocus)) {
100100
this.isFocusInToolbar = true;
101101
this.setFocusOnFirstItem();
102102
}
@@ -172,20 +172,20 @@ class Toolbar extends React.Component {
172172
onToolbarResize() {
173173
const focusableItemRefs = this.getFocusableItemRefs();
174174
// Note: isFocusActionFocusable needs to be calculated here before any
175-
// update to the toolbar caused by the code in the subsequent if ststement.
175+
// update to the toolbar caused by the code in the subsequent if statement.
176176
const isFocusActionFocusable = this.isFocusActionFocusable(this.state.focusAction, focusableItemRefs);
177177
const refWithOpenSubArea = this.getRefWithOpenSubArea();
178178

179179
if (refWithOpenSubArea) {
180-
const action = refWithOpenSubArea.current?.getAction();
180+
const action = this.getRefAction(refWithOpenSubArea);
181181
const isFocusActionWithOpenSubAreaFocusable = this.isFocusActionFocusable(action, focusableItemRefs);
182182

183183
if (!isFocusActionWithOpenSubAreaFocusable) {
184184
refWithOpenSubArea.current?.closeSubArea();
185185

186186
} else {
187187
// This forces a refresh that will cause the position of any
188-
// open sub-area to be recaulculated based on the new toolbar width.
188+
// open sub-area to be recalculated based on the new toolbar width.
189189
this.setFocusAction(this.state.focusAction);
190190
}
191191
}
@@ -311,7 +311,7 @@ class Toolbar extends React.Component {
311311
} else if (!overflowItemRef) {
312312
const leftRefAction = this.getRefAction(this.leftItemRefs[i]);
313313
const overflowAction = this.getOverflowAction(leftRefAction);
314-
overflowItemRef = this.overflowItemRefs.find((oRef) => oRef.current?.getAction() === overflowAction);
314+
overflowItemRef = this.overflowItemRefs.find((oRef) => this.getRefAction(oRef) === overflowAction);
315315
if (overflowItemRef) {
316316
focusableItemRefs.push(overflowItemRef);
317317
}
@@ -386,7 +386,7 @@ class Toolbar extends React.Component {
386386
// items. (It may not be if it has been placed in the overflow menu).
387387
isFocusActionFocusable(focusAction, focusableItemRefs) {
388388
const indexFocusAction = focusableItemRefs.findIndex((ref) =>
389-
ref.current?.props.actionObj?.action === focusAction);
389+
this.getRefAction(ref) === focusAction);
390390
return indexFocusAction > -1;
391391
}
392392

@@ -535,13 +535,13 @@ class Toolbar extends React.Component {
535535
if (ref.current?.props.actionObj.setExtIsSubAreaDisplayed) {
536536
ref.current?.props.actionObj.setExtIsSubAreaDisplayed(false);
537537

538-
} else if (ref.current?.state.subAreaDisplayed) {
538+
} else if (ref.current?.isSubAreaDisplayed()) {
539539
ref.current?.closeSubArea();
540540
}
541541
}
542542

543543
closeOverflowMenuOnRef(ref) {
544-
if (ref.current?.state.showExtendedMenu) {
544+
if (ref.current?.showExtendedMenu) {
545545
ref.current?.closeSubArea();
546546
}
547547
}
@@ -585,7 +585,7 @@ Toolbar.propTypes = {
585585
toolbarActionHandler: PropTypes.func,
586586
tooltipDirection: PropTypes.string,
587587
additionalText: PropTypes.object,
588-
setInititalFocus: PropTypes.bool,
588+
setInitialFocus: PropTypes.bool,
589589
closeToolbarOnEsc: PropTypes.bool,
590590
closeToolbar: PropTypes.func,
591591
size: PropTypes.oneOf(["md", "sm"])

0 commit comments

Comments
 (0)