mirror of
https://github.com/YuzuZensai/VRC-Circle.git
synced 2026-09-13 19:08:52 +00:00
✨ feat: persist favorite folder collapse state across navigation
This commit is contained in:
@@ -1,26 +1,33 @@
|
|||||||
import { useState, type ReactNode } from "react";
|
import { useState, type ReactNode } from "react";
|
||||||
import { ChevronDown, ChevronRight } from "lucide-react";
|
import { ChevronDown, ChevronRight } from "lucide-react";
|
||||||
import { LABEL_HEADING } from "./styles";
|
import { LABEL_HEADING } from "./styles";
|
||||||
|
import { useCollapsed } from "../../store/ui";
|
||||||
|
|
||||||
export function CollapsibleCard({
|
export function CollapsibleCard({
|
||||||
title,
|
title,
|
||||||
count,
|
count,
|
||||||
action,
|
action,
|
||||||
defaultOpen = true,
|
defaultOpen = true,
|
||||||
|
persistKey,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
title: string;
|
title: string;
|
||||||
count?: number | string;
|
count?: number | string;
|
||||||
action?: ReactNode;
|
action?: ReactNode;
|
||||||
defaultOpen?: boolean;
|
defaultOpen?: boolean;
|
||||||
|
persistKey?: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const [open, setOpen] = useState(defaultOpen);
|
const local = useState(defaultOpen);
|
||||||
|
const persisted = useCollapsed(persistKey ?? "", defaultOpen);
|
||||||
|
const [open, toggle] = persistKey
|
||||||
|
? persisted
|
||||||
|
: [local[0], () => local[1]((v) => !v)];
|
||||||
return (
|
return (
|
||||||
<section className="rounded-xl border border-border bg-surface-2 p-5 shadow-sm">
|
<section className="rounded-xl border border-border bg-surface-2 p-5 shadow-sm">
|
||||||
<div className={`flex items-center gap-1.5 ${open ? "mb-3" : ""}`}>
|
<div className={`flex items-center gap-1.5 ${open ? "mb-3" : ""}`}>
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpen((v) => !v)}
|
onClick={toggle}
|
||||||
className={`flex flex-1 items-center gap-1.5 ${LABEL_HEADING}`}
|
className={`flex flex-1 items-center gap-1.5 ${LABEL_HEADING}`}
|
||||||
>
|
>
|
||||||
<span className="text-faint">
|
<span className="text-faint">
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ export function MyFavoriteWorldsSection({ filter }: { filter?: WorldFilter }) {
|
|||||||
{shown.map((folder) => (
|
{shown.map((folder) => (
|
||||||
<CollapsibleCard
|
<CollapsibleCard
|
||||||
key={folder.name}
|
key={folder.name}
|
||||||
|
persistKey={`fav-worlds:${folder.name}`}
|
||||||
title={folder.displayName}
|
title={folder.displayName}
|
||||||
count={`${folder.count} / ${maxPerGroup}`}
|
count={`${folder.count} / ${maxPerGroup}`}
|
||||||
action={
|
action={
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
interface UiState {
|
||||||
|
collapsed: Record<string, boolean>;
|
||||||
|
toggleCollapsed: (key: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUi = create<UiState>((set) => ({
|
||||||
|
collapsed: {},
|
||||||
|
toggleCollapsed: (key) =>
|
||||||
|
set((st) => ({ collapsed: { ...st.collapsed, [key]: !st.collapsed[key] } })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export function useCollapsed(key: string, defaultOpen: boolean): [boolean, () => void] {
|
||||||
|
const open = useUi((s) => (key in s.collapsed ? !s.collapsed[key] : defaultOpen));
|
||||||
|
const toggle = useUi((s) => s.toggleCollapsed);
|
||||||
|
return [open, () => toggle(key)];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user