UX: Migrate core likes post action menu to show likes with DMenu (#34265)

This commit is contained in:
Jordan Vidrine
2025-11-11 13:58:54 -06:00
committed by GitHub
parent d1001d876f
commit 6ae9325a62
7 changed files with 249 additions and 144 deletions
@@ -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);
}
}
@@ -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}}
/>
<SmallUserList
class="who-liked"
@addSelf={{and @post.liked (eq this.remainingLikedUsers 0)}}
@isVisible={{this.isWhoLikedVisible}}
@count={{if
this.remainingLikedUsers
this.remainingLikedUsers
this.totalLikedUsers
}}
@description={{if
this.remainingLikedUsers
"post.actions.people.like_capped"
"post.actions.people.like"
}}
@users={{this.likedUsers}}
/>
{{#if
(this.showMoreButton.shouldRender
(hash post=this.post state=this.state)
@@ -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 {
<template>
{{#if @post.showLike}}
<div class="double-button">
<LikeCount
...attributes
@action={{@buttonActions.toggleWhoLiked}}
@state={{@state}}
@post={{@post}}
/>
<div
class={{concatClass
"double-button"
(if @post.liked "has-liked" "")
"post-action-menu__double-button"
}}
>
{{#if @post.likeCount}}
<LikedUsersList ...attributes @post={{@post}} />
{{/if}}
<DButton
class={{concatClass
"post-action-menu__like"
@@ -87,76 +89,8 @@ export default class PostMenuLikeButton extends Component {
</div>
{{else}}
<div class="double-button">
<LikeCount
...attributes
@action={{@buttonActions.toggleWhoLiked}}
@state={{@state}}
@post={{@post}}
/>
<LikedUsersList ...attributes @post={{@post}} />
</div>
{{/if}}
</template>
}
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,
});
}
<template>
{{#if @post.likeCount}}
<DButton
class={{concatClass
"post-action-menu__like-count"
"like-count"
"button-count"
"highlight-action"
(if @post.yours "my-likes" "regular-likes")
}}
...attributes
@ariaPressed={{@state.isWhoLikedVisible}}
@translatedAriaLabel={{i18n
"post.sr_post_like_count_button"
count=@post.likeCount
}}
@translatedTitle={{this.translatedTitle}}
@action={{@action}}
>
{{@post.likeCount}}
{{!--
When displayed, the icon on the Like Count button is aligned to the right
To get the desired effect will use the {{yield}} in the DButton component to our advantage
introducing manually the icon after the label
--}}
{{#if this.icon}}
{{~icon this.icon~}}
{{/if}}
</DButton>
{{/if}}
</template>
}
@@ -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";
}
<template>
<DMenu
@modalForMobile={{true}}
@identifier="post-like-users_{{@post.id}}"
@onShow={{this.fetchLikedUsers}}
@triggerClass={{concatClass
"post-action-menu__like-count"
"like-count"
"btn-flat"
"button-count"
"highlight-action"
(if @post.yours "my-likes" "regular-likes")
}}
@icon={{if @post.yours "d-liked" ""}}
@placement="top"
label={{i18n "post.sr_post_like_count_button" count=@post.likeCount}}
id="post-like-users_{{@post.id}}"
>
<:trigger>
{{@post.likeCount}}
</:trigger>
<:content>
<ConditionalLoadingSpinner
@condition={{this.loadingLikedUsers}}
class="liked-users-list__container"
>
<span class="liked-users-list__count">
{{icon "d-liked" class="liked-users-list__count-icon"}}
{{@post.likeCount}}
</span>
<div class="liked-users-list">
<ul class="liked-users-list__list">
{{#each this.truncatedUsers as |user|}}
<li class="liked-users-list__item">
<UserAvatar
class="trigger-user-card"
@user={{user}}
@size="small"
/>
</li>
{{/each}}
{{#if this.slicedUsers}}
<li class="liked-users-list__item">
<DButton
class="liked-users-list__more-button btn-flat"
@icon={{this.toggleSlicedUsersVisiblityIcon}}
@action={{this.toggleSlicedUsersVisiblity}}
/>
</li>
{{/if}}
</ul>
{{#if this.slicedUsersVisible}}
<ul class="liked-users-list__list">
{{#each this.slicedUsers as |user|}}
<li class="liked-users-list__item">
<UserAvatar
class="trigger-user-card"
@user={{user}}
@size="small"
/>
</li>
{{/each}}
</ul>
{{#if this.hiddenUserCount}}
<span class="liked-users-list__more">
{{i18n
"discourse_reactions.state_panel.more_users"
count=this.hiddenUserCount
}}
</span>
{{/if}}
{{/if}}
</div>
</ConditionalLoadingSpinner>
</:content>
</DMenu>
</template>
}
@@ -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) {
+4 -3
View File
@@ -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
+2 -8
View File
@@ -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