Merge pull request #4897 from Gazook89/Accessible-Dropdowns

More Accessible Dropdowns
This commit is contained in:
Víctor Losada Hernández
2026-08-16 10:44:51 +02:00
committed by GitHub
4 changed files with 48 additions and 69 deletions
+6 -4
View File
@@ -92,12 +92,13 @@ const Dropdown = ({ groupName, className = null, icon, children, color = null, c
}; };
return ( return (
<div className={['menu-wrapper', className].join(' ')} role='none' > <li className='menu-wrapper' role='none'>
<button <button
id={`${menuId}-trigger`} id={`${menuId}-trigger`}
className={['menu-item', color].join(' ')} className={['menu-item', color].join(' ')}
popoverTarget={menuId} popoverTarget={menuId}
aria-haspopup='menu' aria-haspopup='menu'
aria-label={groupName}
role='menuitem' role='menuitem'
disabled={!React.Children.count(children)} disabled={!React.Children.count(children)}
ref={triggerRef} ref={triggerRef}
@@ -105,18 +106,19 @@ const Dropdown = ({ groupName, className = null, icon, children, color = null, c
{trigger(groupName, icon)} {trigger(groupName, icon)}
</button> </button>
<MenuDepthContext.Provider value={depth + 1}> <MenuDepthContext.Provider value={depth + 1}>
<div <ul
ref={menuRef} ref={menuRef}
id={menuId} id={menuId}
className='menu-list' className='menu-list'
popover='auto' popover='auto'
role='menu' role='menu'
aria-label={`${groupName} Submenu`}
onClick={handleMenuActionClick} onClick={handleMenuActionClick}
> >
{children} {children}
</div> </ul>
</MenuDepthContext.Provider> </MenuDepthContext.Provider>
</div> </li>
); );
}; };
+19 -10
View File
@@ -1,24 +1,33 @@
.menu-wrapper { @property --menuColor {
position: relative; syntax: '<color>';
&:is(.menu-bar > .menu-section > .menu-wrapper){ inherits: true;
display: inline-block; initial-value: #DDD;
}
} }
@property --activeTriggerColor {
syntax: '<color>';
inherits: true;
initial-value: #DDD;
}
:root{
--activeTriggerColor : var(--activeTriggerColor);
}
.menu-list { .menu-list {
contain : content;
position : fixed; position : fixed;
z-index : 1000;
top : anchor(bottom); top : anchor(bottom);
left : anchor(left); left : anchor(left);
position-try: flip-inline flip-block; position-try: flip-inline flip-block;
color: inherit; // [popover] gets a `canvastext` color value from useragent. color: inherit; // [popover] gets a `canvastext` color value from useragent.
> .menu-wrapper { background: var(--menuColor);
position:relative; li > .menu-list {
> .menu-list {
margin: 0 0px; margin: 0 0px;
top : anchor(top); top : anchor(top);
left : anchor(right); left : anchor(right);
position-try: flip-inline; position-try: flip-inline;
} }
} }
.menu-wrapper:has(:popover-open) > button { // if menu is open...
background-color: var(--activeTriggerColor, hsl(from var(--menuColor) h s calc(l * .85))); // tint menu triggers based on menu color
} }
@@ -188,7 +188,7 @@ const Snippetbar = createReactClass({
const snippets = this.state.snippets.filter((snippetGroup)=>snippetGroup.view === this.props.view); const snippets = this.state.snippets.filter((snippetGroup)=>snippetGroup.view === this.props.view);
if(snippets.length === 0) return null; if(snippets.length === 0) return null;
return <div className='snippets'> return <ul className='snippets' role='menubar' aria-label='Snippets Menubar'>
{_.map(snippets, (snippetGroup)=>{ {_.map(snippets, (snippetGroup)=>{
return <SnippetGroup return <SnippetGroup
brew={this.props.brew} brew={this.props.brew}
@@ -201,7 +201,7 @@ const Snippetbar = createReactClass({
/>; />;
}) })
} }
</div>; </ul>;
}, },
replaceContent : function(item){ replaceContent : function(item){
@@ -327,12 +327,14 @@ const SnippetGroup = createReactClass({
return _.map(snippets, (snippet)=>{ return _.map(snippets, (snippet)=>{
if(!snippet.subsnippets){ if(!snippet.subsnippets){
return ( return (
<button className='menu-item' key={snippet.name} onClick={(e)=>this.handleSnippetClick(e, snippet)} role='menuitem'> <li key={snippet.name} role='none'>
<i className={snippet.icon} /> <button className='menu-item' onClick={(e)=>this.handleSnippetClick(e, snippet)} role='menuitem' aria-label={snippet.name} disabled={snippet.disabled}>
<span className={`name${snippet.disabled ? ' disabled' : ''}`} title={snippet.name}>{snippet.name}</span> <i className={snippet.icon} />
{snippet.experimental && <span className='beta'>beta</span>} <span className={`name${snippet.disabled ? ' disabled' : ''}`} title={snippet.name}>{snippet.name}</span>
{snippet.disabled && <span className='beta' title='temporarily disabled due to large slowdown; under re-design'>disabled</span>} {snippet.experimental && <span className='status'>beta</span>}
</button> {snippet.disabled && <span className='status' title='temporarily disabled due to large slowdown; under re-design'>disabled</span>}
</button>
</li>
); );
} else if(snippet.subsnippets){ } else if(snippet.subsnippets){
return ( return (
@@ -3,6 +3,8 @@
@import (less) '@themes/fonts/5e/fonts.less'; @import (less) '@themes/fonts/5e/fonts.less';
.snippetBar { .snippetBar {
--activeTriggerColor: inherit;
--menuColor : #DDDDDD;
@menuHeight : 25px; @menuHeight : 25px;
position : relative; position : relative;
display : flex; display : flex;
@@ -16,12 +18,6 @@
text-transform: uppercase; text-transform: uppercase;
font-weight: 800; font-weight: 800;
.snippets {
display : flex;
justify-content : flex-start;
min-width : 499.35px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
}
.editors { .editors {
display : flex; display : flex;
justify-content : flex-end; justify-content : flex-end;
@@ -118,23 +114,7 @@
} }
} }
} }
.snippetBarButton {
display : inline-block;
height : @menuHeight;
padding : 0px 5px;
font-size : 0.625em;
font-weight : 800;
line-height : @menuHeight;
text-transform : uppercase;
text-wrap : nowrap;
cursor : pointer;
&:hover, &.selected { background-color : #999999; }
i {
margin-right : 3px;
font-size : 1.4em;
vertical-align : middle;
}
}
.toggleMeta { .toggleMeta {
position : absolute; position : absolute;
top : 0px; top : 0px;
@@ -143,20 +123,15 @@
.tooltipLeft('Edit Brew Properties'); .tooltipLeft('Edit Brew Properties');
} }
.snippets {
.menu-wrapper { display : flex;
.menu-item:is(.snippets > .menu-wrapper > .menu-item):first-child { justify-content : flex-start;
.caret { min-width : 565.95px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
display: none;
}
}
.menu-list {
padding : 0px;
background-color : #DDDDDD;
}
} }
// removed caret for top level items, by request (makes buttons too wide).
.menu-wrapper .menu-item:is(.snippets > .menu-wrapper > .menu-item):first-child .caret { display: none; }
.menu-item { .menu-item {
position : relative; position : relative;
display : flex; display : flex;
@@ -166,9 +141,8 @@
padding : 5px; padding : 5px;
cursor : pointer; cursor : pointer;
width: 100%; width: 100%;
.animate(background-color);
&:is(.menu-list .menu-item) [class*="name"] { &:is(.menu-list .menu-item) [class*="name"] {
padding-inline: 8px; padding-inline: 8px; // additional space between icon and name (helpful in Fonts menu especially).
} }
.menu-name { .menu-name {
flex: 1; flex: 1;
@@ -214,8 +188,7 @@
} }
} }
.name { margin-right : auto; } .name { margin-right : auto; }
.disabled { text-decoration : line-through; } .status {
.beta {
align-self : center; align-self : center;
padding : 4px 6px; padding : 4px 6px;
margin-left : 5px; margin-left : 5px;
@@ -234,15 +207,8 @@
&:hover { background-color: unset; } &:hover { background-color: unset; }
} }
} }
.disabledSnippets {
color: grey;
cursor: not-allowed;
&:hover { background-color: #DDDDDD;}
}
} }
@container editor (width < 750px) { @container editor (width < 816px) {
.snippetBar { .snippetBar {
.editors { .editors {
flex : 1; flex : 1;