FIX: Prevent focus shift when navigating the user directory (#29209)

Prevent focus shift when filtering in the user directory.

Also glimmerifies the table header toggle
This commit is contained in:
Natalie Tay 2024-10-16 11:37:08 +08:00 committed by GitHub
parent 35284c77f1
commit c8a94b5b3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 177 additions and 139 deletions

View File

@ -98,4 +98,12 @@ export default class AdminUsersListShowController extends Controller.extend(
this.toggleProperty("showEmails");
this.resetFilters();
}
@action
updateOrder(field, asc) {
this.setProperties({
order: field,
asc,
});
}
}

View File

@ -46,6 +46,7 @@
>
<:header>
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@field="username"
@labelKey="username"
@order={{this.order}}
@ -54,6 +55,7 @@
class="directory-table__column-header--username"
/>
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@field="email"
@labelKey="email"
@order={{this.order}}
@ -66,6 +68,7 @@
}}
/>
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@field="last_emailed"
@labelKey="admin.users.last_emailed"
@order={{this.order}}
@ -73,6 +76,7 @@
@automatic={{true}}
/>
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@field="seen"
@labelKey="last_seen"
@order={{this.order}}
@ -80,6 +84,7 @@
@automatic={{true}}
/>
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@field="topics_viewed"
@labelKey="admin.user.topics_entered"
@order={{this.order}}
@ -87,6 +92,7 @@
@automatic={{true}}
/>
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@field="posts_read"
@labelKey="admin.user.posts_read_count"
@order={{this.order}}
@ -94,6 +100,7 @@
@automatic={{true}}
/>
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@field="read_time"
@labelKey="admin.user.time_read"
@order={{this.order}}
@ -101,6 +108,7 @@
@automatic={{true}}
/>
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@field="created"
@labelKey="created"
@order={{this.order}}

View File

@ -7,6 +7,7 @@
/>
{{#each this.columns as |column|}}
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@field={{column.name}}
@icon={{column.icon}}
@order={{this.order}}

View File

@ -29,4 +29,12 @@ export default class DirectoryTable extends Component {
this._table.scrollLeft = scrollPixels;
}
}
@action
updateOrder(field, asc) {
this.setProperties({
order: field,
asc,
});
}
}

View File

@ -0,0 +1,130 @@
import Component from "@glimmer/component";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { schedule } from "@ember/runloop";
import icon from "discourse-common/helpers/d-icon";
import I18n from "discourse-i18n";
export default class TableHeaderToggle extends Component {
get id() {
return `table-header-toggle-${this.args.field.replace(/\s/g, "")}`;
}
get labelKey() {
if (!this.args.automatic && !this.args.translated) {
return this.args.field;
} else {
return this.args.labelKey;
}
}
get ariaSort() {
if (this.args.order === this.args.field) {
return this.args.asc ? "ascending" : "descending";
} else {
return "none";
}
}
get chevronIcon() {
if (this.args.order === this.args.field) {
return this.args.asc ? "chevron-up" : "chevron-down";
}
}
get pressedState() {
if (this.args.order === this.args.field) {
return this.args.asc ? "mixed" : "true";
} else {
return "false";
}
}
get ariaLabel() {
let criteria = "";
if (this.args.icon === "heart") {
criteria += `${I18n.t("likes_lowercase", { count: 2 })} `;
}
if (this.args.translated) {
criteria += this.args.field;
} else {
const labelKey = this.labelKey || `directory.${this.args.field}`;
criteria += I18n.t(`${labelKey}_long`, {
defaultValue: I18n.t(labelKey),
});
}
return I18n.t("directory.sort.label", { criteria });
}
get iconName() {
return this.args.icon || null;
}
get label() {
const labelKey = this.args.labelKey || `directory.${this.args.field}`;
return this.args.translated
? this.args.field
: I18n.t(`${labelKey}_long`, {
defaultValue: I18n.t(labelKey),
});
}
@action
toggleProperties() {
const newAsc =
this.args.order === this.args.field && !this.args.asc ? true : null;
this.args.onToggle?.(this.args.field, newAsc);
schedule("afterRender", () => {
document.getElementById(this.id)?.focus();
});
}
@action
click() {
this.toggleProperties();
}
@action
keyPress(event) {
if (event.key === "Enter") {
this.toggleProperties();
}
}
<template>
<div
...attributes
class="directory-table__column-header sortable"
aria-sort={{this.ariaSort}}
role="columnheader"
{{! template-lint-disable no-invalid-interactive }}
{{on "click" this.click}}
{{! template-lint-disable no-invalid-interactive }}
{{on "keypress" this.keyPress}}
>
<div
class="header-contents"
id={{this.id}}
role="button"
tabindex="0"
aria-label={{this.ariaLabel}}
aria-pressed={{this.pressedState}}
>
{{yield}}
<span class="text">
{{#if this.iconName}}
{{icon this.iconName}}
{{/if}}
{{this.label}}
{{#if this.chevronIcon}}
{{icon this.chevronIcon}}
{{/if}}
</span>
</div>
</div>
</template>
}

View File

@ -1,20 +0,0 @@
<div
class="header-contents"
id={{this.id}}
role="button"
tabindex="0"
aria-label={{this.ariaLabel}}
aria-pressed={{this.pressedState}}
>
{{yield}}
<span class="text">
{{directory-table-header-title
field=this.field
labelKey=this.labelKey
icon=this.icon
translated=this.translated
}}
{{this.chevronIcon}}
</span>
</div>

View File

@ -1,119 +0,0 @@
import Component from "@ember/component";
import { schedule } from "@ember/runloop";
import { htmlSafe } from "@ember/template";
import {
attributeBindings,
classNames,
tagName,
} from "@ember-decorators/component";
import { iconHTML } from "discourse-common/lib/icon-library";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
@tagName("div")
@classNames("directory-table__column-header", "sortable")
@attributeBindings("title", "colspan", "ariaSort:aria-sort", "role")
export default class TableHeaderToggle extends Component {
role = "columnheader";
labelKey = null;
chevronIcon = null;
columnIcon = null;
translated = false;
automatic = false;
onActiveRender = null;
pressedState = null;
ariaLabel = null;
@discourseComputed("order", "field", "asc")
ariaSort() {
if (this.order === this.field) {
return this.asc ? "ascending" : "descending";
} else {
return "none";
}
}
toggleProperties() {
if (this.order === this.field) {
this.set("asc", this.asc ? null : true);
} else {
this.setProperties({ order: this.field, asc: null });
}
}
toggleChevron() {
if (this.order === this.field) {
let chevron = iconHTML(this.asc ? "chevron-up" : "chevron-down");
this.set("chevronIcon", htmlSafe(`${chevron}`));
} else {
this.set("chevronIcon", null);
}
}
click() {
this.toggleProperties();
}
keyPress(e) {
if (e.which === 13) {
this.toggleProperties();
}
}
didReceiveAttrs() {
super.didReceiveAttrs(...arguments);
if (!this.automatic && !this.translated) {
this.set("labelKey", this.field);
}
this.set("id", `table-header-toggle-${this.field.replace(/\s/g, "")}`);
this.toggleChevron();
this._updateA11yAttributes();
}
didRender() {
super.didRender(...arguments);
if (this.onActiveRender && this.chevronIcon) {
this.onActiveRender(this.element);
}
}
_updateA11yAttributes() {
let criteria = "";
const pressed = this.order === this.field;
if (this.icon === "heart") {
criteria += `${I18n.t("likes_lowercase", { count: 2 })} `;
}
if (this.translated) {
criteria += this.field;
} else {
const labelKey = this.labelKey || `directory.${this.field}`;
criteria += I18n.t(labelKey + "_long", {
defaultValue: I18n.t(labelKey),
});
}
this.set("ariaLabel", I18n.t("directory.sort.label", { criteria }));
if (pressed) {
if (this.asc) {
this.set("pressedState", "mixed");
} else {
this.set("pressedState", "true");
}
this._focusHeader();
} else {
this.set("pressedState", "false");
}
}
_focusHeader() {
schedule("afterRender", () => {
document.getElementById(this.id)?.focus();
});
}
}

View File

@ -239,4 +239,12 @@ export default class GroupIndexController extends Controller {
this.bulkSelection.removeObject(member);
}
}
@action
updateOrder(field, asc) {
this.setProperties({
order: field,
asc,
});
}
}

View File

@ -114,4 +114,12 @@ export default class GroupRequestsController extends Controller {
request_denied: true,
});
}
@action
updateOrder(field, asc) {
this.setProperties({
order: field,
asc,
});
}
}

View File

@ -82,6 +82,7 @@
>
<:header>
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@order={{this.order}}
@asc={{this.asc}}
@field="username_lower"
@ -103,6 +104,7 @@
/>
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@order={{this.order}}
@asc={{this.asc}}
@field="added_at"
@ -111,6 +113,7 @@
class="directory-table__column-header--added"
/>
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@order={{this.order}}
@asc={{this.asc}}
@field="last_posted_at"
@ -119,6 +122,7 @@
class="directory-table__column-header--last-posted"
/>
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@order={{this.order}}
@asc={{this.asc}}
@field="last_seen_at"

View File

@ -19,6 +19,7 @@
<ResponsiveTable @className="group-members group-members__requests">
<:header>
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@order={{this.order}}
@asc={{this.asc}}
@field="username_lower"
@ -27,6 +28,7 @@
class="username"
/>
<TableHeaderToggle
@onToggle={{this.updateOrder}}
@order={{this.order}}
@asc={{this.asc}}
@field="requested_at"