mirror of
https://github.com/discourse/discourse.git
synced 2025-02-07 03:48:23 +00:00
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.
662 lines
12 KiB
SCSS
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);
|
|
}
|
|
}
|