ui: persist issue properties pane visibility in localStorage
Store the open/closed state of the properties panel in localStorage so it persists across navigations, issues, and companies. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
export function Layout() {
|
export function Layout() {
|
||||||
const { sidebarOpen, setSidebarOpen, toggleSidebar, isMobile } = useSidebar();
|
const { sidebarOpen, setSidebarOpen, toggleSidebar, isMobile } = useSidebar();
|
||||||
const { openNewIssue, openOnboarding } = useDialog();
|
const { openNewIssue, openOnboarding } = useDialog();
|
||||||
const { panelContent, closePanel } = usePanel();
|
const { togglePanelVisible } = usePanel();
|
||||||
const { companies, loading: companiesLoading, selectedCompanyId, setSelectedCompanyId } = useCompany();
|
const { companies, loading: companiesLoading, selectedCompanyId, setSelectedCompanyId } = useCompany();
|
||||||
const { theme, toggleTheme } = useTheme();
|
const { theme, toggleTheme } = useTheme();
|
||||||
const { companyPrefix } = useParams<{ companyPrefix: string }>();
|
const { companyPrefix } = useParams<{ companyPrefix: string }>();
|
||||||
@@ -88,9 +88,7 @@ export function Layout() {
|
|||||||
setSelectedCompanyId,
|
setSelectedCompanyId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const togglePanel = useCallback(() => {
|
const togglePanel = togglePanelVisible;
|
||||||
if (panelContent) closePanel();
|
|
||||||
}, [panelContent, closePanel]);
|
|
||||||
|
|
||||||
// Cmd+1..9 to switch companies
|
// Cmd+1..9 to switch companies
|
||||||
const switchCompany = useCallback(
|
const switchCompany = useCallback(
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
export function PropertiesPanel() {
|
export function PropertiesPanel() {
|
||||||
const { panelContent, closePanel } = usePanel();
|
const { panelContent, panelVisible, setPanelVisible } = usePanel();
|
||||||
|
|
||||||
if (!panelContent) return null;
|
if (!panelContent || !panelVisible) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="hidden md:flex w-80 border-l border-border bg-card flex-col shrink-0">
|
<aside className="hidden md:flex w-80 border-l border-border bg-card flex-col shrink-0">
|
||||||
<div className="flex items-center justify-between px-4 py-2 border-b border-border">
|
<div className="flex items-center justify-between px-4 py-2 border-b border-border">
|
||||||
<span className="text-sm font-medium">Properties</span>
|
<span className="text-sm font-medium">Properties</span>
|
||||||
<Button variant="ghost" size="icon-xs" onClick={closePanel}>
|
<Button variant="ghost" size="icon-xs" onClick={() => setPanelVisible(false)}>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,38 @@
|
|||||||
import { createContext, useCallback, useContext, useState, type ReactNode } from "react";
|
import { createContext, useCallback, useContext, useState, type ReactNode } from "react";
|
||||||
|
|
||||||
|
const STORAGE_KEY = "paperclip:panel-visible";
|
||||||
|
|
||||||
interface PanelContextValue {
|
interface PanelContextValue {
|
||||||
panelContent: ReactNode | null;
|
panelContent: ReactNode | null;
|
||||||
|
panelVisible: boolean;
|
||||||
openPanel: (content: ReactNode) => void;
|
openPanel: (content: ReactNode) => void;
|
||||||
closePanel: () => void;
|
closePanel: () => void;
|
||||||
|
setPanelVisible: (visible: boolean) => void;
|
||||||
|
togglePanelVisible: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PanelContext = createContext<PanelContextValue | null>(null);
|
const PanelContext = createContext<PanelContextValue | null>(null);
|
||||||
|
|
||||||
|
function readPreference(): boolean {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(STORAGE_KEY);
|
||||||
|
return raw === null ? true : raw === "true";
|
||||||
|
} catch {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writePreference(visible: boolean) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(STORAGE_KEY, String(visible));
|
||||||
|
} catch {
|
||||||
|
// Ignore storage failures.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function PanelProvider({ children }: { children: ReactNode }) {
|
export function PanelProvider({ children }: { children: ReactNode }) {
|
||||||
const [panelContent, setPanelContent] = useState<ReactNode | null>(null);
|
const [panelContent, setPanelContent] = useState<ReactNode | null>(null);
|
||||||
|
const [panelVisible, setPanelVisibleState] = useState(readPreference);
|
||||||
|
|
||||||
const openPanel = useCallback((content: ReactNode) => {
|
const openPanel = useCallback((content: ReactNode) => {
|
||||||
setPanelContent(content);
|
setPanelContent(content);
|
||||||
@@ -19,8 +42,23 @@ export function PanelProvider({ children }: { children: ReactNode }) {
|
|||||||
setPanelContent(null);
|
setPanelContent(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const setPanelVisible = useCallback((visible: boolean) => {
|
||||||
|
setPanelVisibleState(visible);
|
||||||
|
writePreference(visible);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const togglePanelVisible = useCallback(() => {
|
||||||
|
setPanelVisibleState((prev) => {
|
||||||
|
const next = !prev;
|
||||||
|
writePreference(next);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelContext.Provider value={{ panelContent, openPanel, closePanel }}>
|
<PanelContext.Provider
|
||||||
|
value={{ panelContent, panelVisible, openPanel, closePanel, setPanelVisible, togglePanelVisible }}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</PanelContext.Provider>
|
</PanelContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user