import { IsMobile } from "$lib/hooks/is-mobile.svelte.js"; import { getContext, setContext } from "svelte"; import { SIDEBAR_KEYBOARD_SHORTCUT } from "./constants.js"; type Getter = () => T; export type SidebarStateProps = { /** * A getter function that returns the current open state of the sidebar. * We use a getter function here to support `bind:open` on the `Sidebar.Provider` * component. */ open: Getter; /** * A function that sets the open state of the sidebar. To support `bind:open`, we need * a source of truth for changing the open state to ensure it will be synced throughout * the sub-components and any `bind:` references. */ setOpen: (open: boolean) => void; }; class SidebarState { readonly props: SidebarStateProps; open = $derived.by(() => this.props.open()); openMobile = $state(false); setOpen: SidebarStateProps["setOpen"]; #isMobile: IsMobile; state = $derived.by(() => (this.open ? "expanded" : "collapsed")); constructor(props: SidebarStateProps) { this.setOpen = props.setOpen; this.#isMobile = new IsMobile(); this.props = props; } // Convenience getter for checking if the sidebar is mobile // without this, we would need to use `sidebar.isMobile.current` everywhere get isMobile() { return this.#isMobile.current; } // Event handler to apply to the `` handleShortcutKeydown = (e: KeyboardEvent) => { if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) { e.preventDefault(); this.toggle(); } }; setOpenMobile = (value: boolean) => { this.openMobile = value; }; toggle = () => { return this.#isMobile.current ? (this.openMobile = !this.openMobile) : this.setOpen(!this.open); }; } const SYMBOL_KEY = "scn-sidebar"; /** * Instantiates a new `SidebarState` instance and sets it in the context. * * @param props The constructor props for the `SidebarState` class. * @returns The `SidebarState` instance. */ export function setSidebar(props: SidebarStateProps): SidebarState { return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props)); } /** * Retrieves the `SidebarState` instance from the context. This is a class instance, * so you cannot destructure it. * @returns The `SidebarState` instance. */ export function useSidebar(): SidebarState { return getContext(Symbol.for(SYMBOL_KEY)); }