refactor(ui): close the sort/layout reuse-gaps (R13 S2c step 5)
Two surfaces reinvented controls the app already consolidated: - PlaylistsRail sort (a bespoke <select> + hand-rolled asc/desc flip) now uses the shared SortControl; "custom" is a directionless option (no arrow), the "unsynced first" pin rides the trailing slot as a pill toggle - PlexPlaylistView Accordion/Tree layout (two hand-rolled toggle buttons) now uses the shared ViewSwitcher with a Rows3/ListTree option set Adds aria-label i18n (plex.playlist.layoutLabel; HU+EN), drops the now-unused ArrowUp/ArrowDown + ReactNode imports. Self-E2E'd the rail sort: key switch reveals the arrow, asc<->desc flips, pin toggles, custom hides the arrow. gate green (tsc/eslint-0/vitest 136/knip/prettier). Branch not merged.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState, type ReactNode } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { qk } from "../lib/queryKeys";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
@@ -35,6 +35,7 @@ import { api, type PlexCard } from "../lib/api";
|
||||
import { getAccountRaw, LS, setAccountRaw } from "../lib/storage";
|
||||
import { useConfirm } from "./ConfirmProvider";
|
||||
import { LoadingState, StateMessage } from "./QueryState";
|
||||
import ViewSwitcher, { type ViewOption } from "./ViewSwitcher";
|
||||
|
||||
// A playlist's items, grouped for readability: movies stay standalone, while a run of episodes from the
|
||||
// same show collapses into a season/show group so a whole series doesn't sprawl into an endless flat
|
||||
@@ -499,18 +500,10 @@ export default function PlexPlaylistView({
|
||||
onRemove,
|
||||
};
|
||||
|
||||
const LayoutBtn = ({ v, icon, label }: { v: Layout; icon: ReactNode; label: string }) => (
|
||||
<button
|
||||
onClick={() => setLayout(v)}
|
||||
title={label}
|
||||
aria-label={label}
|
||||
aria-pressed={layout === v}
|
||||
data-soft="true"
|
||||
className={`pill rounded-lg p-2 text-sm ${layout === v ? "" : "glass-card glass-hover text-muted"}`}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
);
|
||||
const layoutOptions: ViewOption<Layout>[] = [
|
||||
{ id: "accordion", label: t("plex.playlist.layoutAccordion"), icon: Rows3 },
|
||||
{ id: "tree", label: t("plex.playlist.layoutTree"), icon: ListTree },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-[90%] max-w-[1100px] p-4">
|
||||
@@ -536,19 +529,13 @@ export default function PlexPlaylistView({
|
||||
) : (
|
||||
<h1 className="min-w-0 flex-1 truncate text-2xl font-bold">{q.data?.title ?? " "}</h1>
|
||||
)}
|
||||
{/* Layout preference: Accordion (A) or Tree (C). */}
|
||||
<div className="flex items-center gap-1">
|
||||
<LayoutBtn
|
||||
v="accordion"
|
||||
icon={<Rows3 className="h-4 w-4" />}
|
||||
label={t("plex.playlist.layoutAccordion")}
|
||||
/>
|
||||
<LayoutBtn
|
||||
v="tree"
|
||||
icon={<ListTree className="h-4 w-4" />}
|
||||
label={t("plex.playlist.layoutTree")}
|
||||
/>
|
||||
</div>
|
||||
{/* Layout preference: Accordion or Tree — the shared list-view switcher. */}
|
||||
<ViewSwitcher
|
||||
value={layout}
|
||||
options={layoutOptions}
|
||||
onChange={setLayout}
|
||||
label={t("plex.playlist.layoutLabel")}
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
setName(q.data?.title ?? "");
|
||||
|
||||
Reference in New Issue
Block a user