81 lines
2.3 KiB
TypeScript
81 lines
2.3 KiB
TypeScript
import { IsMobile } from "@/hooks/is-mobile.svelte.js";
|
|
import { getContext, setContext } from "svelte";
|
|
import { SIDEBAR_KEYBOARD_SHORTCUT } from "./constants.js";
|
|
|
|
type Getter<T> = () => 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<boolean>;
|
|
|
|
/**
|
|
* 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 `<svelte:window>`
|
|
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));
|
|
}
|