Osama Sayegh 496f910f03
DEV: Various A11Y improvements for the new user menu (#18288)
This commit includes various accessibility improvements for the new user menu:

* Add `title` attributes to the user menu tabs
* Properly label lists (by adding `aria-labelledby` to `<ul>` elements) for screen readers
* Change the user menu structure so that the tabs come before the content panel in the DOM, but use CSS to reverse them visually.
  Normally, changing the order of elements via CSS is bad for accessibility, but I believe this is one of the rare scenarios where it [makes sense](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Ordering_Flex_Items#use_cases_for_order). Prior to this change, if you want to reach the first notification item after you select a tab using the keyboard, you have to hit <kbd>ctrl</kbd>+<kbd>tab</kbd> because the notifications list is before the tabs list. However, with this change, <kbd>tab</kbd> will move you to the first item in the list after you select a tab using your keyboard.
* Aria-hide the unread notifications badge/count on the tabs because the `title` attribute on the tab indicates the unread count.
* Add some tests.
2022-09-20 19:31:56 +03:00

662 lines
12 KiB
SCSS

.menu-panel.slide-in {
position: fixed;
right: 0;
box-shadow: shadow("header");
.panel-body {
width: 100%;
}
}
.header-cloak {
display: none;
}
.menu-panel.drop-down {
position: absolute;
// positions are relative to the .d-header .panel div
top: 100%; // directly underneath .panel
right: -10px; // 10px to the right of .panel - adjust as needed
max-height: 80vh;
}
.menu-panel {
border: 1px solid var(--primary-low);
box-shadow: shadow("menu-panel");
background-color: var(--secondary);
z-index: z("header");
padding: 0.5em;
width: 320px;
overflow: hidden;
display: flex;
flex-direction: column;
hr {
margin: 3px 0;
}
.panel-header {
position: absolute;
right: 20px;
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
.panel-body {
display: flex;
touch-action: pan-y pinch-zoom;
overflow: hidden;
height: 100%;
}
.panel-body-contents {
max-height: 100%;
width: 100%;
display: flex;
flex-direction: column;
}
.panel-body-bottom {
display: flex;
flex: 1 0 0%; // safari height fix
margin-top: 0.5em;
flex-wrap: wrap;
.show-all {
display: flex;
flex: 1 1 auto;
button {
width: 100%;
}
}
.notifications-dismiss {
margin-left: 0.5em;
}
.btn {
background-color: var(--primary-very-low);
color: var(--primary-high);
&:hover {
background: var(--primary-low);
color: var(--primary);
}
}
}
.badge-notification {
vertical-align: text-bottom;
}
}
.user-menu.revamped {
right: 0;
width: 320px;
padding: 0;
.panel-body-bottom {
flex: 0;
}
.menu-tabs-container {
display: flex;
flex-direction: column;
border-left: 1px solid var(--primary-low);
padding: 0.75em 0 0;
}
.tabs-list {
display: flex;
flex-direction: column;
.btn {
display: flex;
padding: 0.857em;
position: relative;
.d-icon {
color: var(--primary-medium);
}
.badge-notification {
background-color: var(--tertiary-med-or-tertiary);
position: absolute;
right: 6px;
top: 6px;
font-size: var(--font-down-3);
}
&.active {
background-color: var(--tertiary-low);
}
&:hover {
background-color: var(--highlight-medium);
}
}
}
.bottom-tabs {
border-top: 1px solid var(--primary-low);
}
.panel-body-contents {
display: flex;
flex-direction: row-reverse;
}
.quick-access-panel {
width: 320px;
padding: 0.75em;
justify-content: space-between;
box-sizing: border-box;
min-width: 0; // makes sure menu tabs don't go off screen
.double-user,
.multi-user {
white-space: unset;
}
.item-label {
color: var(--primary);
}
li {
background-color: var(--secondary);
&.unread,
&.pending {
background-color: var(--tertiary-low);
}
&:hover {
background-color: var(--highlight-medium);
outline: none;
}
&:focus-within {
background: var(--highlight-medium);
a {
// we don't need the link focus because we're styling the parent
outline: 0;
}
}
}
}
#quick-access-profile {
display: inline;
max-height: 99%; // macOS Chrome sometimes adds an unneeded scrollbar at 100%
ul {
flex-wrap: nowrap;
height: 100%;
align-items: center;
overflow-y: auto; // really short viewports
}
li {
flex: 1 1 auto;
max-height: 3em; // prevent buttons from getting too tall
> * {
// button, a, and everything else
height: 100%;
align-items: center;
margin: 0;
padding: 0 0.5em;
}
.d-icon {
padding-top: 0;
}
}
.set-user-status {
.emoji {
padding-top: 0;
}
}
.profile-tab-btn {
.relative-date {
font-size: $font-down-3;
color: var(--primary-medium);
}
justify-content: unset;
line-height: $line-height-large;
width: 100%;
.d-icon {
padding: 0;
}
}
.do-not-disturb {
.d-icon-toggle-on {
color: var(--tertiary);
}
}
}
}
// remove when the widgets-based implementation of the user menu is removed
.user-menu:not(.revamped) {
.quick-access-panel {
li {
span:first-child {
color: var(--primary);
}
}
}
}
.hamburger-panel {
a.widget-link {
width: 100%;
box-sizing: border-box;
@include ellipsis;
}
.panel-body {
overflow-y: auto;
}
span.badge-category {
max-width: 100px;
}
}
.menu-links.columned {
li {
width: 50%;
float: left;
}
}
.menu-panel {
.widget-link,
.categories-link {
padding: 0.25em 0.5em;
display: block;
color: var(--primary);
&:hover,
&:focus {
background-color: var(--highlight-medium);
outline: none;
}
.d-icon {
color: var(--primary-medium);
}
.new {
font-size: $font-down-1;
margin-left: 0.5em;
color: var(--primary-med-or-secondary-med);
}
&.show-help,
&.filter {
color: var(--tertiary);
}
}
li.category-link {
float: left;
background-color: transparent;
display: inline-flex;
align-items: center;
padding: 0.25em 0.5em;
width: 50%;
box-sizing: border-box;
a {
display: inline-flex;
&:hover,
&:focus {
background: transparent;
.category-name {
color: var(--primary);
}
}
}
.badge-notification {
color: var(--primary-med-or-secondary-med);
background-color: transparent;
display: inline;
padding: 0;
font-size: $font-down-1;
line-height: $line-height-large;
}
.badge-wrapper {
&.bar,
&.bullet {
color: var(--primary);
padding: 0 0 0 0.15em;
}
&.box {
color: var(--secondary);
+ a.badge.badge-notification {
padding-top: 2px;
}
span {
z-index: z("base") * -1;
}
}
}
}
// note these topic counts only appear for anons in the category hamburger drop down
b.topics-count {
color: var(--primary-med-or-secondary-med);
font-weight: normal;
font-size: $font-down-1;
}
div.discourse-tags {
font-size: $font-down-1;
}
}
.user-menu {
.quick-access-panel {
width: 100%;
display: flex;
flex-direction: column;
min-height: 0;
max-height: 100%;
border-top: 1px solid var(--primary-low);
padding-top: 0.75em;
margin-top: -1px;
&:focus {
outline: none;
}
h3 {
padding: 0 0.4em;
font-weight: bold;
margin: 0.5em 0;
}
.d-icon,
&:hover .d-icon {
color: var(--primary-medium);
}
.icon {
color: var(--primary-high);
}
.btn-primary {
.d-icon {
color: var(--secondary);
}
}
ul {
display: flex;
flex-flow: column wrap;
overflow: hidden;
max-height: 100%;
}
li {
background-color: var(--tertiary-low);
box-sizing: border-box;
list-style-type: none;
// This is until other languages remove the HTML from within
// notifications. It can then be removed
div .fa {
display: none;
}
span.double-user,
// e.g., "username, username2"
span.multi-user
// e.g., "username, username2, and n others"
{
display: inline-flex;
max-width: 100%;
align-items: baseline;
white-space: nowrap;
}
span.multi-user
// e.g., "username, username2, and n others"
{
span.multi-username:nth-of-type(2) {
// margin between username2 and "and n others"
margin-right: 0.25em;
}
}
// truncate when usernames are very long
span.multi-username {
@include ellipsis;
flex: 0 1 auto;
min-width: 1.2em;
max-width: 10em;
&:nth-of-type(2) {
// margin for comma between username and username2
margin-left: 0.25em;
}
}
&:hover {
background-color: var(--highlight-medium);
outline: none;
}
&:focus-within {
background: var(--highlight-medium);
a {
// we don't need the link focus because we're styling the parent
outline: 0;
}
.btn-flat:focus {
// undo default btn-flat style
background: transparent;
}
}
a,
.profile-tab-btn {
display: flex;
margin: 0.25em;
padding: 0em 0.25em;
}
button {
padding: 0.25em 0.5em;
}
a,
button {
> div {
overflow: hidden; // clears the text from wrapping below icons
overflow-wrap: anywhere;
@supports not (overflow-wrap: anywhere) {
word-break: break-word;
}
// Truncate items with more than 2 lines.
@include line-clamp(2);
}
}
p {
margin: 0;
overflow: hidden;
}
}
li:not(.show-all) {
padding: 0;
align-self: flex-start;
width: 100%;
.d-icon {
padding-top: 0.2em;
margin-right: 0.5em;
}
img.emoji {
height: 1em;
width: 1em;
padding-top: 0.2em;
margin-right: 0.5em;
}
}
.is-warning {
.d-icon-far-envelope {
color: var(--danger);
}
}
.read {
background-color: var(--secondary);
}
.none {
padding-top: 5px;
}
.spinner-container {
min-height: 2em;
}
.spinner {
width: 20px;
height: 20px;
border-width: 2px;
margin: 0 auto;
}
.show-all a {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
min-height: 30px;
color: var(--primary-med-or-secondary-high);
background: var(--blend-primary-secondary-5);
&:hover {
color: var(--primary);
background: var(--primary-low);
}
}
/* as a big ol' click target, don't let text inside be selected */
@include unselectable;
&.quick-access-profile {
display: inline;
li:not(.show-all) a {
color: var(--primary);
display: flex;
}
ul button {
line-height: 1.4; // match 'ul a' link height
width: 100%;
display: flex;
}
@media screen and (max-height: 360px) {
// two column grid to avoid scroll
ul {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-gap: 0 1em;
a {
@include ellipsis;
> div {
display: block;
}
}
button {
span:not(.relative-date) {
@include ellipsis;
}
}
}
li:not(.show-all) a {
color: var(--primary);
display: flex;
}
}
}
}
}
div.menu-links-header {
width: 100%;
.menu-links-row {
box-sizing: border-box;
display: flex;
width: 100%;
z-index: 2;
justify-content: space-between;
.glyphs {
display: inline-flex;
align-items: center;
flex-wrap: nowrap;
width: 100%;
justify-content: space-between;
padding: 0;
button {
display: flex;
flex: 1 1 auto;
padding: 0.65em 0.25em 0.75em;
justify-content: center;
svg {
pointer-events: none;
}
}
}
button {
// This is to make sure active and inactive tab icons have the same
// size. `box-sizing` does not work and I have no idea why.
border: 1px solid transparent;
&:not(.active):hover {
border-bottom: 0;
margin-top: -1px;
}
}
button.active {
border: 1px solid var(--primary-low);
border-bottom: 1px solid var(--secondary);
position: relative;
.d-icon {
color: var(--primary-high);
}
&:hover {
background-color: inherit;
}
}
}
button:hover,
button:focus {
background-color: var(--primary-low);
outline: none;
&.active {
background-color: var(--primary-very-low);
}
}
button {
padding: 0.3em 0.5em;
}
.glyphs {
display: table-cell;
width: auto;
text-align: center;
}
.glyphs:first-child {
text-align: left;
}
.glyphs:last-child {
text-align: right;
}
.fa,
button {
color: var(--primary-med-or-secondary-med);
}
}