feat(ui): customizable sidebar — collapsible, reorderable, toggleable widgets
Each filter group is now a card with a collapse chevron. An Edit mode (pencil) reveals drag handles (@dnd-kit) to reorder and eye toggles to show/hide widgets, plus Reset to defaults. Layout (order/collapsed/hidden) persists to localStorage and the server preferences blob, adopted on login.
This commit is contained in:
parent
a595a33e36
commit
01960d3bb7
5 changed files with 542 additions and 213 deletions
|
|
@ -9,6 +9,12 @@ import {
|
|||
type ThemePrefs,
|
||||
} from "./lib/theme";
|
||||
import { hasFilterParams, paramsToFilters, syncUrl } from "./lib/urlState";
|
||||
import {
|
||||
loadLayout,
|
||||
normalizeLayout,
|
||||
saveLayoutLocal,
|
||||
type SidebarLayout,
|
||||
} from "./lib/sidebarLayout";
|
||||
import Login from "./components/Login";
|
||||
import Header from "./components/Header";
|
||||
import Sidebar from "./components/Sidebar";
|
||||
|
|
@ -47,6 +53,7 @@ export default function App() {
|
|||
const [theme, setThemeState] = useState<ThemePrefs>(() => loadLocalTheme());
|
||||
const [filters, setFiltersState] = useState<FeedFilters>(loadInitialFilters);
|
||||
const [view, setView] = useState<"grid" | "list">("grid");
|
||||
const [sidebarLayout, setSidebarLayoutState] = useState<SidebarLayout>(loadLayout);
|
||||
|
||||
function setFilters(next: FeedFilters) {
|
||||
setFiltersState(next);
|
||||
|
|
@ -54,6 +61,12 @@ export default function App() {
|
|||
syncUrl(next);
|
||||
}
|
||||
|
||||
function setSidebarLayout(next: SidebarLayout) {
|
||||
setSidebarLayoutState(next);
|
||||
saveLayoutLocal(next);
|
||||
api.savePrefs({ sidebarLayout: next }).catch(() => {});
|
||||
}
|
||||
|
||||
useEffect(() => applyTheme(theme), [theme]);
|
||||
|
||||
// Reflect the initial filters (from localStorage or URL) into the address bar
|
||||
|
|
@ -72,6 +85,11 @@ export default function App() {
|
|||
saveLocalTheme(merged);
|
||||
}
|
||||
if (prefs.view === "grid" || prefs.view === "list") setView(prefs.view);
|
||||
if (prefs.sidebarLayout) {
|
||||
const l = normalizeLayout(prefs.sidebarLayout);
|
||||
setSidebarLayoutState(l);
|
||||
saveLayoutLocal(l);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [meQuery.data?.id]);
|
||||
|
||||
|
|
@ -108,7 +126,12 @@ export default function App() {
|
|||
setView={changeView}
|
||||
/>
|
||||
<div className="flex flex-1 min-h-0">
|
||||
<Sidebar filters={filters} setFilters={setFilters} />
|
||||
<Sidebar
|
||||
filters={filters}
|
||||
setFilters={setFilters}
|
||||
layout={sidebarLayout}
|
||||
setLayout={setSidebarLayout}
|
||||
/>
|
||||
<main className="flex-1 min-w-0 overflow-y-auto">
|
||||
<Feed filters={filters} setFilters={setFilters} view={view} />
|
||||
</main>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue