(frontend) display unread count into mailbox dropdown

Display the count of unread messages next to the mailbox name
to help to quickly identify mailbox with new unread messages.

Resolve #738

Co-authored-by: Nicolas Aunai <nicolas.aunai@lpp.polytechnique.fr>
This commit is contained in:
jbpenrath
2026-07-06 15:55:50 +02:00
co-authored by Nicolas Aunai
parent 6e077eab89
commit 27b56ee058
4 changed files with 68 additions and 7 deletions
@@ -123,6 +123,8 @@
"{{count}} threads have been unarchived._other": "{{count}} threads have been unarchived.",
"{{count}} threads have been updated._one": "The thread has been updated.",
"{{count}} threads have been updated._other": "{{count}} threads have been updated.",
"{{count}} unread_one": "{{count}} unread",
"{{count}} unread_other": "{{count}} unread",
"{{count}} unread messages_one": "{{count}} unread message",
"{{count}} unread messages_other": "{{count}} unread messages",
"{{count}} unread messages mentioning you_one": "{{count}} unread message mentioning you",
@@ -182,6 +182,9 @@
"{{count}} threads have been updated._one": "La conversation a été mise à jour.",
"{{count}} threads have been updated._many": "{{count}} conversations ont été mises à jour.",
"{{count}} threads have been updated._other": "{{count}} conversations ont été mises à jour.",
"{{count}} unread_one": "{{count}} message non lu",
"{{count}} unread_many": "{{count}} messages non lus",
"{{count}} unread_other": "{{count}} messages non lus",
"{{count}} unread messages_one": "{{count}} message non lu",
"{{count}} unread messages_many": "{{count}} messages non lus",
"{{count}} unread messages_other": "{{count}} messages non lus",
@@ -97,3 +97,42 @@
white-space: nowrap;
}
}
.mailbox-selector__option-label {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: var(--c--globals--spacings--sm);
& > .mailbox-selector__option-name {
font-size: var(--c--globals--font--sizes--sm);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& > .mailbox-selector__option-unread-count {
display: inline-flex;
align-items: center;
flex-shrink: 0;
font-size: var(--c--globals--font--sizes--xs);
color: var(--c--contextuals--content--semantic--neutral--tertiary);
gap: var(--c--globals--spacings--3xs);
line-height: 1;
&:before {
content: '';
display: inline-block;
--size: 0.5rem;
flex-shrink: 0;
width: var(--size);
height: var(--size);
border-radius: 50%;
box-sizing: border-box;
border: 0.5px solid var(--c--contextuals--border--semantic--contextual--primary);
background-color: var(--c--contextuals--background--semantic--brand--primary);
}
}
}
@@ -2,6 +2,7 @@ import { DropdownMenu, UserAvatar } from "@gouvfr-lasuite/ui-kit";
import { ChevronDown } from "@gouvfr-lasuite/ui-kit/icons";
import { Button } from "@gouvfr-lasuite/cunningham-react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Mailbox } from "@/features/api/gen";
import MailboxHelper from "@/features/utils/mailbox-helper";
@@ -30,6 +31,7 @@ export const MailboxSelector = ({
selectedMailbox,
onSelect,
}: MailboxSelectorProps) => {
const { t } = useTranslation();
const [isOpen, setIsOpen] = useState(false);
const label = getMailboxLabel(selectedMailbox);
@@ -66,9 +68,24 @@ export const MailboxSelector = ({
const sortedMailboxes = MailboxHelper.sortByKind(mailboxes);
const options = sortedMailboxes.map((mailbox, index) => ({
label: getMailboxLabel(mailbox),
label: (<div className="mailbox-selector__option-label">
<span className="mailbox-selector__option-name">
{getMailboxLabel(mailbox)}
</span>
{mailbox.id !== selectedMailbox.id && mailbox.count_unread_threads > 0 && (
<span
className="mailbox-selector__option-unread-count"
aria-label={t("{{count}} unread", { count: mailbox.count_unread_threads })}
>
{mailbox.count_unread_threads > 9999 ? "9999+" : mailbox.count_unread_threads}
</span>
)}
</div>) as unknown as string,
subText: mailbox.name?.trim() ? mailbox.email : undefined,
value: mailbox.id,
// The dropdown option type has no right-side slot (label/subText are plain
// strings), so the unread counter piggybacks on the icon ReactNode and is
// moved to the trailing edge in CSS.
icon: (
<span
className="mailbox-selector__option-avatar"