Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
a2f0390
chore: initial commit - making elements focusable and react to enter
kbangelov Dec 15, 2025
06d2d62
feat: added some accessability with arrow logic
kbangelov Dec 19, 2025
34b18df
chore: addressed copilot stuff
kbangelov Dec 19, 2025
1c05d24
feat: refactored dropdown menu logic
kbangelov Dec 22, 2025
9914e61
feat: more dropdowns in new files and stuff
kbangelov Dec 23, 2025
328b234
chore: completed functionality and refactored code inconsistencies
kbangelov Dec 29, 2025
0c20327
chore: added aria-expanded everywhere on dropdowns
kbangelov Dec 29, 2025
6f3a80d
chore: code redacting
kbangelov Dec 29, 2025
05fb1da
chore: cleanup of isOpen logic for menus, since it is reimplemented
kbangelov Dec 29, 2025
333f1f8
chore: adjusted key press logic to match wanted functionality
kbangelov Dec 29, 2025
f1ea8f6
Merge branch 'develop' into task/uepr-445-ensure-navigable-toolbar
kbangelov Dec 30, 2025
2ce4eac
chore: post-merge accessibility readjustments
kbangelov Dec 30, 2025
fdac2f4
chore: minor code changes
kbangelov Dec 30, 2025
77f2ac6
chore: translated labels and some more adjustments
kbangelov Dec 30, 2025
3f518b5
chore: undeleted a line
kbangelov Jan 5, 2026
83bdad7
chore: moved file
kbangelov Jan 6, 2026
5640a76
chore: beginning to remove now obsolete old logic related to opening …
kbangelov Jan 7, 2026
4f92988
chore: refactored menu navigation logic via a hook
kbangelov Jan 8, 2026
287e1a5
chore: more refactoring
kbangelov Jan 8, 2026
19fef13
chore: removed old menus logic
kbangelov Jan 8, 2026
d2fd053
chore: package-lock.json back to original
kbangelov Jan 8, 2026
016f46f
chore: deleting rows
kbangelov Jan 8, 2026
d75933d
chore: refactored some menuRef, itemRef code
kbangelov Jan 9, 2026
e186ba9
chore: passing down remix message
kbangelov Jan 9, 2026
37c9974
chore: added some isRequired-s
kbangelov Jan 9, 2026
fa75484
chore: brought some elements inside file menu
kbangelov Jan 9, 2026
8626a1a
chore: fixed some aria labels
kbangelov Jan 9, 2026
965434b
chore: context is function instead of class now
kbangelov Jan 9, 2026
dbaa015
chore: more refactoring and semantic improvements
kbangelov Jan 12, 2026
824f352
chore: tiny bit more refactoring
kbangelov Jan 12, 2026
bad3449
Merge branch 'develop' into task/uepr-445-ensure-navigable-toolbar
kbangelov Jan 12, 2026
8c76688
chore: fix pipeline fail
kbangelov Jan 12, 2026
58b77bf
chore: fixing test
kbangelov Jan 12, 2026
408f6e2
chore: package-lock.json
kbangelov Jan 12, 2026
c4c59b1
chore: fixed unit test
kbangelov Jan 12, 2026
0ddeddb
chore: completed nav bar with all optional buttons
kbangelov Jan 15, 2026
7599d05
chore: refactored code, addressed comments
kbangelov Jan 15, 2026
7618f41
chore: changed accessibility messages and updated jsdoc
kbangelov Jan 15, 2026
a0985ea
chore: more code refactoring and comment addressing
kbangelov Jan 16, 2026
3a935c4
chore: removed redundant aria-label usage
kbangelov Jan 16, 2026
6864586
chore: fixing more things
kbangelov Jan 16, 2026
84124af
chore: removed old unnecessary code
kbangelov Jan 16, 2026
67c14ac
Merge branch 'develop' into task/uepr-445-ensure-navigable-toolbar
kbangelov Jan 16, 2026
3290043
chore: restore enter functionality thorugh hook
kbangelov Jan 16, 2026
f80c5d7
feat: reimplemented algorithm for finding inner menus via HTML tag BFS
kbangelov Jan 19, 2026
734a948
chore: fix unit tests
kbangelov Jan 19, 2026
f17c40f
chore: fixed integration tests
kbangelov Jan 19, 2026
ed3395d
Merge branch 'develop' into task/uepr-445-ensure-navigable-toolbar
kbangelov Jan 19, 2026
dd8b41c
chore: renamed methods
kbangelov Jan 19, 2026
c539ae6
chore: addressing some copilot comments
kbangelov Jan 19, 2026
b7ee8d9
chore: addressing comments, final pieces of refactoring
kbangelov Jan 20, 2026
c1d29dd
chore: improved html bfs tag logic
kbangelov Jan 21, 2026
e0eca58
chore: deleted obsolete code
kbangelov Jan 21, 2026
6690d7e
chore: small changes - prop name and description
kbangelov Jan 21, 2026
9b68c81
chore: refactor movement logic and account menu structure
kbangelov Jan 22, 2026
bc8dcb9
chore: refactored hook logic - switched from keeping state focusedInd…
kbangelov Jan 22, 2026
c4178e8
chore: moved depth as a prop for menus and separated styling into men…
kbangelov Jan 22, 2026
eb9e6da
chore: moved stylings to separate css file
kbangelov Jan 23, 2026
3f38961
chore: fixed background bug
kbangelov Jan 23, 2026
4aac3b7
chore: addressed copilot comments
kbangelov Jan 23, 2026
d393d77
chore: addressing code mistakes - replaced needless state, brought ba…
kbangelov Jan 27, 2026
783dbaf
chore: added reverse arrow key navigation on isRtl condition to hook
kbangelov Jan 28, 2026
0fb1a20
chore: refactored account menu, imrpoved hook logic, added label to m…
kbangelov Feb 5, 2026
4868bad
chore: added some default depth
kbangelov Feb 5, 2026
6449901
chore(deps): update dependency webpack to v5.105.0
renovate[bot] Feb 6, 2026
81d16ac
fix(deps): update dependency scratch-storage to v6.1.6
renovate[bot] Feb 6, 2026
6146fec
chore(deps): update dependency tap to v21.5.1
renovate[bot] Feb 9, 2026
e4ea44e
chore: fixed bug for focusing on the wrong element on close
kbangelov Feb 9, 2026
3ec88dd
fix(deps): update dependency scratch-storage to v6.1.7
renovate[bot] Feb 9, 2026
4ae6b5b
Merge branch 'develop' into task/uepr-445-ensure-navigable-toolbar
kbangelov Feb 10, 2026
ddb7970
chore: fixed package lock diff
kbangelov Feb 10, 2026
d1f2213
chore: updated package lock
kbangelov Feb 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 67 additions & 3 deletions package-lock.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from 'react';
import PropTypes from 'prop-types';
import bindAll from 'lodash.bindall';

export const MenuRefContext = React.createContext(null);

export class MenuRefProvider extends React.Component {
constructor (props) {
super(props);

this.state = {
refStack: []
};

bindAll(this, [
'push',
'pop',
'cut',
'clear',
'isTopMenu',
'isOpenMenu'
]);
}

push (ref, depth) {
if (depth <= this.state.refStack.length) {
this.cut(this.state.refStack[depth - 1]);
}

this.setState(prev => ({
refStack: [...prev.refStack, ref]
}));
}

pop () {
this.setState(prev => ({
stack: prev.refStack.slice(0, prev.refStack.length - 1)
}));
}

cut (ref) {
this.setState(prev => {
const refs = prev.refStack;
const index = refs.indexOf(ref);

if (index === -1) return {refStack: refs};

return {
refStack: refs.slice(0, index)
};
});
}

clear () {
this.setState({refStack: []});
}

isTopMenu (ref) {
const {refStack} = this.state;
return refStack.length > 0 && refStack[refStack.length - 1] === ref;
}

isOpenMenu (ref) {
return this.state.refStack.includes(ref);
}

render () {
const value = {
refStack: this.state.refStack,
push: this.push,
pop: this.pop,
cut: this.cut,
clear: this.clear,
isTopMenu: this.isTopMenu,
isOpenMenu: this.isOpenMenu
};

return (
<MenuRefContext.Provider value={value}>
{this.props.children}
</MenuRefContext.Provider>
);
}
}

MenuRefProvider.propTypes = {
children: PropTypes.node
};
86 changes: 45 additions & 41 deletions packages/scratch-gui/src/components/gui/gui.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import DebugModal from '../debug-modal/debug-modal.jsx';
import {setPlatform} from '../../reducers/platform.js';
import {setTheme} from '../../reducers/settings.js';
import {PLATFORM} from '../../lib/platform.js';
import {MenuRefProvider} from '../context-menu/menu-ref-context.jsx';

// Cache this value to only retrieve it once the first time.
// Assume that it doesn't change for a session.
Expand Down Expand Up @@ -272,47 +273,50 @@ const GUIComponent = props => {
onRequestClose={onRequestCloseBackdropLibrary}
/>
) : null}
{!menuBarHidden && <MenuBar
ariaRole="banner"
ariaLabel="Menu topbar"
accountNavOpen={accountNavOpen}
authorId={authorId}
authorThumbnailUrl={authorThumbnailUrl}
authorUsername={authorUsername}
authorAvatarBadge={authorAvatarBadge}
canChangeLanguage={canChangeLanguage}
canChangeColorMode={canChangeColorMode}
canChangeTheme={canChangeTheme}
canCreateCopy={canCreateCopy}
canCreateNew={canCreateNew}
canEditTitle={canEditTitle}
canManageFiles={canManageFiles}
canRemix={canRemix}
canSave={canSave}
canShare={canShare}
className={styles.menuBarPosition}
enableCommunity={enableCommunity}
hasActiveMembership={hasActiveMembership}
isShared={isShared}
isTotallyNormal={isTotallyNormal}
logo={logo}
renderLogin={renderLogin}
showComingSoon={showComingSoon}
onClickAbout={onClickAbout}
onClickAccountNav={onClickAccountNav}
onClickLogo={onClickLogo}
onCloseAccountNav={onCloseAccountNav}
onLogOut={onLogOut}
onOpenRegistration={onOpenRegistration}
onProjectTelemetryEvent={onProjectTelemetryEvent}
onSeeCommunity={onSeeCommunity}
onShare={onShare}
onStartSelectingFileUpload={onStartSelectingFileUpload}
onToggleLoginOpen={onToggleLoginOpen}
userOwnsProject={userOwnsProject}
username={username}
accountMenuOptions={accountMenuOptions}
/>}
{!menuBarHidden && <MenuRefProvider>
Comment thread
kbangelov marked this conversation as resolved.
Outdated
<MenuBar
ariaRole="banner"
ariaLabel="Menu topbar"
accountNavOpen={accountNavOpen}
authorId={authorId}
authorThumbnailUrl={authorThumbnailUrl}
authorUsername={authorUsername}
authorAvatarBadge={authorAvatarBadge}
canChangeLanguage={canChangeLanguage}
canChangeColorMode={canChangeColorMode}
canChangeTheme={canChangeTheme}
canCreateCopy={canCreateCopy}
canCreateNew={canCreateNew}
canEditTitle={canEditTitle}
canManageFiles={canManageFiles}
canRemix={canRemix}
canSave={canSave}
canShare={canShare}
className={styles.menuBarPosition}
enableCommunity={enableCommunity}
hasActiveMembership={hasActiveMembership}
isShared={isShared}
isTotallyNormal={isTotallyNormal}
logo={logo}
renderLogin={renderLogin}
showComingSoon={showComingSoon}
onClickAbout={onClickAbout}
onClickAccountNav={onClickAccountNav}
onClickLogo={onClickLogo}
onCloseAccountNav={onCloseAccountNav}
onLogOut={onLogOut}
onOpenRegistration={onOpenRegistration}
onProjectTelemetryEvent={onProjectTelemetryEvent}
onSeeCommunity={onSeeCommunity}
onShare={onShare}
onStartSelectingFileUpload={onStartSelectingFileUpload}
onToggleLoginOpen={onToggleLoginOpen}
userOwnsProject={userOwnsProject}
username={username}
accountMenuOptions={accountMenuOptions}
/>
</MenuRefProvider>
}
<Box className={classNames(boxStyles, styles.flexWrapper)}>
<Box
role="main"
Expand Down
129 changes: 129 additions & 0 deletions packages/scratch-gui/src/components/menu-bar/base-menu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {MenuRefContext} from '../context-menu/menu-ref-context';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General note - after selecting an item in a menu, and the menu closes, we'd want to keep the focus on the top-level menu element (e.g. keep focus on the "Settings" element after selecting a language

import React from 'react';
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';

/* Subclasses must implement (some optionally):
Comment thread
kbangelov marked this conversation as resolved.
Outdated
_______________________________________________
render
define this.itemRefs
add onKeyDown={this.handleKeyPress}
and onParentKeyPress={this.handleKeyPressOpenMenu} for MenuItem elements

and replace isOpenMenu-like props with this.isExpanded() checks

They should also receive:
______________________
onOpen,
onClose,
menuRef,
depth
*/
export class BaseMenu extends React.PureComponent {
constructor (props) {
super(props);
bindAll(this, [
'handleKeyPress',
'handleKeyPressOpenMenu',
'handleMove',
'handleOnOpen',
'handleOnClose',
'refocusRef',
'refocusItemByIndex',
'isExpanded'
]);

this.state = {focusedIndex: -1};
this.menuRef = props.menuRef;
Comment thread
kbangelov marked this conversation as resolved.
Outdated
}

static contextType = MenuRefContext;

refocusRef (ref) {
if (ref && ref.current) {
ref.current.focus();
}
}

refocusItemByIndex (index) {
this.setState({focusedIndex: index}, () => {
this.refocusRef(this.itemRefs[index]);
});
}

handleKeyPress (e) {
if (this.props.depth === 1) {
if (e.key === 'Tab') {
Comment thread
kbangelov marked this conversation as resolved.
Outdated
this.handleOnClose();
this.context.clear();
}
}

if (this.context.isTopMenu(this.menuRef)) {
this.handleKeyPressOpenMenu(e);
} else if (!this.isExpanded() && (e.key === ' ' || (e.key === 'ArrowRight' && this.props.depth !== 1))) {
Comment thread
kbangelov marked this conversation as resolved.
Outdated
Comment thread
kbangelov marked this conversation as resolved.
Outdated
e.preventDefault();
this.handleOnOpen();
}
}

handleKeyPressOpenMenu (e) {
if (e.key === 'ArrowDown') {
Comment thread
kbangelov marked this conversation as resolved.
Outdated
Comment thread
kbangelov marked this conversation as resolved.
Outdated
e.preventDefault();
this.handleMove(1);
}
if (e.key === 'ArrowUp') {
e.preventDefault();
this.handleMove(-1);
}
if (e.key === 'Enter' && this.props.clearOnItemSelect) {
this.context.clear();
}
if (e.key === 'ArrowLeft' || e.key === 'Escape') {
e.preventDefault();
this.handleOnClose();
}
}

handleOnOpen () {
if (this.context.isOpenMenu(this.menuRef)) return;

this.props.onOpen();
this.refocusItemByIndex(0);

this.context.push(this.menuRef, this.props.depth);
}

handleMove (direction) {
const newIndex = (this.state.focusedIndex + direction + this.itemRefs.length) % this.itemRefs.length;
Comment thread
kbangelov marked this conversation as resolved.
Outdated
this.setState({focusedIndex: newIndex}, () => {
this.refocusRef(this.itemRefs[newIndex]);
});
}

handleOnClose () {
Comment thread
kbangelov marked this conversation as resolved.
Outdated
this.context.cut(this.menuRef);
this.setState({focusedIndex: -1}, () => {
this.refocusRef(this.menuRef);
});

this.props.onClose();
}

isExpanded () {
return this.context.isOpenMenu(this.menuRef);
}
}

BaseMenu.propTypes = {
menuRef: PropTypes.shape({current: PropTypes.instanceOf(Element)}),
Comment thread
kbangelov marked this conversation as resolved.
Outdated
depth: PropTypes.number,
onOpen: PropTypes.func,
onClose: PropTypes.func,
clearOnItemSelect: PropTypes.bool
};

BaseMenu.defaultProps = {
onClose: () => {},
clearOnItemSelect: false
Comment thread
kbangelov marked this conversation as resolved.
Outdated
};
Loading
Loading