From b809fc042fc224b1a7e34d87a010f48c037944dd Mon Sep 17 00:00:00 2001 From: Rajesh Chityal Date: Wed, 10 Jun 2026 16:32:03 +0530 Subject: [PATCH 1/3] nav bar of mobile fixed --- src/components/layout/AppSidebar.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/layout/AppSidebar.tsx b/src/components/layout/AppSidebar.tsx index 427964e7..537f48f4 100644 --- a/src/components/layout/AppSidebar.tsx +++ b/src/components/layout/AppSidebar.tsx @@ -299,6 +299,8 @@ const AppSidebar = () => { .filter(Boolean) .join(" "); + const showExpanded = !isDesktop || !isCollapsed; + const selectWorkspace = (workspace: SolidMenuItem) => { setSelectedWorkspaceKey(workspace.key || ""); setSearchTerm(""); @@ -321,7 +323,7 @@ const AppSidebar = () => { {(selectedWorkspace?.title || "W").slice(0, 1).toUpperCase()} - {!isCollapsed && ( + {showExpanded && ( <> Workspace @@ -337,7 +339,7 @@ const AppSidebar = () => { )} - {workspaceOpen && !isCollapsed && ( + {workspaceOpen && showExpanded && (
{workspaces.map((workspace) => (
- {!isCollapsed ? ( + {showExpanded ? ( <>
Date: Tue, 15 Sep 2026 18:36:48 +0530 Subject: [PATCH 2/3] fix: resolve tree view button display issue --- src/resources/solid-custom.css | 35 ++++++++++++------- .../themes/solid-light-modern/theme.css | 6 ++-- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/resources/solid-custom.css b/src/resources/solid-custom.css index 9287702d..5ea37475 100644 --- a/src/resources/solid-custom.css +++ b/src/resources/solid-custom.css @@ -4493,6 +4493,15 @@ html.dark .solid-paginator-btn:hover:not(:disabled), box-shadow: -8px 0 12px -12px rgba(15, 23, 42, 0.18); } +.solid-data-table-td.solid-tree-actions-cell { + vertical-align: middle; +} + +.solid-data-table-td.solid-tree-actions-cell > div { + align-items: center; + min-height: 100%; +} + .solid-data-table-head .solid-tree-group-header-cell { min-height: 42px; z-index: 10 !important; @@ -4684,25 +4693,27 @@ html.dark .solid-paginator-btn:hover:not(:disabled), } .solid-tree-row-menu-trigger { + width: 28px; + height: 28px; + min-width: 28px; + padding: 0; + border-radius: 8px; + border: 1px solid color-mix(in srgb, var(--border) 86%, transparent); + background: var(--background); + color: color-mix(in srgb, var(--foreground) 78%, transparent); display: inline-flex; align-items: center; justify-content: center; - width: 2rem; - height: 2rem; - border-radius: 9px; - border: 1px solid transparent; - background: transparent; - color: color-mix(in srgb, var(--foreground) 76%, transparent); + cursor: pointer; transition: - background-color 120ms ease, - border-color 120ms ease, + background 120ms ease, color 120ms ease; } -.solid-tree-row-menu-trigger:hover { - background: color-mix(in srgb, var(--accent) 92%, transparent); - border-color: color-mix(in srgb, var(--border) 74%, transparent); - color: var(--foreground); +.solid-tree-row-menu-trigger:hover, +.solid-tree-row-menu-trigger[data-state="open"] { + background: var(--accent); + color: var(--accent-foreground); } .solid-tree-row-menu .solid-dropdown-menu-item { diff --git a/src/resources/themes/solid-light-modern/theme.css b/src/resources/themes/solid-light-modern/theme.css index 888ed5f2..c472509c 100644 --- a/src/resources/themes/solid-light-modern/theme.css +++ b/src/resources/themes/solid-light-modern/theme.css @@ -741,8 +741,7 @@ border-color: var(--solid-border-default) !important; } - .solid-row-menu-trigger, - .solid-tree-row-menu-trigger { + .solid-row-menu-trigger { background: #f3ebdd !important; background-color: #f3ebdd !important; border-color: var(--solid-border-default) !important; @@ -750,8 +749,7 @@ box-shadow: none !important; } - .solid-row-menu-trigger:hover, - .solid-tree-row-menu-trigger:hover { + .solid-row-menu-trigger:hover { background: #ede2d1 !important; background-color: #ede2d1 !important; color: #2e2e2e !important; From 77bebdf8e70c3c3f4b6ac167abf89d34045a1707 Mon Sep 17 00:00:00 2001 From: Gaurav Dafale Date: Tue, 15 Sep 2026 18:39:18 +0530 Subject: [PATCH 3/3] chore: sync AppSidebar with dev --- src/components/layout/AppSidebar.tsx | 928 ++++++++++++--------------- 1 file changed, 410 insertions(+), 518 deletions(-) diff --git a/src/components/layout/AppSidebar.tsx b/src/components/layout/AppSidebar.tsx index 271c60a2..7e27f614 100644 --- a/src/components/layout/AppSidebar.tsx +++ b/src/components/layout/AppSidebar.tsx @@ -16,11 +16,11 @@ import type { MenuItemIconSource } from "../../helpers/menuItemIcons"; import { SolidMenuItemIcon } from "./SolidMenuItemIcon"; type SolidMenuItem = Omit & { - key?: string; - title: string; - path?: string; - icon?: string | null; - children?: SolidMenuItem[]; + key?: string; + title: string; + path?: string; + icon?: string | null; + children?: SolidMenuItem[]; }; const defaultMenuKey = env("NEXT_PUBLIC_DEFAULT_MENU_KEY"); @@ -32,558 +32,450 @@ const DEFAULT_WORKSPACE_TITLE = "Solid Core"; const DEFAULT_WORKSPACE_KEY = "solid-core"; function normalizePath(path: string): string { - if (!path || path === "/") return "/"; - return path.endsWith("/") ? path.slice(0, -1) : path; + if (!path || path === "/") return "/"; + return path.endsWith("/") ? path.slice(0, -1) : path; } -function getPathAndParams(targetPath: string): { - pathname: string; - searchParams: URLSearchParams; -} { - const parsed = new URL(targetPath, "http://localhost"); - return { - pathname: normalizePath(parsed.pathname), - searchParams: new URLSearchParams(parsed.search), - }; +function getPathAndParams(targetPath: string): { pathname: string; searchParams: URLSearchParams } { + const parsed = new URL(targetPath, "http://localhost"); + return { + pathname: normalizePath(parsed.pathname), + searchParams: new URLSearchParams(parsed.search), + }; } -function doesSearchParamsMatchSubset( - requiredParams: URLSearchParams, - currentParams: URLSearchParams, -): boolean { - const currentValueCounts = new Map(); - currentParams.forEach((value, key) => { - const token = `${key}=${value}`; - currentValueCounts.set(token, (currentValueCounts.get(token) || 0) + 1); - }); - - let isSubset = true; - requiredParams.forEach((value, key) => { - if (!isSubset) return; - const token = `${key}=${value}`; - const count = currentValueCounts.get(token) || 0; - if (count <= 0) { - isSubset = false; - return; - } - currentValueCounts.set(token, count - 1); - }); - - return isSubset; +function doesSearchParamsMatchSubset(requiredParams: URLSearchParams, currentParams: URLSearchParams): boolean { + const currentValueCounts = new Map(); + currentParams.forEach((value, key) => { + const token = `${key}=${value}`; + currentValueCounts.set(token, (currentValueCounts.get(token) || 0) + 1); + }); + + let isSubset = true; + requiredParams.forEach((value, key) => { + if (!isSubset) return; + const token = `${key}=${value}`; + const count = currentValueCounts.get(token) || 0; + if (count <= 0) { + isSubset = false; + return; + } + currentValueCounts.set(token, count - 1); + }); + + return isSubset; } -function isMenuPathActive( - itemPath: string | undefined, - currentPathname: string, - currentSearchParams: URLSearchParams, -): boolean { +function isMenuPathActive(itemPath: string | undefined, currentPathname: string, currentSearchParams: URLSearchParams): boolean { if (!itemPath) return false; const resolvedItemPath = resolveRetainedModelViewRoute(itemPath); - const { pathname: itemPathname, searchParams: itemSearchParams } = - getPathAndParams(resolvedItemPath); + const { pathname: itemPathname, searchParams: itemSearchParams } = getPathAndParams(resolvedItemPath); if (itemPathname !== normalizePath(currentPathname)) return false; - return doesSearchParamsMatchSubset(itemSearchParams, currentSearchParams); + return doesSearchParamsMatchSubset(itemSearchParams, currentSearchParams); } function cloneMenuItems(items: SolidMenuItem[] = []): SolidMenuItem[] { - return items.map((item) => ({ - ...item, - children: item.children ? cloneMenuItems(item.children) : [], - })); + return items.map((item) => ({ + ...item, + children: item.children ? cloneMenuItems(item.children) : [], + })); } -function filterMenuItems( - items: SolidMenuItem[], - query: string, -): SolidMenuItem[] { - const normalizedQuery = query.trim().toLowerCase(); - if (!normalizedQuery) return items; - - const next: SolidMenuItem[] = []; - items.forEach((item) => { - const children = item.children - ? filterMenuItems(item.children, normalizedQuery) - : []; - const matchesSelf = item.title.toLowerCase().includes(normalizedQuery); - const isPathlessParentMatch = - matchesSelf && !item.path && item.children && item.children.length > 0; - - if (isPathlessParentMatch) { - next.push({ ...item, children: cloneMenuItems(item.children) }); - return; - } - - if (matchesSelf || children.length > 0) { - next.push({ ...item, children }); - } - }); - return next; +function filterMenuItems(items: SolidMenuItem[], query: string): SolidMenuItem[] { + const normalizedQuery = query.trim().toLowerCase(); + if (!normalizedQuery) return items; + + const next: SolidMenuItem[] = []; + items.forEach((item) => { + const children = item.children ? filterMenuItems(item.children, normalizedQuery) : []; + const matchesSelf = item.title.toLowerCase().includes(normalizedQuery); + const isPathlessParentMatch = matchesSelf && !item.path && item.children && item.children.length > 0; + + if (isPathlessParentMatch) { + next.push({ ...item, children: cloneMenuItems(item.children) }); + return; + } + + if (matchesSelf || children.length > 0) { + next.push({ ...item, children }); + } + }); + return next; } function resolveDefaultWorkspace(items: SolidMenuItem[]) { - return ( - items.find((item) => item.key === DEFAULT_WORKSPACE_KEY) || - items.find( - (item) => - item.title.trim().toLowerCase() === - DEFAULT_WORKSPACE_TITLE.toLowerCase(), - ) || - items.find((item) => item.key === defaultMenuKey) || - items[0] - ); + return ( + items.find((item) => item.key === DEFAULT_WORKSPACE_KEY) || + items.find((item) => item.title.trim().toLowerCase() === DEFAULT_WORKSPACE_TITLE.toLowerCase()) || + items.find((item) => item.key === defaultMenuKey) || + items[0] + ); } -const WorkspaceIcon = ({ - workspace, - className, -}: { - workspace?: SolidMenuItem; - className: string; -}) => { - const iconUrl = normalizeAssetUrl(workspace?.icon) || null; - const [failedUrl, setFailedUrl] = useState(null); - const showImage = !!iconUrl && failedUrl !== iconUrl; - - useEffect(() => { - setFailedUrl(null); - }, [iconUrl]); - - return ( - - {showImage ? ( - setFailedUrl(iconUrl)} - /> - ) : ( - (workspace?.title || "W").slice(0, 1).toUpperCase() - )} - - ); -}; +const WorkspaceIcon = ({ workspace, className }: { workspace?: SolidMenuItem; className: string }) => { + const iconUrl = normalizeAssetUrl(workspace?.icon) || null; + const [failedUrl, setFailedUrl] = useState(null); + const showImage = !!iconUrl && failedUrl !== iconUrl; -const SidebarMenuTree = ({ - items, - pathname, - searchParams, - forceExpand, -}: { - items: SolidMenuItem[]; - pathname: string; - searchParams: URLSearchParams; - forceExpand?: boolean; -}) => { - const [expandedKeys, setExpandedKeys] = useState>({}); - - const buildNodeId = ( - node: SolidMenuItem, - parentId: string, - index: number, - ) => { - const base = node.key || node.path || node.title || `node-${index}`; - return `${parentId}/${base}`; - }; - - useEffect(() => { - const initialExpanded: Record = {}; - if (forceExpand) { - const walk = (nodes: SolidMenuItem[], parentId = "root") => { - nodes.forEach((node, index) => { - const nodeId = buildNodeId(node, parentId, index); - if (node.children && node.children.length > 0 && node.key) { - initialExpanded[nodeId] = true; - walk(node.children, nodeId); - } else if (node.children && node.children.length > 0) { - initialExpanded[nodeId] = true; - walk(node.children, nodeId); - } - }); - }; - walk(items); - } - setExpandedKeys(initialExpanded); - }, [items, forceExpand]); - - const toggleExpanded = (nodeId: string) => { - setExpandedKeys((prev) => ({ ...prev, [nodeId]: !prev[nodeId] })); - }; - - const renderNode = ( - node: SolidMenuItem, - depth = 0, - parentId = "root", - index = 0, - ) => { - const nodeId = buildNodeId(node, parentId, index); - const hasChildren = !!(node.children && node.children.length > 0); - const isExpanded = expandedKeys[nodeId] === true; - const isActive = isMenuPathActive(node.path, pathname, searchParams); - const resolvedPath = node.path - ? resolveRetainedModelViewRoute(node.path) - : undefined; - const paddingLeft = 12 + depth * 14; + useEffect(() => { + setFailedUrl(null); + }, [iconUrl]); return ( -
  • -
    - {hasChildren ? ( - - ) : node.path ? ( - - - {node.title} - - ) : ( - - - {node.title} - - )} - {hasChildren && ( - - )} -
    - {hasChildren && isExpanded && ( -
      - {node.children!.map((child, childIndex) => - renderNode(child as SolidMenuItem, depth + 1, nodeId, childIndex), + + {showImage ? ( + setFailedUrl(iconUrl)} + /> + ) : ( + (workspace?.title || "W").slice(0, 1).toUpperCase() )} -
    - )} -
  • + ); - }; - - return ( -
      - {items.map((item, index) => renderNode(item, 0, "root", index))} -
    - ); }; -const AppSidebar = () => { - const dispatch = useDispatch(); - const pathname = usePathname(); - const searchParams = useSearchParams(); - const visibleNavbar = useSelector( - (state: any) => state.navbarState.visibleNavbar, - ); - const { data: menu } = useGetSolidMenuBasedOnRoleQuery(""); - const workspaceSwitcherRef = useRef(null); - - const [searchTerm, setSearchTerm] = useState(""); - const [workspaceOpen, setWorkspaceOpen] = useState(false); - const [selectedWorkspaceKey, setSelectedWorkspaceKey] = useState( - () => { - if (typeof window === "undefined") return ""; - return window.localStorage.getItem(SELECTED_WORKSPACE_STORAGE_KEY) || ""; - }, - ); - const [isDesktop, setIsDesktop] = useState( - typeof window === "undefined" ? true : window.innerWidth > 1199, - ); - const [isCollapsed, setIsCollapsed] = useState(() => { - if (typeof window === "undefined") return false; - const stored = window.localStorage.getItem(SIDEBAR_STORAGE_KEY); - if (stored === null) return true; - return stored === "true"; - }); - - const { data } = useSession(); - - useEffect(() => { - if (data) { - dispatch(setUser(data?.user)); - dispatch(setIsAuthenticated(true)); - } - }, [data, dispatch]); - - useEffect(() => { - const onResize = () => { - const desktop = window.innerWidth > 1199; - setIsDesktop(desktop); - if (desktop) { - dispatch(hideNavbar()); - } - }; +const SidebarMenuTree = ({ + items, + pathname, + searchParams, + forceExpand, +}: { + items: SolidMenuItem[]; + pathname: string; + searchParams: URLSearchParams; + forceExpand?: boolean; +}) => { + const [expandedKeys, setExpandedKeys] = useState>({}); - onResize(); - window.addEventListener("resize", onResize); - return () => window.removeEventListener("resize", onResize); - }, [dispatch]); - - useEffect(() => { - if (typeof window === "undefined") return; - window.localStorage.setItem(SIDEBAR_STORAGE_KEY, String(isCollapsed)); - }, [isCollapsed]); - - useEffect(() => { - if (typeof window === "undefined") return; - if (!selectedWorkspaceKey) { - window.localStorage.removeItem(SELECTED_WORKSPACE_STORAGE_KEY); - return; - } - window.localStorage.setItem( - SELECTED_WORKSPACE_STORAGE_KEY, - selectedWorkspaceKey, - ); - }, [selectedWorkspaceKey]); - - useEffect(() => { - const width = isDesktop - ? isCollapsed - ? "0px" - : DESKTOP_SIDEBAR_WIDTH - : "0px"; - document.documentElement.style.setProperty("--solid-sidebar-width", width); - }, [isDesktop, isCollapsed]); - - useEffect(() => { - if (!menu?.data?.length) return; - const defaultWorkspace = resolveDefaultWorkspace(menu.data); - const hasSelectedWorkspace = menu.data.some( - (workspace: SolidMenuItem) => workspace.key === selectedWorkspaceKey, - ); - const nextWorkspaceKey = hasSelectedWorkspace - ? selectedWorkspaceKey - : defaultWorkspace?.key || ""; - - if (nextWorkspaceKey !== selectedWorkspaceKey) { - setSelectedWorkspaceKey(nextWorkspaceKey); - } - }, [menu, selectedWorkspaceKey]); - - useEffect(() => { - if (!isDesktop) { - dispatch(hideNavbar()); - } - }, [pathname, dispatch, isDesktop]); - - useEffect(() => { - const onToggleRequest = () => { - if (window.innerWidth > 1199) { - setIsCollapsed((prev) => !prev); - return; - } - dispatch(toggleNavbar()); + const buildNodeId = (node: SolidMenuItem, parentId: string, index: number) => { + const base = node.key || node.path || node.title || `node-${index}`; + return `${parentId}/${base}`; }; - window.addEventListener(SIDEBAR_TOGGLE_EVENT, onToggleRequest); - return () => - window.removeEventListener(SIDEBAR_TOGGLE_EVENT, onToggleRequest); - }, [dispatch]); + useEffect(() => { + const initialExpanded: Record = {}; + if (forceExpand) { + const walk = (nodes: SolidMenuItem[], parentId = "root") => { + nodes.forEach((node, index) => { + const nodeId = buildNodeId(node, parentId, index); + if (node.children && node.children.length > 0 && node.key) { + initialExpanded[nodeId] = true; + walk(node.children, nodeId); + } else if (node.children && node.children.length > 0) { + initialExpanded[nodeId] = true; + walk(node.children, nodeId); + } + }); + }; + walk(items); + } + setExpandedKeys(initialExpanded); + }, [items, forceExpand]); + + const toggleExpanded = (nodeId: string) => { + setExpandedKeys((prev) => ({ ...prev, [nodeId]: !prev[nodeId] })); + }; - useEffect(() => { - if (!workspaceOpen) return; + const renderNode = (node: SolidMenuItem, depth = 0, parentId = "root", index = 0) => { + const nodeId = buildNodeId(node, parentId, index); + const hasChildren = !!(node.children && node.children.length > 0); + const isExpanded = expandedKeys[nodeId] === true; + const isActive = isMenuPathActive(node.path, pathname, searchParams); + const resolvedPath = node.path ? resolveRetainedModelViewRoute(node.path) : undefined; + const paddingLeft = 12 + depth * 14; + + return ( +
  • +
    + {hasChildren ? ( + + ) : node.path ? ( + + + {node.title} + + ) : ( + + + {node.title} + + )} + {hasChildren && ( + + )} +
    + {hasChildren && isExpanded && ( +
      + {node.children!.map((child, childIndex) => renderNode(child as SolidMenuItem, depth + 1, nodeId, childIndex))} +
    + )} +
  • + ); + }; - const handlePointerDown = (event: MouseEvent) => { - const switcher = workspaceSwitcherRef.current; - const target = event.target as Node; + return
      {items.map((item, index) => renderNode(item, 0, "root", index))}
    ; +}; - if (!switcher || switcher.contains(target)) { - return; - } +const AppSidebar = () => { + const dispatch = useDispatch(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + const visibleNavbar = useSelector((state: any) => state.navbarState.visibleNavbar); + const { data: menu } = useGetSolidMenuBasedOnRoleQuery(""); + const workspaceSwitcherRef = useRef(null); + + const [searchTerm, setSearchTerm] = useState(""); + const [workspaceOpen, setWorkspaceOpen] = useState(false); + const [selectedWorkspaceKey, setSelectedWorkspaceKey] = useState(() => { + if (typeof window === "undefined") return ""; + return window.localStorage.getItem(SELECTED_WORKSPACE_STORAGE_KEY) || ""; + }); + const [isDesktop, setIsDesktop] = useState(typeof window === "undefined" ? true : window.innerWidth > 1199); + const [isCollapsed, setIsCollapsed] = useState(() => { + if (typeof window === "undefined") return false; + const stored = window.localStorage.getItem(SIDEBAR_STORAGE_KEY); + if (stored === null) return true; + return stored === "true"; + }); + + const { data } = useSession(); + + useEffect(() => { + if (data) { + dispatch(setUser(data?.user)); + dispatch(setIsAuthenticated(true)); + } + }, [data, dispatch]); + + useEffect(() => { + const onResize = () => { + const desktop = window.innerWidth > 1199; + setIsDesktop(desktop); + if (desktop) { + dispatch(hideNavbar()); + } + }; + + onResize(); + window.addEventListener("resize", onResize); + return () => window.removeEventListener("resize", onResize); + }, [dispatch]); + + useEffect(() => { + if (typeof window === "undefined") return; + window.localStorage.setItem(SIDEBAR_STORAGE_KEY, String(isCollapsed)); + }, [isCollapsed]); + + useEffect(() => { + if (typeof window === "undefined") return; + if (!selectedWorkspaceKey) { + window.localStorage.removeItem(SELECTED_WORKSPACE_STORAGE_KEY); + return; + } + window.localStorage.setItem(SELECTED_WORKSPACE_STORAGE_KEY, selectedWorkspaceKey); + }, [selectedWorkspaceKey]); + + useEffect(() => { + const width = isDesktop ? (isCollapsed ? "0px" : DESKTOP_SIDEBAR_WIDTH) : "0px"; + document.documentElement.style.setProperty("--solid-sidebar-width", width); + }, [isDesktop, isCollapsed]); + + useEffect(() => { + if (!menu?.data?.length) return; + const defaultWorkspace = resolveDefaultWorkspace(menu.data); + const hasSelectedWorkspace = menu.data.some((workspace: SolidMenuItem) => workspace.key === selectedWorkspaceKey); + const nextWorkspaceKey = hasSelectedWorkspace ? selectedWorkspaceKey : defaultWorkspace?.key || ""; + + if (nextWorkspaceKey !== selectedWorkspaceKey) { + setSelectedWorkspaceKey(nextWorkspaceKey); + } + }, [menu, selectedWorkspaceKey]); + + useEffect(() => { + if (!isDesktop) { + dispatch(hideNavbar()); + } + }, [pathname, dispatch, isDesktop]); + + useEffect(() => { + const onToggleRequest = () => { + if (window.innerWidth > 1199) { + setIsCollapsed((prev) => !prev); + return; + } + dispatch(toggleNavbar()); + }; + + window.addEventListener(SIDEBAR_TOGGLE_EVENT, onToggleRequest); + return () => window.removeEventListener(SIDEBAR_TOGGLE_EVENT, onToggleRequest); + }, [dispatch]); + + useEffect(() => { + if (!workspaceOpen) return; + + const handlePointerDown = (event: MouseEvent) => { + const switcher = workspaceSwitcherRef.current; + const target = event.target as Node; + + if (!switcher || switcher.contains(target)) { + return; + } + + setWorkspaceOpen(false); + }; + + document.addEventListener("pointerdown", handlePointerDown); + return () => document.removeEventListener("pointerdown", handlePointerDown); + }, [workspaceOpen]); + + const workspaces: SolidMenuItem[] = menu?.data || []; + const selectedWorkspace = workspaces.find((m) => m.key === selectedWorkspaceKey) || workspaces[0]; + const selectedWorkspaceChildren = selectedWorkspace?.children || []; + + const filteredMenu = useMemo( + () => filterMenuItems(selectedWorkspaceChildren, searchTerm), + [selectedWorkspaceChildren, searchTerm] + ); - setWorkspaceOpen(false); + const shellClasses = [ + "solid-sidebar", + isDesktop && isCollapsed ? "is-collapsed" : "", + !isDesktop && visibleNavbar ? "is-open" : "", + ] + .filter(Boolean) + .join(" "); + // The collapsed preference belongs to the desktop sidebar. Mobile uses + // the navbar visibility state for its drawer, so it must always render + // the full menu when opened. + const isSidebarContentCollapsed = isDesktop && isCollapsed; + + const selectWorkspace = (workspace: SolidMenuItem) => { + setSelectedWorkspaceKey(workspace.key || ""); + setSearchTerm(""); + setWorkspaceOpen(false); }; - document.addEventListener("pointerdown", handlePointerDown); - return () => document.removeEventListener("pointerdown", handlePointerDown); - }, [workspaceOpen]); - - const workspaces: SolidMenuItem[] = menu?.data || []; - const selectedWorkspace = - workspaces.find((m) => m.key === selectedWorkspaceKey) || workspaces[0]; - const selectedWorkspaceChildren = selectedWorkspace?.children || []; - - const filteredMenu = useMemo( - () => filterMenuItems(selectedWorkspaceChildren, searchTerm), - [selectedWorkspaceChildren, searchTerm], - ); - - const shellClasses = [ - "solid-sidebar", - isDesktop && isCollapsed ? "is-collapsed" : "", - !isDesktop && visibleNavbar ? "is-open" : "", - ] - .filter(Boolean) - .join(" "); - // The collapsed preference belongs to the desktop sidebar. Mobile uses - // the navbar visibility state for its drawer, so it must always render - // the full menu when opened. - const isSidebarContentCollapsed = isDesktop && isCollapsed; - - const selectWorkspace = (workspace: SolidMenuItem) => { - setSelectedWorkspaceKey(workspace.key || ""); - setSearchTerm(""); - setWorkspaceOpen(false); - }; - - return ( - <> - {!isDesktop && visibleNavbar && ( -
    dispatch(toggleNavbar())} - /> - )} - - + + {isDesktop && isCollapsed && ( + - ))} -
    + className="solid-sidebar-hotspot" + onClick={() => setIsCollapsed(false)} + aria-label="Expand sidebar" + title="Expand sidebar" + /> )} -
    - - - {!isSidebarContentCollapsed ? ( - <> -
    - setSearchTerm(e.target.value)} - placeholder="Search in menu..." - className="solid-sidebar-search" - /> -
    - -
    - -
    - - ) : ( -
    - {filteredMenu.slice(0, 10).map((item) => ( - - {item.title.slice(0, 1).toUpperCase()} - - ))} -
    - )} - -
    - - -
    - - - {isDesktop && isCollapsed && ( -