refactor(ui): shared SegmentedToggle for MATCH controls (R13 S2c step 1)
New source-agnostic SegmentedToggle primitive (rounded-full pill track, one active option, aria-pressed drives the paint via the .pill recipe). Migrates the three hand-rolled MATCH segments onto it: - feed tag any/all "Match" toggle (Sidebar) - feed Mine/Library scope bar (Sidebar; fullWidth + icons + per-option tips) - Plex any/all combine toggle (PlexSidebar MatchToggle is now a thin binding) Caption mode (justify-between + label) covers the match toggles; fullWidth + size="md" covers the scope bar. Adds SegmentedToggle.dom.test.tsx (6 tests). Part of the app-wide single-select control unification; branch not merged.
This commit is contained in:
@@ -15,6 +15,7 @@ import SavedViewsWidget from "./SavedViewsWidget";
|
||||
import SidePanel from "./SidePanel";
|
||||
import PanelGroups, { type PanelItem } from "./PanelGroups";
|
||||
import ChannelPicker from "./ChannelPicker";
|
||||
import SegmentedToggle from "./SegmentedToggle";
|
||||
import { useFeedFilters, useFeedFiltersActions } from "./FeedFiltersProvider";
|
||||
import { useChannelsActions } from "./ChannelsProvider";
|
||||
import { useMe } from "../lib/useMe";
|
||||
@@ -255,30 +256,17 @@ export default function Sidebar({
|
||||
const chips = visibleChips(topics);
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="flex items-center justify-between mb-1.5"
|
||||
<SegmentedToggle
|
||||
label={t("sidebar.match")}
|
||||
title={t("sidebar.tagModeTooltip")}
|
||||
>
|
||||
<span className="text-3xs uppercase tracking-wide text-muted">
|
||||
{t("sidebar.match")}
|
||||
</span>
|
||||
<div
|
||||
className="flex items-center rounded-full border border-border bg-card p-0.5 text-3xs"
|
||||
role="group"
|
||||
aria-label={t("sidebar.match")}
|
||||
>
|
||||
{(["or", "and"] as const).map((m) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setFilters({ ...filters, tagMode: m })}
|
||||
aria-pressed={filters.tagMode === m}
|
||||
className="pill px-2 py-0.5 rounded-full"
|
||||
>
|
||||
{m === "or" ? t("sidebar.any") : t("sidebar.all")}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
ariaLabel={t("sidebar.match")}
|
||||
options={[
|
||||
{ value: "or", label: t("sidebar.any") },
|
||||
{ value: "and", label: t("sidebar.all") },
|
||||
]}
|
||||
value={filters.tagMode}
|
||||
onChange={(tagMode) => setFilters({ ...filters, tagMode })}
|
||||
/>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{chips.map((tg) => (
|
||||
<TagChip
|
||||
@@ -368,24 +356,27 @@ export default function Sidebar({
|
||||
actions={actions}
|
||||
>
|
||||
{/* Scope: your own subscriptions (Mine) vs the shared Library. */}
|
||||
<div
|
||||
className="flex items-center rounded-full border border-border bg-card p-0.5 text-xs"
|
||||
role="group"
|
||||
aria-label={t("header.scope.label")}
|
||||
>
|
||||
{(["my", "all"] as const).map((s) => (
|
||||
<button
|
||||
key={s}
|
||||
onClick={() => setFilters({ ...filters, scope: s })}
|
||||
title={t("header.scope." + s + "Tip")}
|
||||
aria-pressed={filters.scope === s}
|
||||
className="pill flex-1 inline-flex items-center justify-center gap-1 px-2.5 py-1 rounded-full"
|
||||
>
|
||||
{s === "my" ? <User className="w-3.5 h-3.5" /> : <Library className="w-3.5 h-3.5" />}
|
||||
<span>{t("header.scope." + s)}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<SegmentedToggle
|
||||
ariaLabel={t("header.scope.label")}
|
||||
fullWidth
|
||||
size="md"
|
||||
options={[
|
||||
{
|
||||
value: "my",
|
||||
label: t("header.scope.my"),
|
||||
icon: <User className="w-3.5 h-3.5" />,
|
||||
title: t("header.scope.myTip"),
|
||||
},
|
||||
{
|
||||
value: "all",
|
||||
label: t("header.scope.all"),
|
||||
icon: <Library className="w-3.5 h-3.5" />,
|
||||
title: t("header.scope.allTip"),
|
||||
},
|
||||
]}
|
||||
value={filters.scope}
|
||||
onChange={(scope) => setFilters({ ...filters, scope })}
|
||||
/>
|
||||
|
||||
<PanelGroups items={items} layout={layout} setLayout={setLayout} editing={editing} />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user