mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 05:21:50 +08:00
UX: Automatically collapse admin page header buttons on mobile (#29040)
This commit attempts to improve the mobile experience for admin page header and subheader by automatically collapsing all action buttons in these components into a DMenu when viewing mobile. This is done by using different "list" wrapper components and a DMenu trigger and a DropdownMenu on mobile only, and uses has-block to determine whether to render the DMenu trigger at all. This also removes the `PluginOutlet` in `AdminPluginConfigPage`, it was too inflexible for this `DropdownMenu` case, and since the `:actions` were always rendering we couldn't rely on `has-block`. A new plugin API, `registerPluginHeaderActionComponent`, has been introduced instead to replace it.
This commit is contained in:
parent
4ea3d69979
commit
85774cc214
|
@ -74,14 +74,13 @@ export default class AdminBackupsActions extends Component {
|
|||
@action={{routeAction "rollback"}}
|
||||
@label="admin.backups.operations.rollback.label"
|
||||
@title="admin.backups.operations.rollback.title"
|
||||
@icon="truck-medical"
|
||||
@disabled={{this.rollbackDisabled}}
|
||||
@icon="truck-medical"
|
||||
class="admin-backups__rollback"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<@actions.Default
|
||||
@icon={{if this.site.isReadOnly "far-eye-slash" "far-eye"}}
|
||||
@action={{this.toggleReadOnlyMode}}
|
||||
@disabled={{@backups.isOperationRunning}}
|
||||
@title={{if
|
||||
|
@ -94,6 +93,7 @@ export default class AdminBackupsActions extends Component {
|
|||
"admin.backups.read_only.disable.label"
|
||||
"admin.backups.read_only.enable.label"
|
||||
}}
|
||||
@icon={{if this.site.isReadOnly "far-eye-slash" "far-eye"}}
|
||||
class="admin-backups__toggle-read-only"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { hash } from "@ember/helper";
|
||||
import DButton from "discourse/components/d-button";
|
||||
|
||||
export const AdminPageActionButton = <template>
|
||||
|
@ -13,6 +14,18 @@ export const AdminPageActionButton = <template>
|
|||
@isLoading={{@isLoading}}
|
||||
/>
|
||||
</template>;
|
||||
|
||||
// This is used for cases where there is another component,
|
||||
// e.g. UppyBackupUploader, that is a button which cannot use
|
||||
// PrimaryButton and so on directly. This should be used very rarely,
|
||||
// most cases are covered by the other button types.
|
||||
export const WrappedButton = <template>
|
||||
<span class="admin-page-action-wrapped-button">{{yield}}</span>
|
||||
</template>;
|
||||
|
||||
// No buttons here pass in an @icon by design. They are okay to
|
||||
// use on dropdown list items, but our UI guidelines do not allow them
|
||||
// on regular buttons.
|
||||
export const PrimaryButton = <template>
|
||||
<AdminPageActionButton
|
||||
class="btn-primary"
|
||||
|
@ -22,10 +35,10 @@ export const PrimaryButton = <template>
|
|||
@routeModels={{@routeModels}}
|
||||
@label={{@label}}
|
||||
@title={{@title}}
|
||||
@icon={{@icon}}
|
||||
@isLoading={{@isLoading}}
|
||||
/>
|
||||
</template>;
|
||||
|
||||
export const DangerButton = <template>
|
||||
<AdminPageActionButton
|
||||
class="btn-danger"
|
||||
|
@ -35,12 +48,55 @@ export const DangerButton = <template>
|
|||
@routeModels={{@routeModels}}
|
||||
@label={{@label}}
|
||||
@title={{@title}}
|
||||
@icon={{@icon}}
|
||||
@isLoading={{@isLoading}}
|
||||
/>
|
||||
</template>;
|
||||
|
||||
export const DefaultButton = <template>
|
||||
<AdminPageActionButton
|
||||
class="btn-default"
|
||||
...attributes
|
||||
@action={{@action}}
|
||||
@route={{@route}}
|
||||
@routeModels={{@routeModels}}
|
||||
@label={{@label}}
|
||||
@title={{@title}}
|
||||
@isLoading={{@isLoading}}
|
||||
/>
|
||||
</template>;
|
||||
|
||||
export const AdminPageActionListItem = <template>
|
||||
<li class="dropdown-menu__item admin-page-action-list-item">
|
||||
<AdminPageActionButton
|
||||
class="btn-transparent"
|
||||
...attributes
|
||||
@action={{@action}}
|
||||
@route={{@route}}
|
||||
@routeModels={{@routeModels}}
|
||||
@label={{@label}}
|
||||
@title={{@title}}
|
||||
@icon={{@icon}}
|
||||
@isLoading={{@isLoading}}
|
||||
/>
|
||||
</li>
|
||||
</template>;
|
||||
|
||||
// This is used for cases where there is another component,
|
||||
// e.g. UppyBackupUploader, that is a button which cannot use
|
||||
// PrimaryActionListItem and so on directly. This should be used very rarely,
|
||||
// most cases are covered by the other list types.
|
||||
export const WrappedActionListItem = <template>
|
||||
<li
|
||||
class="dropdown-menu__item admin-page-action-list-item admin-page-action-wrapped-list-item"
|
||||
>
|
||||
{{yield (hash buttonClass="btn-transparent")}}
|
||||
</li>
|
||||
</template>;
|
||||
|
||||
// It is not a mistake that `btn-default` is used here, in a list
|
||||
// there is no need for blue text.
|
||||
export const PrimaryActionListItem = <template>
|
||||
<AdminPageActionListItem
|
||||
class="btn-default"
|
||||
...attributes
|
||||
@action={{@action}}
|
||||
|
@ -52,3 +108,31 @@ export const DefaultButton = <template>
|
|||
@isLoading={{@isLoading}}
|
||||
/>
|
||||
</template>;
|
||||
|
||||
export const DefaultActionListItem = <template>
|
||||
<AdminPageActionListItem
|
||||
class="btn-default"
|
||||
...attributes
|
||||
@action={{@action}}
|
||||
@route={{@route}}
|
||||
@routeModels={{@routeModels}}
|
||||
@label={{@label}}
|
||||
@title={{@title}}
|
||||
@icon={{@icon}}
|
||||
@isLoading={{@isLoading}}
|
||||
/>
|
||||
</template>;
|
||||
|
||||
export const DangerActionListItem = <template>
|
||||
<AdminPageActionListItem
|
||||
class="btn-danger"
|
||||
...attributes
|
||||
@action={{@action}}
|
||||
@route={{@route}}
|
||||
@routeModels={{@routeModels}}
|
||||
@label={{@label}}
|
||||
@title={{@title}}
|
||||
@icon={{@icon}}
|
||||
@isLoading={{@isLoading}}
|
||||
/>
|
||||
</template>;
|
||||
|
|
|
@ -1,17 +1,28 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { hash } from "@ember/helper";
|
||||
import { service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { or } from "truth-helpers";
|
||||
import DBreadcrumbsContainer from "discourse/components/d-breadcrumbs-container";
|
||||
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
||||
import DropdownMenu from "discourse/components/dropdown-menu";
|
||||
import HorizontalOverflowNav from "discourse/components/horizontal-overflow-nav";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
import {
|
||||
DangerActionListItem,
|
||||
DangerButton,
|
||||
DefaultActionListItem,
|
||||
DefaultButton,
|
||||
PrimaryActionListItem,
|
||||
PrimaryButton,
|
||||
WrappedActionListItem,
|
||||
WrappedButton,
|
||||
} from "admin/components/admin-page-action-button";
|
||||
import DMenu from "float-kit/components/d-menu";
|
||||
|
||||
export default class AdminPageHeader extends Component {
|
||||
@service site;
|
||||
|
||||
get title() {
|
||||
if (this.args.titleLabelTranslated) {
|
||||
return this.args.titleLabelTranslated;
|
||||
|
@ -41,14 +52,54 @@ export default class AdminPageHeader extends Component {
|
|||
<h1 class="admin-page-header__title">{{this.title}}</h1>
|
||||
{{/if}}
|
||||
|
||||
<div class="admin-page-header__actions">
|
||||
{{yield
|
||||
(hash
|
||||
Primary=PrimaryButton Default=DefaultButton Danger=DangerButton
|
||||
)
|
||||
to="actions"
|
||||
}}
|
||||
</div>
|
||||
{{#if (or (has-block "actions") @headerActionComponent)}}
|
||||
<div class="admin-page-header__actions">
|
||||
{{#if this.site.mobileView}}
|
||||
<DMenu
|
||||
@identifier="admin-page-header-mobile-actions"
|
||||
@title={{i18n "more_options"}}
|
||||
@icon="ellipsis-vertical"
|
||||
class="btn-small"
|
||||
>
|
||||
<:content>
|
||||
<DropdownMenu class="admin-page-header__mobile-actions">
|
||||
{{#let
|
||||
(hash
|
||||
Primary=PrimaryActionListItem
|
||||
Default=DefaultActionListItem
|
||||
Danger=DangerActionListItem
|
||||
Wrapped=WrappedActionListItem
|
||||
)
|
||||
as |actions|
|
||||
}}
|
||||
{{#if (has-block "actions")}}
|
||||
{{yield actions to="actions"}}
|
||||
{{else}}
|
||||
<@headerActionComponent @actions={{actions}} />
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
</DropdownMenu>
|
||||
</:content>
|
||||
</DMenu>
|
||||
{{else}}
|
||||
{{#let
|
||||
(hash
|
||||
Primary=PrimaryButton
|
||||
Default=DefaultButton
|
||||
Danger=DangerButton
|
||||
Wrapped=WrappedButton
|
||||
)
|
||||
as |actions|
|
||||
}}
|
||||
{{#if (has-block "actions")}}
|
||||
{{yield actions to="actions"}}
|
||||
{{else}}
|
||||
<@headerActionComponent @actions={{actions}} />
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if this.description}}
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { hash } from "@ember/helper";
|
||||
import { service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import DropdownMenu from "discourse/components/dropdown-menu";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
import {
|
||||
DangerActionListItem,
|
||||
DangerButton,
|
||||
DefaultActionListItem,
|
||||
DefaultButton,
|
||||
PrimaryActionListItem,
|
||||
PrimaryButton,
|
||||
WrappedActionListItem,
|
||||
WrappedButton,
|
||||
} from "admin/components/admin-page-action-button";
|
||||
import DMenu from "float-kit/components/d-menu";
|
||||
|
||||
export default class AdminPageSubheader extends Component {
|
||||
@service site;
|
||||
|
||||
get title() {
|
||||
if (this.args.titleLabelTranslated) {
|
||||
return this.args.titleLabelTranslated;
|
||||
|
@ -29,14 +39,42 @@ export default class AdminPageSubheader extends Component {
|
|||
<div class="admin-page-subheader">
|
||||
<div class="admin-page-subheader__title-row">
|
||||
<h3 class="admin-page-subheader__title">{{this.title}}</h3>
|
||||
<div class="admin-page-subheader__actions">
|
||||
{{yield
|
||||
(hash
|
||||
Primary=PrimaryButton Default=DefaultButton Danger=DangerButton
|
||||
)
|
||||
to="actions"
|
||||
}}
|
||||
</div>
|
||||
{{#if (has-block "actions")}}
|
||||
<div class="admin-page-subheader__actions">
|
||||
{{#if this.site.mobileView}}
|
||||
<DMenu
|
||||
@identifier="admin-page-subheader-mobile-actions"
|
||||
@title={{i18n "more_options"}}
|
||||
@icon="ellipsis-vertical"
|
||||
class="btn-small"
|
||||
>
|
||||
<:content>
|
||||
<DropdownMenu class="admin-page-subheader__mobile-actions">
|
||||
{{yield
|
||||
(hash
|
||||
Primary=PrimaryActionListItem
|
||||
Default=DefaultActionListItem
|
||||
Danger=DangerActionListItem
|
||||
Wrapped=WrappedActionListItem
|
||||
)
|
||||
to="actions"
|
||||
}}
|
||||
</DropdownMenu>
|
||||
</:content>
|
||||
</DMenu>
|
||||
{{else}}
|
||||
{{yield
|
||||
(hash
|
||||
Primary=PrimaryButton
|
||||
Default=DefaultButton
|
||||
Danger=DangerButton
|
||||
Wrapped=WrappedButton
|
||||
)
|
||||
to="actions"
|
||||
}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if this.description}}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { hash } from "@ember/helper";
|
||||
import { service } from "@ember/service";
|
||||
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
||||
import NavItem from "discourse/components/nav-item";
|
||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||
import { headerActionComponentForPlugin } from "discourse/lib/admin-plugin-header-actions";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
import AdminPageHeader from "./admin-page-header";
|
||||
import AdminPluginConfigArea from "./admin-plugin-config-area";
|
||||
|
@ -25,7 +24,11 @@ export default class AdminPluginConfigPage extends Component {
|
|||
}
|
||||
|
||||
get actionsOutletName() {
|
||||
return `admin-plugin-config-page-actions-${this.args.plugin.kebabCaseName}`;
|
||||
return `admin-plugin-config-page-actions-${this.args.plugin.dasherizedName}`;
|
||||
}
|
||||
|
||||
get headerActionComponent() {
|
||||
return headerActionComponentForPlugin(this.args.plugin.dasherizedName);
|
||||
}
|
||||
|
||||
linkText(navLink) {
|
||||
|
@ -42,6 +45,7 @@ export default class AdminPluginConfigPage extends Component {
|
|||
@titleLabelTranslated={{@plugin.nameTitleized}}
|
||||
@descriptionLabelTranslated={{@plugin.about}}
|
||||
@learnMoreUrl={{@plugin.linkUrl}}
|
||||
@headerActionComponent={{this.headerActionComponent}}
|
||||
>
|
||||
<:breadcrumbs>
|
||||
|
||||
|
@ -71,14 +75,6 @@ export default class AdminPluginConfigPage extends Component {
|
|||
{{/each}}
|
||||
{{/if}}
|
||||
</:tabs>
|
||||
<:actions as |actions|>
|
||||
<div class={{this.actionsOutletName}}>
|
||||
<PluginOutlet
|
||||
@name={{this.actionsOutletName}}
|
||||
@outletArgs={{hash plugin=@plugin actions=actions}}
|
||||
/>
|
||||
</div>
|
||||
</:actions>
|
||||
</AdminPageHeader>
|
||||
|
||||
<div class="admin-plugin-config-page__content">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { cached, tracked } from "@glimmer/tracking";
|
||||
import { capitalize } from "@ember/string";
|
||||
import { capitalize, dasherize } from "@ember/string";
|
||||
import { snakeCaseToCamelCase } from "discourse-common/lib/case-converter";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
|
@ -24,8 +24,8 @@ export default class AdminPlugin {
|
|||
return this.name.replaceAll("-", "_");
|
||||
}
|
||||
|
||||
get kebabCaseName() {
|
||||
return this.name.replaceAll(" ", "-").replaceAll("_", "-");
|
||||
get dasherizedName() {
|
||||
return dasherize(this.name);
|
||||
}
|
||||
|
||||
get translatedCategoryName() {
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
<AdminPageSubheader @titleLabel="admin.backups.files_title">
|
||||
<:actions>
|
||||
{{#if this.localBackupStorage}}
|
||||
<UppyBackupUploader
|
||||
@done={{route-action "uploadSuccess"}}
|
||||
@localBackupStorage={{this.localBackupStorage}}
|
||||
/>
|
||||
{{else}}
|
||||
<UppyBackupUploader @done={{route-action "remoteUploadSuccess"}} />
|
||||
{{/if}}
|
||||
<:actions as |actions|>
|
||||
<actions.Wrapped as |wrapped|>
|
||||
{{#if this.localBackupStorage}}
|
||||
<UppyBackupUploader
|
||||
class={{wrapped.buttonClass}}
|
||||
@done={{route-action "uploadSuccess"}}
|
||||
@localBackupStorage={{this.localBackupStorage}}
|
||||
/>
|
||||
{{else}}
|
||||
<UppyBackupUploader
|
||||
class={{wrapped.buttonClass}}
|
||||
@done={{route-action "remoteUploadSuccess"}}
|
||||
/>
|
||||
{{/if}}
|
||||
</actions.Wrapped>
|
||||
</:actions>
|
||||
</AdminPageSubheader>
|
||||
|
||||
|
@ -34,10 +40,9 @@
|
|||
<tr class="backup-item-row" data-backup-filename={{backup.filename}}>
|
||||
<td class="backup-filename">{{backup.filename}}</td>
|
||||
<td class="backup-size">{{human-size backup.size}}</td>
|
||||
<td class="backup-controls">
|
||||
<td class="backup-controls admin-table-row-controls">
|
||||
<DButton
|
||||
@action={{fn this.download backup}}
|
||||
@icon="download"
|
||||
@title="admin.backups.operations.download.title"
|
||||
@label="admin.backups.operations.download.label"
|
||||
class="btn-default btn-small backup-item-row__download"
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
class="btn btn-small btn-primary admin-backups-upload"
|
||||
disabled={{this.uploading}}
|
||||
title={{i18n "admin.backups.upload.title"}}
|
||||
...attributes
|
||||
>
|
||||
{{d-icon "upload"}}{{this.uploadButtonText}}
|
||||
{{this.uploadButtonText}}
|
||||
<input
|
||||
class="hidden-upload-field"
|
||||
disabled={{this.uploading}}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
let pluginHeaderActionComponents = new Map();
|
||||
|
||||
export function registerPluginHeaderActionComponent(pluginId, componentClass) {
|
||||
pluginHeaderActionComponents.set(pluginId, componentClass);
|
||||
}
|
||||
|
||||
export function clearPluginHeaderActionComponents() {
|
||||
pluginHeaderActionComponents = new Map();
|
||||
}
|
||||
|
||||
export function headerActionComponentForPlugin(pluginId) {
|
||||
return pluginHeaderActionComponents.get(pluginId);
|
||||
}
|
|
@ -61,6 +61,7 @@ import {
|
|||
PLUGIN_NAV_MODE_TOP,
|
||||
registerAdminPluginConfigNav,
|
||||
} from "discourse/lib/admin-plugin-config-nav";
|
||||
import { registerPluginHeaderActionComponent } from "discourse/lib/admin-plugin-header-actions";
|
||||
import classPrepend, {
|
||||
withPrependsRolledBack,
|
||||
} from "discourse/lib/class-prepend";
|
||||
|
@ -3252,6 +3253,24 @@ class PluginApi {
|
|||
addLegacyAboutPageStat(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a component class that will be rendered within the AdminPageHeader component
|
||||
* only on plugins using the AdminPluginConfigPage and the new plugin "show" route.
|
||||
*
|
||||
* This component will be passed an `@actions` argument, with Primary, Default, Danger,
|
||||
* and Wrapped keys, which can be used for various different types of buttons (Wrapped
|
||||
* should be used only in very rare scenarios).
|
||||
*
|
||||
* This component would be used for actions that should be present on the entire UI
|
||||
* for that plugin -- one example is "Create export" for chat.
|
||||
*
|
||||
* @param {string} pluginId - The `dasherizedName` of the plugin using this component.
|
||||
* @param {Class} componentClass - The JS class of the component to render.
|
||||
*/
|
||||
registerPluginHeaderActionComponent(pluginId, componentClass) {
|
||||
registerPluginHeaderActionComponent(pluginId, componentClass);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
#deprecatedWidgetOverride(widgetName, override) {
|
||||
// insert here the code to handle widget deprecations, e.g. for the header widgets we used:
|
||||
|
|
|
@ -35,6 +35,7 @@ import { clearHTMLCache } from "discourse/helpers/custom-html";
|
|||
import { resetUsernameDecorators } from "discourse/helpers/decorate-username-selector";
|
||||
import { resetBeforeAuthCompleteCallbacks } from "discourse/instance-initializers/auth-complete";
|
||||
import { resetAdminPluginConfigNav } from "discourse/lib/admin-plugin-config-nav";
|
||||
import { clearPluginHeaderActionComponents } from "discourse/lib/admin-plugin-header-actions";
|
||||
import { rollbackAllPrepends } from "discourse/lib/class-prepend";
|
||||
import { clearPopupMenuOptions } from "discourse/lib/composer/custom-popup-menu-options";
|
||||
import { clearDesktopNotificationHandlers } from "discourse/lib/desktop-notifications";
|
||||
|
@ -251,6 +252,7 @@ export function testCleanup(container, app) {
|
|||
clearAboutPageActivities();
|
||||
clearLegacyAboutPageStats();
|
||||
resetWidgetCleanCallbacks();
|
||||
clearPluginHeaderActionComponents();
|
||||
}
|
||||
|
||||
function cleanupCssGeneratorTags() {
|
||||
|
|
|
@ -2,10 +2,23 @@ import { click, render } from "@ember/test-helpers";
|
|||
import { module, test } from "qunit";
|
||||
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
||||
import NavItem from "discourse/components/nav-item";
|
||||
import { forceMobile } from "discourse/lib/mobile";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
import AdminPageHeader from "admin/components/admin-page-header";
|
||||
|
||||
const AdminPageHeaderActionsTestComponent = <template>
|
||||
<div class="admin-page-header-actions-test-component">
|
||||
<@actions.Default
|
||||
@route="adminBadges.award"
|
||||
@routeModels="new"
|
||||
@icon="upload"
|
||||
@label="admin.badges.mass_award.title"
|
||||
class="award-badge"
|
||||
/>
|
||||
</div>
|
||||
</template>;
|
||||
|
||||
module("Integration | Component | AdminPageHeader", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
|
@ -174,4 +187,60 @@ module("Integration | Component | AdminPageHeader", function (hooks) {
|
|||
await click(".edit-groupings-btn");
|
||||
assert.true(actionCalled);
|
||||
});
|
||||
|
||||
test("@headerActionComponent is rendered with actions arg", async function (assert) {
|
||||
await render(<template>
|
||||
<AdminPageHeader
|
||||
@headerActionComponent={{AdminPageHeaderActionsTestComponent}}
|
||||
/>
|
||||
</template>);
|
||||
|
||||
assert
|
||||
.dom(".admin-page-header-actions-test-component .award-badge")
|
||||
.exists();
|
||||
});
|
||||
});
|
||||
|
||||
module("Integration | Component | AdminPageHeader | Mobile", function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
forceMobile();
|
||||
});
|
||||
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("action buttons become a dropdown on mobile", async function (assert) {
|
||||
await render(<template>
|
||||
<AdminPageHeader>
|
||||
<:actions as |actions|>
|
||||
<actions.Primary
|
||||
@route="adminBadges.show"
|
||||
@routeModels="new"
|
||||
@icon="plus"
|
||||
@label="admin.badges.new"
|
||||
class="new-badge"
|
||||
/>
|
||||
|
||||
<actions.Default
|
||||
@route="adminBadges.award"
|
||||
@routeModels="new"
|
||||
@icon="upload"
|
||||
@label="admin.badges.mass_award.title"
|
||||
class="award-badge"
|
||||
/>
|
||||
</:actions>
|
||||
</AdminPageHeader>
|
||||
</template>);
|
||||
|
||||
assert
|
||||
.dom(
|
||||
".admin-page-header__actions .fk-d-menu__trigger.admin-page-header-mobile-actions-trigger"
|
||||
)
|
||||
.exists();
|
||||
|
||||
await click(".admin-page-header-mobile-actions-trigger");
|
||||
|
||||
assert
|
||||
.dom(".dropdown-menu.admin-page-header__mobile-actions .new-badge")
|
||||
.exists();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { click, render } from "@ember/test-helpers";
|
||||
import { module, test } from "qunit";
|
||||
import { forceMobile } from "discourse/lib/mobile";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
import AdminPageSubheader from "admin/components/admin-page-subheader";
|
||||
|
@ -127,3 +128,50 @@ module("Integration | Component | AdminPageSubheader", function (hooks) {
|
|||
assert.true(actionCalled);
|
||||
});
|
||||
});
|
||||
|
||||
module(
|
||||
"Integration | Component | AdminPageSubheader | Mobile",
|
||||
function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
forceMobile();
|
||||
});
|
||||
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("action buttons become a dropdown on mobile", async function (assert) {
|
||||
await render(<template>
|
||||
<AdminPageSubheader>
|
||||
<:actions as |actions|>
|
||||
<actions.Primary
|
||||
@route="adminBadges.show"
|
||||
@routeModels="new"
|
||||
@icon="plus"
|
||||
@label="admin.badges.new"
|
||||
class="new-badge"
|
||||
/>
|
||||
|
||||
<actions.Default
|
||||
@route="adminBadges.award"
|
||||
@routeModels="new"
|
||||
@icon="upload"
|
||||
@label="admin.badges.mass_award.title"
|
||||
class="award-badge"
|
||||
/>
|
||||
</:actions>
|
||||
</AdminPageSubheader>
|
||||
</template>);
|
||||
|
||||
assert
|
||||
.dom(
|
||||
".admin-page-subheader .fk-d-menu__trigger.admin-page-subheader-mobile-actions-trigger"
|
||||
)
|
||||
.exists();
|
||||
|
||||
await click(".admin-page-subheader-mobile-actions-trigger");
|
||||
|
||||
assert
|
||||
.dom(".dropdown-menu.admin-page-subheader__mobile-actions .new-badge")
|
||||
.exists();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -6,10 +6,6 @@
|
|||
align-items: stretch;
|
||||
margin-bottom: var(--space-2);
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
h1,
|
||||
h3 {
|
||||
margin: 0;
|
||||
|
@ -58,3 +54,33 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.admin-page-header {
|
||||
&__title-row {
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.admin-page-header__actions {
|
||||
button {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.admin-page-subheader {
|
||||
&__title-row {
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.admin-page-action-list-item {
|
||||
.btn-primary {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,7 +97,6 @@
|
|||
}
|
||||
|
||||
.award-badge {
|
||||
margin: 15px 0 0 15px;
|
||||
float: left;
|
||||
max-width: 70%;
|
||||
|
||||
|
|
|
@ -29,10 +29,11 @@ export default class ChatAdminPluginActions extends Component {
|
|||
}
|
||||
|
||||
<template>
|
||||
<@outletArgs.actions.Primary
|
||||
<@actions.Primary
|
||||
@label="chat.admin.export_messages.create_export"
|
||||
@title="chat.admin.export_messages.create_export"
|
||||
@action={{this.confirmExportMessages}}
|
||||
@icon="right-from-bracket"
|
||||
class="admin-chat-export"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
@title="chat.incoming_webhooks.new"
|
||||
@route="adminPlugins.show.discourse-chat-incoming-webhooks.new"
|
||||
@routeModels="chat"
|
||||
@icon="plus"
|
||||
class="admin-incoming-webhooks-new"
|
||||
/>
|
||||
</:actions>
|
||||
|
|
|
@ -19,10 +19,7 @@ export default {
|
|||
},
|
||||
]);
|
||||
|
||||
api.renderInOutlet(
|
||||
"admin-plugin-config-page-actions-chat",
|
||||
ChatAdminPluginActions
|
||||
);
|
||||
api.registerPluginHeaderActionComponent("chat", ChatAdminPluginActions);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user