refactor(channels): ⋯ overflow ActionMenu + TAGS-as-trigger (R13 S3)
Declutter the channel-manager tags row: - New reusable ActionMenu (⋯) on the AnchoredPopover platform — a compact overflow menu of one-shot actions with optional pending/disabled + spinner. The two big glass buttons (Read subscriptions, Backfill everything) move into it, freeing the row. - The "YOUR TAGS" caption becomes a "✎ TAGS" trigger button that opens the tag editor directly, retiring the separate "Manage tags" button. Adds i18n (channels.actionsLabel, channels.tags.label; HU+EN). ActionMenu is built to be reused for other overflow menus later. Self-E2E'd: ⋯ opens the two actions, ✎ TAGS opens the manager. gate green (tsc/eslint-0/prettier).
This commit is contained in:
@@ -20,6 +20,7 @@ import { useChannelPriority } from "../lib/useChannelPriority";
|
||||
import ViewSwitcher, { type ViewOption } from "./ViewSwitcher";
|
||||
import ColumnSortControl from "./ColumnSortControl";
|
||||
import FilterChipGroup, { filterChipClass } from "./FilterChipGroup";
|
||||
import ActionMenu, { type ActionMenuItem } from "./ActionMenu";
|
||||
import Pager from "./Pager";
|
||||
import { CHANNEL_LAYOUTS, type ChannelLayout } from "../lib/channelLayout";
|
||||
import { sortRows } from "../lib/columnSort";
|
||||
@@ -347,30 +348,28 @@ export default function Channels() {
|
||||
</div>
|
||||
);
|
||||
|
||||
// Sync + backfill — now share the tags row (right side).
|
||||
// Catalog-wide, occasional actions — tucked into a shared ⋯ overflow menu so they stop crowding the
|
||||
// tags row. Pending mutations keep the item visible but disabled, with a spinning icon.
|
||||
const catalogActions: ActionMenuItem[] = [
|
||||
{
|
||||
id: "sync",
|
||||
label: t("channels.syncSubscriptions"),
|
||||
title: t("channels.syncSubscriptionsHint"),
|
||||
icon: <RefreshCw className={`h-4 w-4 ${syncSubs.isPending ? "animate-spin" : ""}`} />,
|
||||
onSelect: () => syncSubs.mutate(),
|
||||
disabled: syncSubs.isPending,
|
||||
},
|
||||
{
|
||||
id: "backfill",
|
||||
label: t("channels.backfillEverything"),
|
||||
title: t("channels.backfillEverythingHint"),
|
||||
icon: <History className={`h-4 w-4 ${deepAll.isPending ? "animate-pulse" : ""}`} />,
|
||||
onSelect: () => deepAll.mutate(),
|
||||
disabled: deepAll.isPending,
|
||||
},
|
||||
];
|
||||
const actionButtons = (
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<Tooltip side="bottom" hint={t("channels.syncSubscriptionsHint")}>
|
||||
<button
|
||||
onClick={() => syncSubs.mutate()}
|
||||
disabled={syncSubs.isPending}
|
||||
className="glass-card glass-hover flex items-center gap-2 px-3 py-2 rounded-xl text-sm disabled:opacity-50 transition"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${syncSubs.isPending ? "animate-spin" : ""}`} />
|
||||
{t("channels.syncSubscriptions")}
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip side="bottom" hint={t("channels.backfillEverythingHint")}>
|
||||
<button
|
||||
onClick={() => deepAll.mutate()}
|
||||
disabled={deepAll.isPending}
|
||||
className="glass-card glass-hover flex items-center gap-2 px-3 py-2 rounded-xl text-sm disabled:opacity-50 transition"
|
||||
>
|
||||
<History className={`w-4 h-4 ${deepAll.isPending ? "animate-pulse" : ""}`} />
|
||||
{t("channels.backfillEverything")}
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<ActionMenu ariaLabel={t("channels.actionsLabel")} items={catalogActions} />
|
||||
);
|
||||
|
||||
// Table / cards density — shared by the subscribed + discovery tables (blocked is a chip list, no
|
||||
@@ -465,10 +464,16 @@ export default function Channels() {
|
||||
search lives in the shared top SearchBar, tag create/delete in the manager. */}
|
||||
<div className="flex items-center justify-between gap-4 flex-wrap">
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
{/* The "TAGS" label IS the tags-editor trigger (pencil), replacing the separate
|
||||
"Manage tags" button; the tooltip explains what tags are + that it's clickable. */}
|
||||
<Tooltip hint={t("channels.tags.yourTagsHint")}>
|
||||
<span className="text-xs uppercase tracking-wide text-muted mr-1 cursor-help underline decoration-dotted decoration-muted/40 underline-offset-4">
|
||||
{t("channels.tags.yourTags")}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setTagManagerOpen(true)}
|
||||
className="mr-1 inline-flex items-center gap-1.5 rounded-lg border border-border px-2 py-1 text-xs uppercase tracking-wide text-muted transition hover:border-accent hover:text-accent"
|
||||
>
|
||||
<Pencil className="h-3 w-3" />
|
||||
{t("channels.tags.label")}
|
||||
</button>
|
||||
</Tooltip>
|
||||
{userTags.map((tg) => (
|
||||
<button
|
||||
@@ -488,13 +493,6 @@ export default function Channels() {
|
||||
{t("datatable.clear")}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setTagManagerOpen(true)}
|
||||
className="inline-flex items-center gap-1 text-xs px-2 py-1 rounded-lg border border-dashed border-border text-muted hover:text-accent hover:border-accent transition"
|
||||
>
|
||||
<Pencil className="w-3 h-3" />
|
||||
{t("channels.tags.manage")}
|
||||
</button>
|
||||
</div>
|
||||
{actionButtons}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user