| import throttle from 'lodash/throttle'; |
| |
| import { useState, useRef, useCallback, useEffect, memo } from 'react'; |
| |
| |
| |
| |
| |
| import type { ImperativePanelHandle } from 'react-resizable-panels'; |
| |
| import { |
| ResizablePanel, |
| ResizablePanelGroup, |
| } from '~/components/ui/Resizable'; |
| import { TooltipProvider, Tooltip } from '~/components/ui/Tooltip'; |
| |
| import { useMediaQuery, useLocalStorage } from '~/hooks'; |
| import NavToggle from '~/components/Nav/NavToggle'; |
| |
| |
| import { cn } from '~/utils'; |
| |
|
|
| interface SidePanelProps { |
| defaultLayout?: number[] | undefined; |
| defaultCollapsed?: boolean; |
| navCollapsedSize?: number; |
| fullPanelCollapse?: boolean; |
| children: React.ReactNode; |
| } |
|
|
| const defaultMinSize = 20; |
| |
|
|
| const SidePanel = ({ |
| defaultLayout = [97, 3], |
| defaultCollapsed = false, |
| fullPanelCollapse = false, |
| navCollapsedSize = 3, |
| children, |
| }: SidePanelProps) => { |
| const [isHovering, setIsHovering] = useState(false); |
| const [minSize, setMinSize] = useState(defaultMinSize); |
| const [newUser, setNewUser] = useLocalStorage('newUser', true); |
| const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed); |
| const [fullCollapse, setFullCollapse] = useState(fullPanelCollapse); |
| const [collapsedSize, setCollapsedSize] = useState(navCollapsedSize); |
| |
| |
| |
| |
| |
| |
|
|
| const isSmallScreen = useMediaQuery('(max-width: 767px)'); |
| |
| |
| |
|
|
| const panelRef = useRef<ImperativePanelHandle>(null); |
|
|
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
|
|
| |
| const throttledSaveLayout = useCallback( |
| throttle((sizes: number[]) => { |
| localStorage.setItem('react-resizable-panels:layout', JSON.stringify(sizes)); |
| }, 350), |
| [], |
| ); |
|
|
| useEffect(() => { |
| if (isSmallScreen) { |
| setIsCollapsed(true); |
| setCollapsedSize(0); |
| setMinSize(defaultMinSize); |
| setFullCollapse(true); |
| localStorage.setItem('fullPanelCollapse', 'true'); |
| panelRef.current?.collapse(); |
| return; |
| } else { |
| setIsCollapsed(defaultCollapsed); |
| setCollapsedSize(navCollapsedSize); |
| setMinSize(defaultMinSize); |
| } |
| }, [isSmallScreen, defaultCollapsed, navCollapsedSize, fullPanelCollapse]); |
|
|
| const toggleNavVisible = useCallback(() => { |
| if (newUser) { |
| setNewUser(false); |
| } |
| setIsCollapsed((prev: boolean) => { |
| if (prev) { |
| setMinSize(defaultMinSize); |
| setCollapsedSize(navCollapsedSize); |
| setFullCollapse(false); |
| localStorage.setItem('fullPanelCollapse', 'false'); |
| } |
| return !prev; |
| }); |
| if (!isCollapsed) { |
| panelRef.current?.collapse(); |
| } else { |
| panelRef.current?.expand(); |
| } |
| }, [isCollapsed, newUser, setNewUser, navCollapsedSize]); |
|
|
| return ( |
| <> |
| <TooltipProvider delayDuration={0}> |
| <ResizablePanelGroup |
| direction="horizontal" |
| onLayout={(sizes: number[]) => throttledSaveLayout(sizes)} |
| className="transition-width relative h-full w-full flex-1 overflow-auto bg-white dark:bg-gray-800" |
| > |
| <ResizablePanel defaultSize={defaultLayout[0]} minSize={30}> |
| {children} |
| </ResizablePanel> |
| {/* <TooltipProvider delayDuration={400}> |
| <Tooltip> |
| <div |
| onMouseEnter={() => setIsHovering(true)} |
| onMouseLeave={() => setIsHovering(false)} |
| className="relative flex w-px items-center justify-center" |
| > |
| <NavToggle |
| navVisible={!isCollapsed} |
| isHovering={isHovering} |
| onToggle={toggleNavVisible} |
| setIsHovering={setIsHovering} |
| className={cn( |
| 'fixed top-1/2', |
| (isCollapsed && (minSize === 0 || collapsedSize === 0)) || fullCollapse |
| ? 'mr-9' |
| : 'mr-16', |
| )} |
| translateX={false} |
| side="right" |
| /> |
| </div> |
| </Tooltip> |
| </TooltipProvider> */} |
| {/* {(!isCollapsed || minSize > 0) && !isSmallScreen && !fullCollapse && ( |
| <ResizableHandleAlt withHandle className="bg-transparent dark:text-white" /> |
| )} */} |
| {/* <ResizablePanel |
| collapsedSize={collapsedSize} |
| defaultSize={defaultLayout[1]} |
| collapsible={true} |
| minSize={minSize} |
| maxSize={40} |
| ref={panelRef} |
| style={{ |
| overflowY: 'auto', |
| transition: 'width 0.2s ease, visibility 0s linear 0.2s', |
| }} |
| onExpand={() => { |
| setIsCollapsed(false); |
| localStorage.setItem('react-resizable-panels:collapsed', 'false'); |
| }} |
| onCollapse={() => { |
| setIsCollapsed(true); |
| localStorage.setItem('react-resizable-panels:collapsed', 'true'); |
| }} |
| className={cn( |
| 'sidenav hide-scrollbar border-l border-border-light bg-surface-primary-alt transition-opacity', |
| isCollapsed ? 'min-w-[50px]' : 'min-w-[340px] sm:min-w-[352px]', |
| (isSmallScreen && isCollapsed && (minSize === 0 || collapsedSize === 0)) || |
| fullCollapse |
| ? 'hidden min-w-0' |
| : 'opacity-100', |
| )} |
| > |
| {interfaceConfig.modelSelect && ( |
| <div |
| className={cn( |
| 'sticky left-0 right-0 top-0 z-[100] flex h-[52px] flex-wrap items-center justify-center bg-surface-primary-alt', |
| isCollapsed ? 'h-[52px]' : 'px-2', |
| )} |
| > |
| <Switcher |
| isCollapsed={isCollapsed} |
| endpointKeyProvided={keyProvided} |
| endpoint={endpoint} |
| /> |
| </div> |
| )} |
| <Nav |
| resize={panelRef.current?.resize} |
| isCollapsed={isCollapsed} |
| defaultActive={defaultActive} |
| links={Links} |
| /> |
| </ResizablePanel> */} |
| </ResizablePanelGroup> |
| </TooltipProvider> |
| <div |
| className={`nav-mask${!isCollapsed ? ' active' : ''}`} |
| onClick={() => { |
| setIsCollapsed(() => { |
| localStorage.setItem('fullPanelCollapse', 'true'); |
| setFullCollapse(true); |
| setCollapsedSize(0); |
| setMinSize(0); |
| return false; |
| }); |
| panelRef.current?.collapse(); |
| }} |
| /> |
| </> |
| ); |
| }; |
|
|
| export default memo(SidePanel); |
|
|