Files
Reactive-Resume/apps/web/src/routes/builder/$resumeId/-store/sidebar.ts
T
Amruth Pillai 50ba37a27f v5.1.0 (#2970)
* chore(release): v5.1.0

* feat: implement resume thumbnails

* fix: remove unused mcp tools

* docs: fix formatting of docs
2026-05-07 15:12:33 +02:00

140 lines
4.0 KiB
TypeScript

import type { Layout, usePanelRef } from "react-resizable-panels";
import { useCallback, useMemo } from "react";
import { useWindowSize } from "usehooks-ts";
import { create } from "zustand/react";
import { useIsMobile } from "@/hooks/use-mobile";
type PanelImperativeHandle = ReturnType<typeof usePanelRef>;
export const BUILDER_LAYOUT_COOKIE_NAME = "builder_layout";
export type BuilderLayout = {
left: number;
artboard: number;
right: number;
};
export const DEFAULT_BUILDER_LAYOUT: BuilderLayout = {
left: 22,
artboard: 56,
right: 22,
};
export const mapPanelLayoutToBuilderLayout = (layout: Layout): BuilderLayout => {
const left = layout.left;
const artboard = layout.artboard;
const right = layout.right;
if (typeof left !== "number" || typeof artboard !== "number" || typeof right !== "number")
return DEFAULT_BUILDER_LAYOUT;
return { left, artboard, right };
};
export const parseBuilderLayoutCookie = (value?: string | null): BuilderLayout => {
if (!value) return DEFAULT_BUILDER_LAYOUT;
try {
const parsed = JSON.parse(value);
if (Array.isArray(parsed)) return DEFAULT_BUILDER_LAYOUT;
if (typeof parsed !== "object" || parsed === null) return DEFAULT_BUILDER_LAYOUT;
const left = (parsed as { left?: unknown }).left;
const artboard = (parsed as { artboard?: unknown }).artboard;
const right = (parsed as { right?: unknown }).right;
if (typeof left !== "number" || typeof artboard !== "number" || typeof right !== "number")
return DEFAULT_BUILDER_LAYOUT;
return { left, artboard, right };
} catch {
return DEFAULT_BUILDER_LAYOUT;
}
};
interface BuilderSidebarState {
layout: BuilderLayout;
leftSidebar: PanelImperativeHandle | null;
rightSidebar: PanelImperativeHandle | null;
}
interface BuilderSidebarActions {
setLayout: (layout: BuilderLayout) => void;
setLeftSidebar: (ref: PanelImperativeHandle | null) => void;
setRightSidebar: (ref: PanelImperativeHandle | null) => void;
}
type BuilderSidebar = BuilderSidebarState & BuilderSidebarActions;
export const useBuilderSidebarStore = create<BuilderSidebar>((set) => ({
layout: DEFAULT_BUILDER_LAYOUT,
leftSidebar: null,
rightSidebar: null,
setLayout: (layout) => set({ layout }),
setLeftSidebar: (ref) => set({ leftSidebar: ref }),
setRightSidebar: (ref) => set({ rightSidebar: ref }),
}));
type UseBuilderSidebarReturn = {
maxSidebarSize: string | number;
collapsedSidebarSize: number;
isCollapsed: (side: "left" | "right") => boolean;
toggleSidebar: (side: "left" | "right", forceState?: boolean) => void;
};
export function useBuilderSidebar<T = UseBuilderSidebarReturn>(selector?: (builder: UseBuilderSidebarReturn) => T): T {
const isMobile = useIsMobile();
const { width } = useWindowSize();
const maxSidebarSize = useMemo((): string | number => {
if (!width) return 0;
return isMobile ? "95%" : "45%";
}, [width, isMobile]);
const collapsedSidebarSize = useMemo((): number => {
if (!width) return 0;
return isMobile ? 0 : 48;
}, [width, isMobile]);
const expandSize = useMemo(() => (isMobile ? "95%" : "30%"), [isMobile]);
const isCollapsed = useCallback((side: "left" | "right") => {
const sidebar =
side === "left"
? useBuilderSidebarStore.getState().leftSidebar?.current
: useBuilderSidebarStore.getState().rightSidebar?.current;
if (!sidebar) return false;
return sidebar.isCollapsed();
}, []);
const toggleSidebar = useCallback(
(side: "left" | "right", forceState?: boolean) => {
const sidebar =
side === "left"
? useBuilderSidebarStore.getState().leftSidebar?.current
: useBuilderSidebarStore.getState().rightSidebar?.current;
if (!sidebar) return;
const shouldExpand = forceState === undefined ? sidebar.isCollapsed() : forceState;
if (shouldExpand) sidebar.resize(expandSize);
else sidebar.collapse();
},
[expandSize],
);
const state = useMemo(() => {
return {
maxSidebarSize,
collapsedSidebarSize,
isCollapsed,
toggleSidebar,
};
}, [maxSidebarSize, collapsedSidebarSize, isCollapsed, toggleSidebar]);
return selector ? selector(state) : (state as T);
}