UX: Topic cards v2 (#173)

This is the second iteration on the topic card design, in which we bring
back the OP and change the layout.

**Changes**:
* Show OP avatar
* Remove activity avatar and replace by reply icon
* Remove activity icon background
* Move category tag to top left
* Replace long activity copy ("…replied at…") with dot separator
* Change date formatting to `tiny`
* Adjust bulk select styling to new layout + align checkbox to top on
mobile VS keep centred on desktop

* Why: On desktop, the avatar is taking 2 lines (usually) and aligning
the checkbox vertically looks nice. Exception for excerpts, but since
that's only available for pinned topics atm that's a low occurrence. On
mobile, the topic card is 3 lines, with a smaller avatar, which makes
the checkbox "float" around a bit when centred. Hence aligning it to the
top, which for solid avatars aligns nicely too.

* CSS refactor: grid, breakpoints

Messages/bookmarks have not been changed.

| Description | Visual |
|--------|--------|
| Large topic list | ![CleanShot 2025-06-02 at 17 37
35@2x](https://github.com/user-attachments/assets/f232058e-dbf6-4689-abe3-65970464a3e3)|
| Large bulk edit | ![CleanShot 2025-06-02 at 17 37
17@2x](https://github.com/user-attachments/assets/03d86b5f-d62b-4449-9c0b-452ceb8be60c)
|
| Medium topic list | ![CleanShot 2025-06-02 at 17 39
01@2x](https://github.com/user-attachments/assets/80739641-cf8f-4095-938f-9781cac57a7d)
|
| Medium bulk edit | ![CleanShot 2025-06-02 at 17 39
24@2x](https://github.com/user-attachments/assets/6e9045af-734a-4f3f-830a-e03a2f06fdd8)
|
| Small topic list | ![CleanShot 2025-06-02 at 17 39
44@2x](https://github.com/user-attachments/assets/eeca80bc-6863-4026-8f19-b1b5cf6848f3)
|
| Small bulk edit | ![CleanShot 2025-06-02 at 17 40
00@2x](https://github.com/user-attachments/assets/cde7be08-cde2-4ff2-b81b-328d3d7be848)
|
| Messages page (remains unchanged) | ![CleanShot 2025-06-02 at 17 20
37@2x](https://github.com/user-attachments/assets/6512bfe9-a699-4c67-b709-166540ee6fde)
|
| Bookmark page (remains unchanged) | ![CleanShot 2025-06-02 at 17 21
01@2x](https://github.com/user-attachments/assets/f8f43638-e911-49cb-a0d5-ebcb51ccfba4)
|
This commit is contained in:
chapoi
2025-06-04 14:50:41 +02:00
committed by GitHub
parent 0ed3912d63
commit b72a3c1e95
6 changed files with 222 additions and 277 deletions
@@ -1,9 +1,7 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import avatar from "discourse/helpers/avatar";
import concatClass from "discourse/helpers/concat-class"; import concatClass from "discourse/helpers/concat-class";
import icon from "discourse/helpers/d-icon"; import icon from "discourse/helpers/d-icon";
import formatDate from "discourse/helpers/format-date"; import formatDate from "discourse/helpers/format-date";
import { i18n } from "discourse-i18n";
export default class TopicActivityColumn extends Component { export default class TopicActivityColumn extends Component {
get topicUser() { get topicUser() {
@@ -27,37 +25,34 @@ export default class TopicActivityColumn extends Component {
}; };
} else if (this.args.topic.posts_count === 1) { } else if (this.args.topic.posts_count === 1) {
return { return {
user: this.args.topic.creator, user: this.args.topic.firstPosterUser,
username: this.args.topic.creator.username, username: this.args.topic.last_poster_username,
activityText: "user_posted", class: "--created",
class: "--posted",
}; };
} else {
return;
} }
} }
<template> <template>
<span class={{concatClass "topic-activity" this.topicUser.class}}> <span class={{concatClass "topic-activity" this.topicUser.class}}>
<div class="topic-activity__user"> <div class="topic-activity__type">
{{#if this.topicUser.user}} {{#if this.topicUser.user}}
{{avatar this.topicUser.user imageSize="small"}} {{icon "reply"}}
{{else}} {{else}}
{{icon "pencil"}} {{icon "pencil"}}
{{/if}} {{/if}}
</div> </div>
{{#if this.topicUser.username}} {{#if this.topicUser.username}}
<span <span
class="topic-activity__username" class="topic-activity__username"
>{{this.topicUser.username}}</span> >{{this.topicUser.username}}</span>
<span class="dot-separator"></span>
{{/if}} {{/if}}
<div class="topic-activity__reason">
{{i18n (themePrefix this.topicUser.activityText)}}
</div>
<div class="topic-activity__time"> <div class="topic-activity__time">
{{formatDate {{formatDate @topic.bumpedAt leaveAgo="true" format="tiny"}}
@topic.bumpedAt
leaveAgo="true"
format="medium-with-ago-and-on"
}}
</div> </div>
</span> </span>
</template> </template>
@@ -0,0 +1,19 @@
import Component from "@glimmer/component";
import avatar from "discourse/helpers/avatar";
export default class TopicCreatorColumn extends Component {
get topicCreator() {
return {
user: this.args.topic.creator,
username: this.args.topic.creator.username,
class: "--topic-creator",
};
}
<template>
<div class={{this.topicUser.class}}>
{{avatar this.topicCreator.user}}
<span class="topic-creator__username">{{this.topicUser.username}}</span>
</div>
</template>
}
@@ -1,6 +1,7 @@
import { withPluginApi } from "discourse/lib/plugin-api"; import { withPluginApi } from "discourse/lib/plugin-api";
import TopicActivityColumn from "../components/card/topic-activity-column"; import TopicActivityColumn from "../components/card/topic-activity-column";
import TopicCategoryColumn from "../components/card/topic-category-column"; import TopicCategoryColumn from "../components/card/topic-category-column";
import TopicCreatorColumn from "../components/card/topic-creator-column";
import TopicLikesColumn from "../components/card/topic-likes-column"; import TopicLikesColumn from "../components/card/topic-likes-column";
import TopicRepliesColumn from "../components/card/topic-replies-column"; import TopicRepliesColumn from "../components/card/topic-replies-column";
import TopicStatusColumn from "../components/card/topic-status-column"; import TopicStatusColumn from "../components/card/topic-status-column";
@@ -30,6 +31,12 @@ const TopicLikesReplies = <template>
</td> </td>
</template>; </template>;
const TopicCreator = <template>
<td class="topic-creator-data">
<TopicCreatorColumn @topic={{@topic}} />
</td>
</template>;
export default { export default {
name: "topic-list-customizations", name: "topic-list-customizations",
@@ -52,6 +59,10 @@ export default {
item: TopicLikesReplies, item: TopicLikesReplies,
after: "topic-author-avatar", after: "topic-author-avatar",
}); });
columns.add("topic-creator", {
item: TopicCreator,
after: "topic-author-avatar",
});
columns.delete("views"); columns.delete("views");
columns.delete("replies"); columns.delete("replies");
if (!router.currentRouteName.includes("userPrivateMessages")) { if (!router.currentRouteName.includes("userPrivateMessages")) {
+9 -9
View File
@@ -1,8 +1,8 @@
// Fixing bulk select (only needed for desktop) // Fixing bulk select (only needed for desktop)
.bulk-select-enabled { .bulk-select-enabled {
.topic-list-header .topic-list-data.default { .topic-list-header {
position: sticky; position: relative;
top: 10em; top: 0;
} }
.topic-author-avatar-data { .topic-author-avatar-data {
@@ -47,19 +47,19 @@
} }
} }
// bulk select
.bulk-select-topics { .bulk-select-topics {
position: absolute; position: absolute;
right: -1em; right: 0;
background: var(--secondary); background: var(--secondary);
border-radius: 0 0 0 var(--d-border-radius); border-radius: 0 0 0 var(--d-border-radius);
padding: 1em; display: flex;
gap: 0.5rem;
@media screen and (max-width: 1048px) { margin: 0.5rem;
right: 0;
}
button { button {
white-space: nowrap; white-space: nowrap;
margin: 0;
} }
} }
} }
+171 -252
View File
@@ -1,3 +1,5 @@
@use "lib/viewport";
.topic-list .topic-list-item-separator { .topic-list .topic-list-item-separator {
display: none; display: none;
} }
@@ -28,96 +30,93 @@
} }
.topic-list-body .topic-list-item { .topic-list-body .topic-list-item {
position: relative;
background: var(--d-content-background);
box-shadow: 0 0 12px 1px var(--topic-card-shadow);
text-overflow: ellipsis; text-overflow: ellipsis;
padding: 0.75em 1rem; padding: 0.75em 1rem;
border: 1px solid var(--primary-300); border: 1px solid var(--primary-300);
display: grid; display: grid;
grid-template-columns: 20px min-content min-content auto min-content; grid-template-columns: min-content min-content auto min-content;
grid-template-rows: auto minmax(20px, auto);
grid-template-areas: grid-template-areas:
". . . . status" "creator title title status"
"activity . . likes-replies category"; "creator category activity likes-replies";
grid-column-gap: 12px; grid-column-gap: 0.75rem;
grid-row-gap: 8px; grid-row-gap: 0.5rem;
border-radius: var(--d-border-radius); border-radius: var(--d-border-radius);
cursor: pointer; cursor: pointer;
td.main-link .link-top-line { .bulk-select-enabled & {
grid-row: 1/2; grid-template-columns: min-content min-content min-content auto min-content;
grid-column: 1/-1;
font-weight: 500;
}
&.--has-status-card td.main-link .link-top-line {
grid-column: 1/-2;
}
@include breakpoint(extra-large) {
grid-template-areas: grid-template-areas:
". . . . status" "bulk-select creator title title status"
"activity . . likes-replies category"; "bulk-select creator category activity likes-replies";
} }
@include breakpoint(mobile-extra-large) { @include viewport.until(sm) {
td.main-link .link-top-line,
&.--has-status-card td.main-link .link-top-line {
grid-row: 2/3;
grid-column: 1/-1;
}
grid-template-columns: 20px repeat(5, 1fr);
grid-template-rows: auto auto auto;
grid-template-areas: grid-template-areas:
"category category category category category status" "category category category status"
". . . . . ." "creator title title title"
"activity . . . . likes-replies"; "activity activity activity likes-replies";
row-gap: 0.75em;
border: none; border: none;
border-bottom: 1px solid var(--primary-200); border-bottom: 1px solid var(--primary-200);
box-shadow: none; box-shadow: none;
border-radius: 0; border-radius: 0;
} }
&.excerpt-expanded { &:hover {
grid-template-columns: 20px auto auto min-content min-content; .discourse-no-touch & {
grid-template-rows: auto auto auto; border-color: var(--accent-color);
grid-template-areas: background: var(--d-content-background);
". . . . status"
"activity . . . ."
"excerpt excerpt excerpt likes-replies category";
@include breakpoint(extra-large) {
grid-template-areas:
". . . . status"
"activity . . . ."
"excerpt excerpt excerpt likes-replies category";
} }
}
@include breakpoint(mobile-extra-large) { &.selected {
grid-template-rows: auto auto auto; box-shadow:
grid-template-areas: 0 0 0 2px var(--accent-color),
"category category category category status" 0 0 12px 1px var(--topic-card-shadow);
". . . . ." }
"activity . . . likes-replies";
&.excerpt-expanded {
@include viewport.until(sm) {
.topic-excerpt, .topic-excerpt,
.link-bottom-line { .link-bottom-line {
display: none; display: none;
} }
} }
.topic-likes-replies-data { @include viewport.from(sm) {
align-self: flex-end; grid-template-areas:
"creator title title status"
"creator category activity likes-replies "
"creator excerpt excerpt excerpt";
.bulk-select-enabled & {
grid-template-areas:
"bulk-select creator title title status"
"bulk-select creator category activity likes-replies "
" bulk-select creator excerpt excerpt excerpt";
}
} }
.topic-category-data { // when there is enough space, excerpt can be next to likes-replies
align-items: flex-end; @include viewport.from(lg) {
grid-template-areas:
"creator title title status"
"creator category activity ."
"creator excerpt excerpt likes-replies";
} }
}
.badge-category__wrapper { .link-top-line {
height: min-content; grid-area: title;
} font-weight: 500;
display: flex;
align-items: center;
.link-bottom-line { .title {
display: flex; padding: 0;
} }
} }
@@ -126,7 +125,8 @@
td.posters, td.posters,
td.posts, td.posts,
td.views, td.views,
td.activity { td.activity,
td.topic-category-status-data {
display: contents; display: contents;
} }
@@ -134,59 +134,102 @@
padding: 0; padding: 0;
} }
// topic activity, avatar, text // display:nones
td.main-link a.topic-status,
.link-bottom-line,
.badge-notification.new-topic::before {
display: none;
}
.topic-category-data {
grid-area: category;
display: flex;
}
.badge-category__wrapper {
border-radius: var(--d-border-radius);
padding: 0.25em 0.5rem;
background-color: light-dark(
oklch(from var(--category-badge-color) 97% calc(c * 0.3) h),
oklch(from var(--category-badge-color) 45% calc(c * 0.5) h)
);
@include breakpoint(tablet) {
padding: 0.25em 0.5rem;
font-size: var(--font-down-2);
}
.badge-category__name {
color: light-dark(
oklch(from var(--category-badge-color) 20% calc(c * 1) h),
oklch(from var(--category-badge-color) 100% calc(c * 0.9) h)
);
}
}
// OP avatar
.topic-creator-data {
grid-area: creator;
.avatar {
height: 50px;
width: 50px;
border-radius: var(--d-border-radius);
@include viewport.until(sm) {
height: 30px;
width: 30px;
border-radius: 0.25rem;
}
}
}
.dot-separator {
width: 0.25em;
height: 0.25em;
background-color: var(--primary-500);
border-radius: 100%;
margin-inline: 0.25em;
}
// topic activity, icon, text
.topic-activity-data { .topic-activity-data {
@include ellipsis;
grid-area: activity; grid-area: activity;
} }
.topic-activity { .topic-activity {
display: grid; display: flex;
grid-template-columns: 20px auto auto auto;
font-size: var(--font-down-1); font-size: var(--font-down-1);
height: 100%; height: 100%;
align-items: center; align-items: center;
gap: 0.25em; gap: 0.25em;
} }
.topic-activity__user .avatar { .topic-activity__type {
width: 20px; border-radius: 0.25rem;
height: 20px;
border-radius: 4px;
}
.topic-activity__user {
height: 20px;
width: 20px;
border-radius: 4px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background-color: var(--primary-low);
} }
.topic-activity__username { .topic-activity__username {
@include ellipsis;
margin-left: 0.25em; margin-left: 0.25em;
text-wrap: nowrap;
} }
@include breakpoint(mobile-extra-large) { // timestamp
.topic-activity__username { td.activity .post-activity {
display: none; grid-area: activity;
} font-size: var(--font-down-1);
color: var(--primary-500);
.topic-activity__reason { margin-left: auto;
margin-left: 0.25em; padding: 0;
}
}
.topic-activity.--updated .topic-activity__reason {
margin-left: 0.25em;
} }
// status // status
.topic-status-data { .topic-status-data {
grid-area: status; grid-area: status;
position: relative;
} }
.topic-status-card { .topic-status-card {
@@ -194,23 +237,30 @@
margin-left: auto; margin-left: auto;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
gap: 4px; gap: 0.25em;
align-items: center; align-items: center;
padding: 0 6px; padding: 0.2em 0.5rem;
font-size: var(--font-down-2); font-size: var(--font-down-3);
font-weight: 600; font-weight: 600;
border-radius: var(--d-border-radius); border-radius: var(--d-border-radius);
border: 1px solid var(--status-color); border: 1px solid var(--status-color);
color: var(--status-color); color: var(--status-color);
width: min-content; width: min-content;
@include breakpoint("large", min-width) { &.--pinned {
--status-color: var(--primary-500);
}
&.--hot {
--status-color: #e45735;
}
@include viewport.from(lg) {
position: absolute; position: absolute;
right: 0; right: 1rem;
top: -20px; top: 0;
transform: translateY(-45%);
background-color: var(--d-content-background); background-color: var(--d-content-background);
height: 20px;
font-size: var(--font-down-3);
} }
svg { svg {
@@ -219,31 +269,15 @@
} }
} }
.topic-status-card.--pinned {
--status-color: var(--primary-500);
}
.topic-status-card.--hot {
--status-color: #e45735;
}
.link-top-line .event-date { .link-top-line .event-date {
margin-left: 0.5em; margin-left: 0.5em;
font-size: var(--font-down-3); font-size: var(--font-down-3);
} }
td.main-link a.topic-status {
display: none;
}
.topic-list-data { .topic-list-data {
padding: 0; padding: 0;
} }
td.main-link .link-top-line a.raw-topic-link {
padding: 0;
}
.topic-post-badges .badge-notification.unread-posts, .topic-post-badges .badge-notification.unread-posts,
.topic-post-badges .badge-notification.new-topic { .topic-post-badges .badge-notification.new-topic {
background-color: var(--tertiary); background-color: var(--tertiary);
@@ -256,31 +290,13 @@
min-width: unset; min-width: unset;
} }
.badge-notification.new-topic::before {
display: none;
}
// timestamp
td.activity .post-activity {
grid-area: activity;
font-size: var(--font-down-1);
color: var(--primary-500);
margin-left: auto;
padding: 0;
}
.link-bottom-line {
display: none;
}
// metadata
// metadata - excerpt // metadata - excerpt
.topic-excerpt, .topic-excerpt {
td.main-link .link-bottom-line { grid-area: excerpt;
grid-row: 3 / -1;
grid-column: 1 / -3;
margin: 0; margin: 0;
padding: 0;
font-size: var(--font-down-2); font-size: var(--font-down-2);
width: 100%;
.excerpt__contents { .excerpt__contents {
color: var(--primary-high); color: var(--primary-high);
@@ -292,42 +308,6 @@
} }
} }
td.topic-category-status-data {
display: contents;
}
td.topic-category-data {
grid-area: category;
display: flex;
justify-content: flex-end;
@include breakpoint(mobile-extra-large) {
justify-content: flex-start;
}
}
td.topic-category-data .badge-category__wrapper,
td.main-link .link-bottom-line .badge-category__wrapper {
border-radius: var(--d-border-radius);
padding: 3px 6px;
background-color: light-dark(
oklch(from var(--category-badge-color) 97% calc(c * 0.3) h),
oklch(from var(--category-badge-color) 45% calc(c * 0.5) h)
);
@include breakpoint(tablet) {
padding: 2px 6px;
font-size: var(--font-down-2);
}
.badge-category__name {
color: light-dark(
oklch(from var(--category-badge-color) 20% calc(c * 1) h),
oklch(from var(--category-badge-color) 100% calc(c * 0.9) h)
);
}
}
td.main-link .discourse-tags { td.main-link .discourse-tags {
display: none; display: none;
} }
@@ -356,28 +336,31 @@
gap: 0.5em; gap: 0.5em;
align-items: center; align-items: center;
color: var(--primary-500); color: var(--primary-500);
font-size: var(--font-down-1-rem);
svg { svg {
color: var(--primary-600); color: var(--primary-600);
} }
} }
}
.topic-list-item { .bulk-select {
background: var(--d-content-background); grid-area: bulk-select;
box-shadow: 0 0 12px 1px var(--topic-card-shadow); padding: 0;
margin: 0;
align-self: center;
justify-self: center;
&:hover { @include viewport.until(sm) {
.discourse-no-touch & { align-self: flex-start;
border-color: var(--accent-color);
background: var(--d-content-background);
} }
}
&.selected { label {
box-shadow: margin: 0;
0 0 0 2px var(--accent-color), }
0 0 12px 1px var(--topic-card-shadow);
&th {
display: none;
}
} }
} }
@@ -619,13 +602,16 @@ body.user-messages-page {
grid-template-rows: auto auto; grid-template-rows: auto auto;
&:hover { &:hover {
background-color: var(--primary-low); .discourse-no-touch & {
border-color: var(--primary-200); background-color: var(--primary-low);
border-color: var(--primary-200);
}
} }
td.topic-category-data, td.topic-category-data,
td.topic-likes-replies-data, td.topic-likes-replies-data,
td.topic-status-data { td.topic-status-data,
td.topic-creator-data {
display: none; display: none;
} }
@@ -661,73 +647,6 @@ body.user-messages-page {
} }
} }
// Bulk select
.bulk-select-enabled .topic-list-body .topic-list-item {
grid-template-areas:
"bulk-select . . . status"
"bulk-select activity activity . category";
@include breakpoint(large) {
grid-template-areas:
"bulk-select . . . status"
"bulk-select activity activity . category";
}
@include breakpoint(mobile-extra-large) {
grid-template-areas:
"bulk-select category . . . status"
"bulk-select . . . . ."
"bulk-select activity activity . . .";
td.main-link .link-top-line,
&.--has-status-card td.main-link .link-top-line {
grid-column: 2/-1;
grid-row: 2;
font-weight: 500;
}
}
td.topic-likes-replies-data {
display: none;
}
td.main-link .link-top-line,
&.--has-status-card td.main-link .link-top-line {
grid-column: 2/-1;
font-weight: 500;
}
.topic-excerpt {
grid-area: excerpt;
margin: 0;
}
&.excerpt-expanded {
grid-template-areas:
"bulk-select . . . status" "bulk-select activity . . ."
"bulk-select excerpt excerpt excerpt category";
@include breakpoint(mobile-extra-large) {
grid-template-areas:
"bulk-select category . . . status"
"bulk-select . . . . ."
"bulk-select activity activity . . .";
}
}
.bulk-select {
grid-area: bulk-select;
padding: 0;
margin: 0;
align-self: center;
justify-self: center;
label {
margin: 0;
}
}
}
.event-date-container { .event-date-container {
display: inline-flex; display: inline-flex;
position: relative; position: relative;
+1
View File
@@ -39,6 +39,7 @@ describe "Horizon theme | High level", type: :system do
"topic-status-data", "topic-status-data",
"topic-category-data", "topic-category-data",
"topic-likes-replies-data", "topic-likes-replies-data",
"topic-creator-data",
"topic-activity-data", "topic-activity-data",
], ],
) )