From 6ae9325a62773863b2dffe169e9d64e94c5e289e Mon Sep 17 00:00:00 2001 From: Jordan Vidrine <30537603+jordanvidrine@users.noreply.github.com> Date: Tue, 11 Nov 2025 13:58:54 -0600 Subject: [PATCH] UX: Migrate core likes post action menu to show likes with DMenu (#34265) --- .../stylesheets/common/base/topic-post.scss | 86 ++++++++-- .../discourse/app/components/post/menu.gjs | 18 +- .../app/components/post/menu/buttons/like.gjs | 90 ++-------- .../components/post/menu/liked-users-list.gjs | 155 ++++++++++++++++++ .../tests/acceptance/post-controls-test.js | 27 +-- spec/system/page_objects/pages/topic.rb | 7 +- spec/system/post_menu_spec.rb | 10 +- 7 files changed, 249 insertions(+), 144 deletions(-) create mode 100644 frontend/discourse/app/components/post/menu/liked-users-list.gjs diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss index 1e43d89c3e7..3946f09e749 100644 --- a/app/assets/stylesheets/common/base/topic-post.scss +++ b/app/assets/stylesheets/common/base/topic-post.scss @@ -337,6 +337,15 @@ nav.post-controls { text-align: right; margin-left: auto; + .post-action-menu__double-button { + .discourse-no-touch & { + &:not(.has-liked):hover { + --d-post-control-background--hover: var(--love-low); + --d-button-flat-icon-color--hover: var(--love); + } + } + } + // Some buttons can be doubled up, like likes or flags .double-button { display: inline-flex; @@ -370,28 +379,11 @@ nav.post-controls { border-radius: var(--d-post-control-border-radius); } - &.like { - // Like button with 0 likes - &:hover { - .d-icon { - color: var(--love); - } - } - } - &.has-like { // Like button after I've liked .d-icon { color: var(--love); } - - .discourse-no-touch & { - &:hover { - .d-icon { - color: var(--primary-medium); - } - } - } } &.button-count { @@ -400,6 +392,10 @@ nav.post-controls { padding-right: 0; } + &.my-likes { + flex-direction: row-reverse; + } + + .toggle-like { // Like button when like count is present padding-left: 0.45em; @@ -1732,3 +1728,59 @@ html.discourse-no-touch .fullscreen-table-wrapper:hover { opacity ease-out 1s; } } + +.liked-users-list { + display: flex; + flex-direction: column; + gap: var(--space-1); + + &__list { + margin: 0; + list-style: none; + display: flex; + justify-content: end; + gap: var(--space-1); + + // avatar width + gap * (max amount of users per row + 1) + max-width: calc((var(--liked-users-list-avatar-size) + var(--space-1)) * 9); + flex-wrap: wrap; + } + + &__item, + &__item .avatar { + width: var(--liked-users-list-avatar-size); + height: var(--liked-users-list-avatar-size); + } + + &__container { + --liked-users-list-avatar-size: 22px; + padding: var(--space-2); + display: flex; + align-items: flex-start; + gap: var(--space-2); + } + + &__count { + display: flex; + gap: var(--space-2); + color: var(--primary-medium); + font-weight: 300; + align-items: center; + } + + &__count-icon { + color: var(--love); + } + + &__more { + font-size: var(--font-down-1); + color: var(--primary-medium); + } + + &__more-button.btn-flat.btn { + padding: 0; + width: var(--liked-users-list-avatar-size); + border-radius: var(--d-button-border-radius); + height: var(--liked-users-list-avatar-size); + } +} diff --git a/frontend/discourse/app/components/post/menu.gjs b/frontend/discourse/app/components/post/menu.gjs index 6284343d8e3..e7fec96232a 100644 --- a/frontend/discourse/app/components/post/menu.gjs +++ b/frontend/discourse/app/components/post/menu.gjs @@ -5,7 +5,7 @@ import { action } from "@ember/object"; import { getOwner } from "@ember/owner"; import { service } from "@ember/service"; import { isEmpty, isPresent } from "@ember/utils"; -import { and, eq } from "truth-helpers"; +import { and } from "truth-helpers"; import AdminPostMenu from "discourse/components/admin-post-menu"; import DeleteTopicDisallowedModal from "discourse/components/modal/delete-topic-disallowed"; import PluginOutlet from "discourse/components/plugin-outlet"; @@ -672,22 +672,6 @@ export default class PostMenu extends Component { }} @users={{this.readers}} /> - {{#if (this.showMoreButton.shouldRender (hash post=this.post state=this.state) diff --git a/frontend/discourse/app/components/post/menu/buttons/like.gjs b/frontend/discourse/app/components/post/menu/buttons/like.gjs index 3010145a4c5..3c233e04870 100644 --- a/frontend/discourse/app/components/post/menu/buttons/like.gjs +++ b/frontend/discourse/app/components/post/menu/buttons/like.gjs @@ -4,10 +4,9 @@ import { action } from "@ember/object"; import { service } from "@ember/service"; import DButton from "discourse/components/d-button"; import concatClass from "discourse/helpers/concat-class"; -import icon from "discourse/helpers/d-icon"; import discourseLater from "discourse/lib/later"; import { applyValueTransformer } from "discourse/lib/transformer"; -import { i18n } from "discourse-i18n"; +import LikedUsersList from "../liked-users-list"; export default class PostMenuLikeButton extends Component { static shouldRender(args) { @@ -61,13 +60,16 @@ export default class PostMenuLikeButton extends Component { } - -class LikeCount extends Component { - get icon() { - if (!this.args.post.showLike) { - return this.args.post.yours ? "d-liked" : "d-unliked"; - } - - if (this.args.post.yours) { - return "d-liked"; - } - } - - get translatedTitle() { - let title; - - if (this.args.post.liked) { - title = - this.args.post.likeCount === 1 - ? "post.has_likes_title_only_you" - : "post.has_likes_title_you"; - } else { - title = "post.has_likes_title"; - } - - return i18n(title, { - count: this.args.post.liked - ? this.args.post.likeCount - 1 - : this.args.post.likeCount, - }); - } - - -} diff --git a/frontend/discourse/app/components/post/menu/liked-users-list.gjs b/frontend/discourse/app/components/post/menu/liked-users-list.gjs new file mode 100644 index 00000000000..a8438cbfc20 --- /dev/null +++ b/frontend/discourse/app/components/post/menu/liked-users-list.gjs @@ -0,0 +1,155 @@ +import Component from "@glimmer/component"; +import { tracked } from "@glimmer/tracking"; +import { action } from "@ember/object"; +import { service } from "@ember/service"; +import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner"; +import DButton from "discourse/components/d-button"; +import UserAvatar from "discourse/components/user-avatar"; +import concatClass from "discourse/helpers/concat-class"; +import icon from "discourse/helpers/d-icon"; +import { i18n } from "discourse-i18n"; +import DMenu from "float-kit/components/d-menu"; + +const LIKE_ACTION = 2; // The action type ID for "like" in Discourse +const DISPLAY_MAX_USERS = 8; // will show X users, then a button to show one more row of X; + +export default class LikedUsersList extends Component { + @service store; + + @tracked likedUsers; + @tracked loadingLikedUsers = false; + @tracked slicedUsersVisible = false; + + @action + async fetchLikedUsers() { + if (this.loadingLikedUsers) { + return; + } + + this.loadingLikedUsers = true; + + try { + this.likedUsers = await this.store.find("post-action-user", { + id: this.args.post.id, + post_action_type_id: LIKE_ACTION, + }); + } finally { + this.loadingLikedUsers = false; + } + } + + @action + toggleSlicedUsersVisiblity() { + this.slicedUsersVisible = !this.slicedUsersVisible; + } + + get icon() { + if (!this.args.post.showLike) { + return this.args.post.yours ? "d-liked" : "d-unliked"; + } + + if (this.args.post.yours) { + return "d-liked"; + } + } + + get truncatedUsers() { + return this.likedUsers?.content.slice(0, DISPLAY_MAX_USERS); + } + + get slicedUsers() { + return this.likedUsers?.content.slice( + DISPLAY_MAX_USERS, + DISPLAY_MAX_USERS * 2 + ); + } + + get hiddenUserCount() { + return ( + this.likedUsers?.length - + (this.truncatedUsers.length + this.slicedUsers.length) + ); + } + + get toggleSlicedUsersVisiblityIcon() { + return this.slicedUsersVisible ? "angle-up" : "angle-down"; + } + + +} diff --git a/frontend/discourse/tests/acceptance/post-controls-test.js b/frontend/discourse/tests/acceptance/post-controls-test.js index c7f22600fae..dc4c95a5c17 100644 --- a/frontend/discourse/tests/acceptance/post-controls-test.js +++ b/frontend/discourse/tests/acceptance/post-controls-test.js @@ -4,32 +4,17 @@ import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { i18n } from "discourse-i18n"; acceptance(`Post controls`, function () { - test("accessibility of the likes list below the post", async function (assert) { + test("menu of like count is shown when clicking on like count", async function (assert) { await visit("/t/internationalization-localization/280"); - assert - .dom("#post_2 button.like-count") - .hasAria("pressed", "false", "show likes button isn't pressed"); + assert.dom("#post_2 .button-count").exists("like count button exists"); - await click("#post_2 button.like-count"); - assert - .dom("#post_2 button.like-count") - .hasAria("pressed", "true", "show likes button is now pressed"); + await click("#post_2 .button-count"); + // Assert that the liked users list container appears assert - .dom("#post_2 .small-user-list.who-liked .small-user-list-content") - .hasAttribute("role", "list", "likes container has list role"); - - assert - .dom("#post_2 .small-user-list.who-liked a.trigger-user-card") - .exists("avatars are rendered"); - - assert - .dom("#post_2 .small-user-list.who-liked a.trigger-user-card") - .hasAria("hidden", "false", "avatars are not aria-hidden"); - assert - .dom("#post_2 .small-user-list.who-liked a.trigger-user-card") - .hasAttribute("role", "listitem", "avatars have listitem role"); + .dom(".liked-users-list__container") + .exists("liked users list container appears"); }); test("accessibility of the embedded replies below the post", async function (assert) { diff --git a/spec/system/page_objects/pages/topic.rb b/spec/system/page_objects/pages/topic.rb index 84f58b5e854..e1db87a13ca 100644 --- a/spec/system/page_objects/pages/topic.rb +++ b/spec/system/page_objects/pages/topic.rb @@ -130,9 +130,10 @@ module PageObjects def has_who_liked_on_post?(post, count: nil) if count return( - within_post(post) do - has_css?(".who-liked.--expanded a.trigger-user-card", count: count) - end + has_css?( + ".liked-users-list__container .liked-users-list__item a.trigger-user-card", + count: count, + ) ) end diff --git a/spec/system/post_menu_spec.rb b/spec/system/post_menu_spec.rb index 12d49164c71..07978658a8f 100644 --- a/spec/system/post_menu_spec.rb +++ b/spec/system/post_menu_spec.rb @@ -487,21 +487,15 @@ describe "Post menu", type: :system do expect(topic_page).to have_no_post_action_button(post2, :like_count) end - it "toggles the users who liked when clicking on the like count" do + it "shows the users who liked when clicking on the like count" do PostActionCreator.like(user, post) PostActionCreator.like(admin, post) topic_page.visit_topic(post.topic) - expect(topic_page).to have_no_who_liked_on_post(post) - - # toggle users who liked on + # show users who liked on topic_page.click_post_action_button(post, :like_count) expect(topic_page).to have_who_liked_on_post(post, count: 2) - - # toggle users who liked off - topic_page.click_post_action_button(post, :like_count) - expect(topic_page).to have_no_who_liked_on_post(post) end end