import { useClickOutside, useHotkeys } from "@mantine/hooks"; import * as ScrollArea from "@radix-ui/react-scroll-area"; import { IconChevronDown, IconChevronUp } from "@tabler/icons-react"; import { throttle } from "lodash-es"; import { type ComponentProps, useEffect, useRef, useState } from "react"; import type { IconType } from "react-icons"; import { twMerge } from "tailwind-merge"; import { IconButton } from "ui/components/buttons/IconButton"; import { AppShellProvider, useAppShell } from "ui/layouts/AppShell/use-appshell"; import { useEvent } from "../../hooks/use-event"; export function Root({ children }) { return ( {children} ); } type NavLinkProps = { Icon?: IconType; children: React.ReactNode; className?: string; to?: string; // @todo: workaround as?: E; disabled?: boolean; }; export const NavLink = ({ children, as, className, Icon, disabled, ...otherProps }: NavLinkProps & Omit, keyof NavLinkProps>) => { const Tag = as || "a"; return ( {Icon && } {typeof children === "string" ? {children} : children} ); }; export function Content({ children, center }: { children: React.ReactNode; center?: boolean }) { return ( {children} ); } export function Main({ children }) { return ( {children} ); } export function Sidebar({ children }) { const ctx = useAppShell(); const ref = useClickOutside(ctx.sidebar?.handler?.close); const onClickBackdrop = useEvent((e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); ctx?.sidebar?.handler.close(); }); const onEscape = useEvent(() => { if (ctx?.sidebar?.open) { ctx?.sidebar?.handler.close(); } }); // @todo: potentially has to be added to the root, as modals could be opened useHotkeys([["Escape", onEscape]]); if (!ctx) { console.warn("AppShell.Sidebar: missing AppShellContext"); return null; } return ( <> > ); } export function SectionHeaderTitle({ children, className, ...props }: ComponentProps<"h2">) { return ( {children} ); } export function SectionHeader({ children, right, className, scrollable, sticky }: any = {}) { return ( {typeof children === "string" ? ( {children} ) : ( children )} {right && !scrollable && {right}} {right && scrollable && ( {right} )} ); } type SidebarLinkProps = { children: React.ReactNode; as?: E; to?: string; // @todo: workaround params?: Record; // @todo: workaround disabled?: boolean; }; export const SidebarLink = ({ children, as, className, disabled = false, ...otherProps }: SidebarLinkProps & Omit, keyof SidebarLinkProps>) => { const Tag = as || "a"; return ( {children} ); }; type SectionHeaderLinkProps = { children: React.ReactNode; as?: E; to?: string; // @todo: workaround disabled?: boolean; active?: boolean; badge?: string | number; }; export const SectionHeaderLink = ({ children, as, className, disabled = false, active = false, badge, ...props }: SectionHeaderLinkProps & Omit, keyof SectionHeaderLinkProps>) => { const Tag = as || "a"; return ( {children} {badge ? ( {badge} ) : null} ); }; export type SectionHeaderTabsProps = { title?: string; items?: (Omit, "children"> & { label: string; })[]; }; export const SectionHeaderTabs = ({ title, items }: SectionHeaderTabsProps) => { return ( {title && ( {title} )} {items?.map(({ label, ...item }, key) => ( {label} ))} ); }; export function Scrollable({ children, initialOffset = 64 }: { children: React.ReactNode; initialOffset?: number; }) { const scrollRef = useRef>(null); const [offset, setOffset] = useState(initialOffset); function updateHeaderHeight() { if (scrollRef.current) { setOffset(scrollRef.current.offsetTop); } } useEffect(updateHeaderHeight, []); if (typeof window !== "undefined") { window.addEventListener("resize", throttle(updateHeaderHeight, 500)); } return ( {children} ); } export const SectionHeaderAccordionItem = ({ title, open, toggle, ActiveIcon = IconChevronUp, children, renderHeaderRight }: { title: string; open: boolean; toggle: () => void; ActiveIcon?: any; children?: React.ReactNode; renderHeaderRight?: (props: { open: boolean }) => React.ReactNode; }) => ( {title} {renderHeaderRight?.({ open })} {children} ); export { Header } from "./Header";