From 6338287e89f9f300371cae19db2cf043ed672337 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Thu, 9 Feb 2023 11:36:27 -0800 Subject: [PATCH] UX: Easily toggle badges in admin badge list (#20225) --- .../addon/controllers/admin-badges/show.js | 16 +++- .../addon/templates/admin-badges/show.hbs | 15 ++-- .../app/components/d-toggle-switch.hbs | 20 +++++ .../app/components/d-toggle-switch.js | 15 ++++ .../components/d-toggle-switch-test.js | 66 +++++++++++++++ .../stylesheets/common/components/_index.scss | 1 + .../common/components/d-toggle-switch.scss | 82 +++++++++++++++++++ app/controllers/admin/badges_controller.rb | 2 +- config/locales/client.en.yml | 3 +- .../javascripts/discourse/lib/dummy-data.js | 2 + .../templates/styleguide/atoms/02-buttons.hbs | 10 +++ 11 files changed, 222 insertions(+), 10 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/components/d-toggle-switch.hbs create mode 100644 app/assets/javascripts/discourse/app/components/d-toggle-switch.js create mode 100644 app/assets/javascripts/discourse/tests/integration/components/d-toggle-switch-test.js create mode 100644 app/assets/stylesheets/common/components/d-toggle-switch.scss diff --git a/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js b/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js index 5b098387ef8..a828bbe4d33 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js @@ -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); + } } diff --git a/app/assets/javascripts/admin/addon/templates/admin-badges/show.hbs b/app/assets/javascripts/admin/addon/templates/admin-badges/show.hbs index 4f6619b25e4..d73d41dad94 100644 --- a/app/assets/javascripts/admin/addon/templates/admin-badges/show.hbs +++ b/app/assets/javascripts/admin/addon/templates/admin-badges/show.hbs @@ -1,4 +1,12 @@ +
+ +
+
@@ -253,13 +261,6 @@ {{i18n "admin.badges.show_posts"}}
- -
- -
diff --git a/app/assets/javascripts/discourse/app/components/d-toggle-switch.hbs b/app/assets/javascripts/discourse/app/components/d-toggle-switch.hbs new file mode 100644 index 00000000000..837d2ac2058 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/d-toggle-switch.hbs @@ -0,0 +1,20 @@ +
+ + + {{this.computedLabel}} + +
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/d-toggle-switch.js b/app/assets/javascripts/discourse/app/components/d-toggle-switch.js new file mode 100644 index 00000000000..5dcd2cebb30 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/d-toggle-switch.js @@ -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; + } +} diff --git a/app/assets/javascripts/discourse/tests/integration/components/d-toggle-switch-test.js b/app/assets/javascripts/discourse/tests/integration/components/d-toggle-switch-test.js new file mode 100644 index 00000000000..cd7e82d7360 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/components/d-toggle-switch-test.js @@ -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``); + + 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``); + + 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``); + 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`` + ); + + 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" + ); + }); +}); diff --git a/app/assets/stylesheets/common/components/_index.scss b/app/assets/stylesheets/common/components/_index.scss index 361546e0532..b33546edc10 100644 --- a/app/assets/stylesheets/common/components/_index.scss +++ b/app/assets/stylesheets/common/components/_index.scss @@ -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"; diff --git a/app/assets/stylesheets/common/components/d-toggle-switch.scss b/app/assets/stylesheets/common/components/d-toggle-switch.scss new file mode 100644 index 00000000000..8145a45240c --- /dev/null +++ b/app/assets/stylesheets/common/components/d-toggle-switch.scss @@ -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; + } +} diff --git a/app/controllers/admin/badges_controller.rb b/app/controllers/admin/badges_controller.rb index 4efb86cff89..4723622c25d 100644 --- a/app/controllers/admin/badges_controller.rb +++ b/app/controllers/admin/badges_controller.rb @@ -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, diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 4493f88bbc0..a029f1cab86 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -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 diff --git a/plugins/styleguide/assets/javascripts/discourse/lib/dummy-data.js b/plugins/styleguide/assets/javascripts/discourse/lib/dummy-data.js index 29597f9e579..1416d0b7951 100644 --- a/plugins/styleguide/assets/javascripts/discourse/lib/dummy-data.js +++ b/plugins/styleguide/assets/javascripts/discourse/lib/dummy-data.js @@ -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); diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/02-buttons.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/02-buttons.hbs index bfb9f2fbe90..bca429d5d04 100644 --- a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/02-buttons.hbs +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/02-buttons.hbs @@ -152,4 +152,14 @@ @translatedLabel={{bs.text}} /> {{/each}} + + + + \ No newline at end of file