@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
157 lines (156 loc) • 5.32 kB
JavaScript
"use client";
import FlexBasic_default from "../../Flex/FlexBasic.mjs";
import Button from "../../base-ui/Button/Button.mjs";
import Drawer from "../../base-ui/Drawer/Drawer.mjs";
import { ConsoleShellContext } from "./context.mjs";
import { useIsCompact, useLocalStorage, usePrefersReducedMotion } from "../hooks.mjs";
import { styles } from "./style.mjs";
import { useEffect, useId, useRef, useState } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
import { PanelLeft, X } from "lucide-react";
//#region src/dashboard/ConsoleShell/ConsoleShell.tsx
function isTypingTarget(target) {
if (!(target instanceof HTMLElement)) return false;
const tag = target.tagName;
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || target.isContentEditable;
}
function ConsoleShell({ brand, breadcrumb, children, className, collapsed, closeNavigationLabel = "Close navigation", collapseLabel = "Collapse sidebar", defaultCollapsed = false, expandLabel = "Expand sidebar", footer, hotkey = true, navigation, onCollapsedChange, openNavigationLabel = "Open navigation", skipToContentLabel = "Skip to content", storageKey, tools, ...rest }) {
const mainId = `console-main-${useId().replaceAll(":", "")}`;
const shellRef = useRef(null);
const isCompact = useIsCompact();
const reducedMotion = usePrefersReducedMotion();
const [storedCollapsed, setStoredCollapsed] = useLocalStorage(storageKey, defaultCollapsed);
const [drawerOpen, setDrawerOpen] = useState(false);
const isControlled = collapsed !== void 0;
const railCollapsed = isControlled ? collapsed : storedCollapsed;
const railed = railCollapsed && !isCompact;
const setCollapsed = (next) => {
if (!isControlled) setStoredCollapsed(next);
onCollapsedChange?.(next);
};
const closeNavigation = () => setDrawerOpen(false);
const toggleNavigation = () => {
if (isCompact) {
setDrawerOpen((open) => !open);
return;
}
setCollapsed(!railCollapsed);
};
const toggleRef = useRef(toggleNavigation);
toggleRef.current = toggleNavigation;
useEffect(() => {
if (!hotkey) return;
const node = shellRef.current;
if (!node) return;
const onKeyDown = (event) => {
if (event.key.toLowerCase() !== "b" || !(event.metaKey || event.ctrlKey) || event.altKey) return;
if (event.repeat || isTypingTarget(event.target)) return;
if (!node.contains(event.target)) return;
event.preventDefault();
toggleRef.current();
};
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, [hotkey]);
const stateFor = (rail) => ({
closeNavigation,
collapsed: rail,
compact: isCompact
});
const sidebar = (rail) => /* @__PURE__ */ jsxs(ConsoleShellContext, {
value: stateFor(rail),
children: [
brand || isCompact ? /* @__PURE__ */ jsxs("div", {
className: styles.brand,
"data-collapsed": rail,
children: [brand, isCompact ? /* @__PURE__ */ jsx(Button, {
"aria-label": closeNavigationLabel,
className: styles.brandClose,
icon: X,
type: "text",
onClick: closeNavigation
}) : null]
}) : null,
navigation ? /* @__PURE__ */ jsx("div", {
className: styles.navSlot,
children: navigation
}) : null,
footer ? /* @__PURE__ */ jsx("div", {
className: styles.sidebarBottom,
children: footer
}) : null
]
});
const toggleLabel = isCompact ? openNavigationLabel : railed ? expandLabel : collapseLabel;
return /* @__PURE__ */ jsxs("div", {
className: className ? `${styles.shell} ${className}` : styles.shell,
"data-console-shell": "",
ref: shellRef,
...rest,
children: [
/* @__PURE__ */ jsx("a", {
className: styles.skipLink,
href: `#${mainId}`,
onClick: (event) => {
event.preventDefault();
document.getElementById(mainId)?.focus();
},
children: skipToContentLabel
}),
/* @__PURE__ */ jsx("aside", {
className: styles.sidebar,
"data-collapsed": railed,
"data-instant": reducedMotion,
children: !isCompact && sidebar(railed)
}),
isCompact ? /* @__PURE__ */ jsx(Drawer, {
noHeader: true,
classNames: { bodyContent: styles.drawerBody },
closable: false,
open: drawerOpen,
placement: "left",
width: 272,
onClose: closeNavigation,
children: /* @__PURE__ */ jsx(FlexBasic_default, {
height: "100%",
children: sidebar(false)
})
}) : null,
/* @__PURE__ */ jsxs("div", {
className: styles.workspace,
children: [/* @__PURE__ */ jsxs("header", {
className: styles.topbar,
children: [
/* @__PURE__ */ jsx(Button, {
"aria-label": toggleLabel,
icon: PanelLeft,
type: "text",
onClick: toggleNavigation
}),
/* @__PURE__ */ jsx("span", {
"aria-hidden": true,
className: styles.topbarDivider
}),
/* @__PURE__ */ jsx("div", {
className: styles.topbarMain,
children: breadcrumb
}),
tools ? /* @__PURE__ */ jsx("div", {
className: styles.tools,
children: tools
}) : null
]
}), /* @__PURE__ */ jsx("main", {
className: styles.main,
id: mainId,
tabIndex: -1,
children
})]
})
]
});
}
ConsoleShell.displayName = "ConsoleShell";
//#endregion
export { ConsoleShell as default };
//# sourceMappingURL=ConsoleShell.mjs.map