Task #466: Clean notes page from email-like 4-tab bar
User asked to remove the prominent 4-tab bar (My Notes / Inbox / Sent / Archive) from the notes page because it made the page feel like an email client, and they rarely use the share-between-users feature. Changes (artifacts/tx-os/src/pages/notes.tsx): - Deleted the inline-flex TabButton row that held the four view tabs. - Page now opens directly on the unified "active" feed. - Added an overflow DropdownMenu (⋯ MoreVertical icon) on the end of the controls row containing three items: Inbox (with unread badge), Sent, Archived. Each item calls setView() with the same TabId values, so all downstream branching (data fetching, filtering, composer visibility, folder rail) is unchanged. - Unread inbox badge now appears (a) as a small numeric dot on the overflow trigger when not currently in Inbox view, and (b) inside the dropdown next to the Inbox item — the existing `data-testid="notes-inbox-unread-badge"` is preserved. - Added a small "← My Notes" back button (testid notes-back-to-active) that appears only when view !== "active" so the user can return after drilling into Inbox/Sent/Archived without a visible tab bar. - Removed the now-unused TabButton component definition. Tests (artifacts/tx-os/tests/notes-inbox.spec.mjs): - Replaced the two notes-tab-received / notes-tab-sent clicks with the open-dropdown-then-select-item sequence using the new overflow testids. Validation: - pnpm tsc --noEmit passes for @workspace/tx-os. - Architect review: no severe issues; layout, a11y, RTL, z-index, and state-reset behavior all noted as sound. - The `test` workflow has pre-existing failures (executive-meetings PDF, notes-share, groups-crud) that are explicitly out of scope per the task description and tracked by other open follow-up tasks. No backend, schema, API, or i18n JSON changes were needed: the file uses inline t() defaults, and `notes.tabs.received/sent/archived` keys are reused inside the dropdown.
This commit is contained in:
@@ -470,48 +470,22 @@ export default function NotesPage() {
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex items-center gap-3 flex-wrap">
|
||||
<div className="inline-flex rounded-xl bg-slate-100/70 p-1.5">
|
||||
<TabButton
|
||||
active={view === "active"}
|
||||
{/* Task #466: replaced the four-tab email-style bar with a unified
|
||||
feed. The page opens directly on "My Notes"; Inbox / Sent /
|
||||
Archived now live in the overflow menu (⋯) on the end of the
|
||||
row. A small back button appears when the user has navigated
|
||||
into one of those secondary views so they can return easily. */}
|
||||
{view !== "active" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setView("active")}
|
||||
testId="notes-tab-active"
|
||||
data-testid="notes-back-to-active"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors px-2.5 py-1.5 rounded-lg hover:bg-slate-100/60"
|
||||
>
|
||||
<StickyNote size={18} className="me-2 inline-block" />
|
||||
{t("notes.tabs.active", "My Notes")}
|
||||
</TabButton>
|
||||
<TabButton
|
||||
active={view === "received"}
|
||||
onClick={() => setView("received")}
|
||||
testId="notes-tab-received"
|
||||
>
|
||||
<Inbox size={18} className="me-2 inline-block" />
|
||||
{t("notes.tabs.received", "Inbox")}
|
||||
{unreadInboxCount > 0 && (
|
||||
<span
|
||||
className="ms-2 inline-flex items-center justify-center rounded-full bg-rose-500 text-white text-xs min-w-[22px] h-[22px] px-1.5"
|
||||
data-testid="notes-inbox-unread-badge"
|
||||
>
|
||||
{unreadInboxCount}
|
||||
</span>
|
||||
)}
|
||||
</TabButton>
|
||||
<TabButton
|
||||
active={view === "sent"}
|
||||
onClick={() => setView("sent")}
|
||||
testId="notes-tab-sent"
|
||||
>
|
||||
<Send size={18} className="me-2 inline-block" />
|
||||
{t("notes.tabs.sent", "Sent")}
|
||||
</TabButton>
|
||||
<TabButton
|
||||
active={view === "archived"}
|
||||
onClick={() => setView("archived")}
|
||||
testId="notes-tab-archived"
|
||||
>
|
||||
<Archive size={18} className="me-2 inline-block" />
|
||||
{t("notes.tabs.archived", "Archived")}
|
||||
</TabButton>
|
||||
</div>
|
||||
<ChevronLeft size={16} className="rtl:rotate-180" />
|
||||
<span>{t("notes.tabs.active", "My Notes")}</span>
|
||||
</button>
|
||||
)}
|
||||
{(view === "active" || view === "archived") && labels.length > 0 && (
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
<button
|
||||
@@ -539,9 +513,10 @@ export default function NotesPage() {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="ms-auto flex items-center gap-2">
|
||||
{view === "active" && folderSelection.kind === "all" && (
|
||||
<div
|
||||
className="ms-auto inline-flex rounded-lg border border-slate-300/60 bg-white/60 p-0.5"
|
||||
className="inline-flex rounded-lg border border-slate-300/60 bg-white/60 p-0.5"
|
||||
data-testid="notes-view-toggle"
|
||||
>
|
||||
<button
|
||||
@@ -574,6 +549,61 @@ export default function NotesPage() {
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t("common.more", "More")}
|
||||
title={t("common.more", "More")}
|
||||
data-testid="notes-overflow-menu-trigger"
|
||||
className="relative p-2 rounded-lg border border-slate-300/60 bg-white/60 text-muted-foreground hover:text-foreground hover:bg-white transition-colors"
|
||||
>
|
||||
<MoreVertical size={16} />
|
||||
{unreadInboxCount > 0 && view !== "received" && (
|
||||
<span
|
||||
className="absolute -top-1 -end-1 inline-flex items-center justify-center rounded-full bg-rose-500 text-white text-[10px] min-w-[18px] h-[18px] px-1"
|
||||
data-testid="notes-overflow-unread-dot"
|
||||
>
|
||||
{unreadInboxCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="min-w-[180px]">
|
||||
<DropdownMenuItem
|
||||
onSelect={() => setView("received")}
|
||||
data-testid="notes-overflow-received"
|
||||
>
|
||||
<Inbox className="me-2" />
|
||||
<span className="flex-1">
|
||||
{t("notes.tabs.received", "Inbox")}
|
||||
</span>
|
||||
{unreadInboxCount > 0 && (
|
||||
<span
|
||||
className="ms-auto inline-flex items-center justify-center rounded-full bg-rose-500 text-white text-xs min-w-[20px] h-[20px] px-1.5"
|
||||
data-testid="notes-inbox-unread-badge"
|
||||
>
|
||||
{unreadInboxCount}
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={() => setView("sent")}
|
||||
data-testid="notes-overflow-sent"
|
||||
>
|
||||
<Send className="me-2" />
|
||||
<span>{t("notes.tabs.sent", "Sent")}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={() => setView("archived")}
|
||||
data-testid="notes-overflow-archived"
|
||||
>
|
||||
<Archive className="me-2" />
|
||||
<span>{t("notes.tabs.archived", "Archived")}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -763,32 +793,6 @@ export default function NotesPage() {
|
||||
);
|
||||
}
|
||||
|
||||
function TabButton({
|
||||
active,
|
||||
onClick,
|
||||
children,
|
||||
testId,
|
||||
}: {
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
children: React.ReactNode;
|
||||
testId?: string;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
data-testid={testId}
|
||||
className={`px-4 py-2 text-base rounded-lg transition inline-flex items-center ${
|
||||
active
|
||||
? "bg-white shadow-sm text-foreground"
|
||||
: "text-muted-foreground"
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function Loading() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
|
||||
@@ -158,7 +158,8 @@ test("sender → recipient → reply flow updates Inbox + Sent tabs", async ({
|
||||
await loginViaUi(page, recipient.username);
|
||||
await page.goto("/notes");
|
||||
|
||||
await page.getByTestId("notes-tab-received").click();
|
||||
await page.getByTestId("notes-overflow-menu-trigger").click();
|
||||
await page.getByTestId("notes-overflow-received").click();
|
||||
const inboxCard = page.getByTestId(`received-note-card-${createdNote.id}`);
|
||||
await expect(inboxCard).toBeVisible();
|
||||
await expect(inboxCard).toContainText(noteTitle);
|
||||
@@ -203,7 +204,8 @@ test("sender → recipient → reply flow updates Inbox + Sent tabs", async ({
|
||||
await logout(page);
|
||||
await loginViaUi(page, sender.username);
|
||||
await page.goto("/notes");
|
||||
await page.getByTestId("notes-tab-sent").click();
|
||||
await page.getByTestId("notes-overflow-menu-trigger").click();
|
||||
await page.getByTestId("notes-overflow-sent").click();
|
||||
|
||||
const sentCard = page.getByTestId(`sent-note-card-${createdNote.id}`);
|
||||
await expect(sentCard).toBeVisible();
|
||||
|
||||
Reference in New Issue
Block a user