import classnames from 'classnames'; import React, { useCallback, useEffect, useState } from 'react'; import { Icons } from '../Icons'; import { TooltipTrigger, TooltipContent, Tooltip } from '../Tooltip'; import { Separator } from '../Separator'; /** * SidePanel component properties. * Note that the component monitors changes to the various widths and border sizes and will resize dynamically * @property {boolean} isExpanded - boolean indicating if the side panel is expanded/open or collapsed * @property {number} expandedWidth - the width of this side panel when expanded not including any borders or margins * @property {number} collapsedWidth - the width of this side panel when collapsed not including any borders or margins * @property {number} expandedInsideBorderSize - the width of the space between the expanded side panel content and viewport grid * @property {number} collapsedInsideBorderSize - the width of the space between the collapsed side panel content and the viewport grid * @property {number} collapsedOutsideBorderSize - the width of the space between the collapsed side panel content and the edge of the browser window */ type SidePanelProps = { side: 'left' | 'right'; className: string; activeTabIndex: number; onOpen: () => void; onClose: () => void; onActiveTabIndexChange: () => void; isExpanded: boolean; expandedWidth: number; collapsedWidth: number; expandedInsideBorderSize: number; collapsedInsideBorderSize: number; collapsedOutsideBorderSize: number; tabs: any; }; type StyleMap = { open: { left: { marginLeft: string; // the space between the expanded/open left side panel and the browser window left edge marginRight: string; // the space between the expanded/open left side panel and the viewport grid }; right: { marginLeft: string; // the space between the expanded/open right side panel and the viewport grid marginRight: string; // the space between the expanded/open right side panel and the browser window right edge }; }; closed: { left: { marginLeft: string; // the space between the collapsed/closed left panel and the browser window left edge marginRight: string; // the space between the collapsed/closed left panel and the viewport grid alignItems: 'flex-end'; // the flexbox layout align-items property }; right: { marginLeft: string; // the space between the collapsed/closed right panel and the viewport grid marginRight: string; // the space between the collapsed/closed right panel and the browser window right edge alignItems: 'flex-start'; // the flexbox layout align-items property }; }; }; const closeIconWidth = 30; const gridHorizontalPadding = 10; const tabSpacerWidth = 2; const baseClasses = 'bg-black border-black justify-start box-content flex flex-col'; const openStateIconName = { left: 'SidePanelCloseLeft', right: 'SidePanelCloseRight', }; const getTabWidth = (numTabs: number) => { if (numTabs < 3) { return 68; } else { return 40; } }; const getGridWidth = (numTabs: number, gridAvailableWidth: number) => { const spacersWidth = (numTabs - 1) * tabSpacerWidth; const tabsWidth = getTabWidth(numTabs) * numTabs; if (gridAvailableWidth > tabsWidth + spacersWidth) { return tabsWidth + spacersWidth; } return gridAvailableWidth; }; const getNumGridColumns = (numTabs: number, gridWidth: number) => { if (numTabs === 1) { return 1; } // Start by calculating the number of tabs assuming each tab was accompanied by a spacer. const tabWidth = getTabWidth(numTabs); const numTabsWithOneSpacerEach = Math.floor(gridWidth / (tabWidth + tabSpacerWidth)); // But there is always one less spacer than tabs, so now check if an extra tab with one less spacer fits. if ( (numTabsWithOneSpacerEach + 1) * tabWidth + numTabsWithOneSpacerEach * tabSpacerWidth <= gridWidth ) { return numTabsWithOneSpacerEach + 1; } return numTabsWithOneSpacerEach; }; const getTabClassNames = ( numColumns: number, numTabs: number, tabIndex: number, isActiveTab: boolean, isTabDisabled: boolean ) => classnames('h-[28px] mb-[2px] cursor-pointer text-foreground bg-primary/10 hover:bg-primary/20', { 'hover:text-primary': !isActiveTab && !isTabDisabled, 'rounded-l': tabIndex % numColumns === 0, 'rounded-r': (tabIndex + 1) % numColumns === 0 || tabIndex === numTabs - 1, }); const getTabStyle = (numTabs: number) => { return { width: `${getTabWidth(numTabs)}px`, }; }; const getTabIconClassNames = (numTabs: number, isActiveTab: boolean) => { return classnames('h-full w-full flex items-center justify-center', { 'bg-primary/20': isActiveTab, rounded: isActiveTab, }); }; const createStyleMap = ( expandedWidth: number, expandedInsideBorderSize: number, collapsedWidth: number, collapsedInsideBorderSize: number, collapsedOutsideBorderSize: number ): StyleMap => { const collapsedHideWidth = expandedWidth - collapsedWidth - collapsedOutsideBorderSize; return { open: { left: { marginLeft: '0px', marginRight: `${expandedInsideBorderSize}px` }, right: { marginLeft: `${expandedInsideBorderSize}px`, marginRight: '0px' }, }, closed: { left: { marginLeft: `-${collapsedHideWidth}px`, marginRight: `${collapsedInsideBorderSize}px`, alignItems: `flex-end`, }, right: { marginLeft: `${collapsedInsideBorderSize}px`, marginRight: `-${collapsedHideWidth}px`, alignItems: `flex-start`, }, }, }; }; const getToolTipContent = (label: string, disabled: boolean) => { return ( <>