mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 09:26:54 -06:00
UX: Easily toggle badges in admin badge list (#20225)
This commit is contained in:
parent
58123e8089
commit
6338287e89
@ -1,7 +1,6 @@
|
||||
import Controller, { inject as controller } from "@ember/controller";
|
||||
import { observes } from "discourse-common/utils/decorators";
|
||||
import I18n from "I18n";
|
||||
|
||||
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { next } from "@ember/runloop";
|
||||
@ -25,6 +24,14 @@ export default class AdminBadgesShowController extends Controller.extend(
|
||||
@tracked savingStatus = "";
|
||||
@tracked selectedGraphicType = null;
|
||||
|
||||
get badgeEnabledLabel() {
|
||||
if (this.buffered.get("enabled")) {
|
||||
return "admin.badges.enabled";
|
||||
} else {
|
||||
return "admin.badges.disabled";
|
||||
}
|
||||
}
|
||||
|
||||
get badgeTypes() {
|
||||
return this.adminBadges.badgeTypes;
|
||||
}
|
||||
@ -238,4 +245,11 @@ export default class AdminBadgesShowController extends Controller.extend(
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
toggleBadge() {
|
||||
this.model
|
||||
.save({ enabled: !this.buffered.get("enabled") })
|
||||
.catch(popupAjaxError);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,12 @@
|
||||
<DSection @class="current-badge content-body">
|
||||
<div class="control-group current-badge__toggle-badge">
|
||||
<DToggleSwitch
|
||||
@state={{this.buffered.enabled}}
|
||||
@label={{this.badgeEnabledLabel}}
|
||||
{{on "click" this.toggleBadge}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label for="name">{{i18n "admin.badges.name"}}</label>
|
||||
@ -253,13 +261,6 @@
|
||||
{{i18n "admin.badges.show_posts"}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>
|
||||
<Input @type="checkbox" @checked={{this.buffered.enabled}} />
|
||||
{{i18n "admin.badges.enabled"}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
|
@ -0,0 +1,20 @@
|
||||
<div class="d-toggle-switch">
|
||||
<label class="d-toggle-switch--label">
|
||||
{{! template-lint-disable no-unnecessary-concat }}
|
||||
<button
|
||||
class="d-toggle-switch__checkbox"
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked="{{@state}}"
|
||||
...attributes
|
||||
></button>
|
||||
<span class="d-toggle-switch__checkbox-slider">
|
||||
{{#if @state}}
|
||||
{{d-icon "check"}}
|
||||
{{/if}}
|
||||
</span>
|
||||
</label>
|
||||
<span class="d-toggle-switch__checkbox-label">
|
||||
{{this.computedLabel}}
|
||||
</span>
|
||||
</div>
|
@ -0,0 +1,15 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import I18n from "I18n";
|
||||
|
||||
export default class DiscourseToggleSwitch extends Component {
|
||||
@tracked iconEnabled = true;
|
||||
@tracked showIcon = this.iconEnabled && this.icon;
|
||||
|
||||
get computedLabel() {
|
||||
if (this.args.label) {
|
||||
return I18n.t(this.args.label);
|
||||
}
|
||||
return this.args.translatedLabel;
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
import { module, test } from "qunit";
|
||||
import { render } from "@ember/test-helpers";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
|
||||
import I18n from "I18n";
|
||||
|
||||
module("Integration | Component | d-toggle-switch", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("it renders a toggle button in a disabled state", async function (assert) {
|
||||
this.set("state", false);
|
||||
|
||||
await render(hbs`<DToggleSwitch @state={{this.state}}/>`);
|
||||
|
||||
assert.ok(exists(".d-toggle-switch"), "it renders a toggle switch");
|
||||
assert.strictEqual(
|
||||
query(".d-toggle-switch__checkbox").getAttribute("aria-checked"),
|
||||
"false"
|
||||
);
|
||||
});
|
||||
|
||||
test("it renders a toggle button in a enabled state", async function (assert) {
|
||||
this.set("state", true);
|
||||
|
||||
await render(hbs`<DToggleSwitch @state={{this.state}}/>`);
|
||||
|
||||
assert.ok(exists(".d-toggle-switch"), "it renders a toggle switch");
|
||||
assert.strictEqual(
|
||||
query(".d-toggle-switch__checkbox").getAttribute("aria-checked"),
|
||||
"true"
|
||||
);
|
||||
});
|
||||
|
||||
test("it renders a checkmark icon when enabled", async function (assert) {
|
||||
this.set("state", true);
|
||||
|
||||
await render(hbs`<DToggleSwitch @state={{this.state}}/>`);
|
||||
assert.ok(exists(".d-toggle-switch__checkbox-slider .d-icon-check"));
|
||||
});
|
||||
|
||||
test("it renders a label for the button", async function (assert) {
|
||||
I18n.translations[I18n.locale].js.test = { fooLabel: "foo" };
|
||||
this.set("state", true);
|
||||
await render(
|
||||
hbs`<DToggleSwitch @state={{this.state}}/ @label={{this.label}} @translatedLabel={{this.translatedLabel}} />`
|
||||
);
|
||||
|
||||
this.set("label", "test.fooLabel");
|
||||
|
||||
assert.strictEqual(
|
||||
query(".d-toggle-switch__checkbox-label").innerText,
|
||||
I18n.t("test.fooLabel")
|
||||
);
|
||||
|
||||
this.setProperties({
|
||||
label: null,
|
||||
translatedLabel: "bar",
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
query(".d-toggle-switch__checkbox-label").innerText,
|
||||
"bar"
|
||||
);
|
||||
});
|
||||
});
|
@ -7,6 +7,7 @@
|
||||
@import "conditional-loading-section";
|
||||
@import "convert-to-public-topic-modal";
|
||||
@import "d-tooltip";
|
||||
@import "d-toggle-switch";
|
||||
@import "date-input";
|
||||
@import "date-picker";
|
||||
@import "date-time-input-range";
|
||||
|
@ -0,0 +1,82 @@
|
||||
.d-toggle-switch {
|
||||
--toggle-switch-width: 45px;
|
||||
--toggle-switch-height: 24px;
|
||||
|
||||
&:focus {
|
||||
.d-toggle-switch__checkbox-slider {
|
||||
outline: 2px solid var(--tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.d-toggle-switch__checkbox-slider {
|
||||
background-color: var(--primary-high);
|
||||
}
|
||||
|
||||
.d-toggle-switch__checkbox[aria-checked="true"]
|
||||
+ .d-toggle-switch__checkbox-slider {
|
||||
background-color: var(--tertiary-hover);
|
||||
}
|
||||
}
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&__label {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__checkbox {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&__checkbox[aria-checked="true"] + .d-toggle-switch__checkbox-slider {
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
&__checkbox[aria-checked="true"] + .d-toggle-switch__checkbox-slider::before {
|
||||
left: calc(var(--toggle-switch-width) - 22px);
|
||||
}
|
||||
|
||||
&__checkbox-slider {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
background: var(--primary-low-mid);
|
||||
border-radius: 16px;
|
||||
width: var(--toggle-switch-width);
|
||||
height: var(--toggle-switch-height);
|
||||
margin-right: 0.5em;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
transition: background 0.25s;
|
||||
|
||||
.d-icon {
|
||||
font-size: var(--font-down-1);
|
||||
color: var(--secondary);
|
||||
left: 7px;
|
||||
top: 7px;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
&__checkbox-slider::before,
|
||||
&__checkbox-slider::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__checkbox-slider::before {
|
||||
background: var(--secondary);
|
||||
border-radius: 50%;
|
||||
width: calc(var(--toggle-switch-width) / 2.5);
|
||||
height: calc(var(--toggle-switch-width) / 2.5);
|
||||
top: 3.5px;
|
||||
left: 4px;
|
||||
transition: left 0.25s;
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ class Admin::BadgesController < Admin::AdminController
|
||||
.includes(:badge_grouping)
|
||||
.includes(:badge_type, :image_upload)
|
||||
.references(:badge_grouping)
|
||||
.order("badge_groupings.position, badge_type_id, badges.name")
|
||||
.order("enabled DESC", "badge_groupings.position, badge_type_id, badges.name")
|
||||
.to_a,
|
||||
protected_system_fields: Badge.protected_system_fields,
|
||||
triggers: Badge.trigger_hash,
|
||||
|
@ -6013,7 +6013,8 @@ en:
|
||||
allow_title: Allow badge to be used as a title
|
||||
multiple_grant: Can be granted multiple times
|
||||
listable: Show badge on the public badges page
|
||||
enabled: Enable badge
|
||||
enabled: enabled
|
||||
disabled: disabled
|
||||
icon: Icon
|
||||
image: Image
|
||||
graphic: Graphic
|
||||
|
@ -217,6 +217,8 @@ export function createData(store) {
|
||||
{ disabled: true, text: "disabled" },
|
||||
],
|
||||
|
||||
toggleSwitchState: true,
|
||||
|
||||
navItems: ["latest", "categories", "top"].map((name) => {
|
||||
let item = NavItem.fromText(name);
|
||||
|
||||
|
@ -152,4 +152,14 @@
|
||||
@translatedLabel={{bs.text}}
|
||||
/>
|
||||
{{/each}}
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="DToggleSwitch">
|
||||
<DToggleSwitch
|
||||
@state={{this.dummy.toggleSwitchState}}
|
||||
{{on
|
||||
"click"
|
||||
(fn (mut this.dummy.toggleSwitchState) (not this.dummy.toggleSwitchState))
|
||||
}}
|
||||
/>
|
||||
</StyleguideExample>
|
Loading…
Reference in New Issue
Block a user