import React, { useRef, useState, useEffect, memo } from 'react'; import { Container as SubContainer } from '@/AppBuilder/AppCanvas/Container'; import { resolveWidgetFieldValue, isExpectedDataType } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; import Spinner from '@/_ui/Spinner'; import { useExposeState } from '@/AppBuilder/_hooks/useExposeVariables'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import * as Icons from '@tabler/icons-react'; import { set } from 'lodash'; import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; import Tooltip from 'react-bootstrap/Tooltip'; import OverflowTooltip from '@/_components/OverflowTooltip'; import { TAB_CANVAS_PADDING } from '@/AppBuilder/AppCanvas/appCanvasConstants'; import { useDynamicHeight } from '@/_hooks/useDynamicHeight'; import { shallow } from 'zustand/shallow'; import { getSafeRenderableValue } from '@/Editor/Components/utils'; const tinycolor = require('tinycolor2'); const TabsNavShimmer = ({ divider, headerBackground }) => { return (
{Array(3) .fill(0) .map((ind) => (
))}
); }; export const Tabs = function Tabs({ id, component, width, height, containerProps, removeComponent, setExposedVariable, setExposedVariables, adjustComponentPositions, currentLayout, fireEvent, styles, darkMode, dataCy, properties, }) { const { tabWidth, boxShadow } = styles; const { isDisabled, isVisible, isLoading } = useExposeState( properties.loadingState, properties.visibility, properties.disabledState, setExposedVariables, setExposedVariable ); const { defaultTab, hideTabs, renderOnlyActiveTab, useDynamicOptions, dynamicHeight } = properties; const setSelectedComponents = useStore((state) => state.setSelectedComponents); const widgetVisibility = styles?.visibility ?? true; const disabledState = styles?.disabledState ?? false; // config for tabs. Includes title const tabs = isExpectedDataType(properties.tabs, 'array'); let parsedTabs = tabs; if (!useDynamicOptions) { parsedTabs = properties.tabItems; } else { parsedTabs = resolveWidgetFieldValue(parsedTabs); } parsedTabs = parsedTabs ?.filter((tab) => tab.visible !== false) ?.map((parsedTab, index) => ({ ...parsedTab, id: parsedTab.id ? parsedTab.id : index, })); const highlightColor = styles?.highlightColor ?? '#f44336'; let parsedHighlightColor = highlightColor; parsedHighlightColor = resolveWidgetFieldValue(highlightColor); const headerBackground = styles?.headerBackground ?? '#fff'; const unselectedText = styles?.unselectedText ?? '#6A727C'; const selectedText = styles?.selectedText ?? '#fff'; const hoverBackground = styles?.hoverBackground ?? '#F1F3F4'; const unselectedIcon = styles?.unselectedIcon ?? '#6A727C'; const selectedIcon = styles?.selectedIcon ?? '#fff'; const accent = styles?.accent ?? '#3c92dc'; const divider = styles?.divider ?? '#CCD1D5'; const borderRadius = styles?.borderRadius ?? '0px'; const border = styles?.border === '#CCD1D5' ? false : styles?.border; const padding = styles?.padding ?? 'none'; const transition = styles?.transition ?? 'none'; // Default tab let parsedDefaultTab = defaultTab; const defaultTabExists = parsedTabs?.some((tab) => tab.id === parsedDefaultTab); if (!defaultTabExists && parsedTabs.length > 0) { parsedDefaultTab = parsedTabs[0].id; } const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState) : disabledState; const parsedHideTabs = typeof hideTabs !== 'boolean' ? resolveWidgetFieldValue(hideTabs) : hideTabs; const parsedRenderOnlyActiveTab = typeof renderOnlyActiveTab !== 'boolean' ? resolveWidgetFieldValue(renderOnlyActiveTab) : renderOnlyActiveTab; let parsedWidgetVisibility = widgetVisibility; try { parsedWidgetVisibility = resolveWidgetFieldValue(parsedWidgetVisibility); } catch (err) { console.log(err); } const parentRef = useRef(null); const [currentTab, setCurrentTab] = useState(parsedDefaultTab); const componentCount = useStore( (state) => state.getContainerChildrenMapping(`${id}-${currentTab}`)?.length || 0, shallow ); const [tabItems, setTabItems] = useState(parsedTabs); const tabItemsRef = useRef(tabItems); const [bgColor, setBgColor] = useState('#fff'); useDynamicHeight({ dynamicHeight, id, height, adjustComponentPositions, currentLayout, isContainer: true, value: currentTab, componentCount, visibility: widgetVisibility, }); useEffect(() => { setCurrentTab(parsedDefaultTab); }, [parsedDefaultTab]); useEffect(() => { if (JSON.stringify(tabItemsRef.current) !== JSON.stringify(parsedTabs)) { setTabItems(parsedTabs); tabItemsRef.current = parsedTabs; } }, [parsedTabs]); useEffect(() => { const currentTabData = parsedTabs.filter((tab) => tab.id == currentTab); setBgColor(currentTabData[0]?.backgroundColor ? currentTabData[0]?.backgroundColor : darkMode ? '#324156' : '#fff'); // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentTab, darkMode, parsedTabs]); useEffect(() => { const exposedVariables = { setTab: async function (id) { if (currentTab != id) { setCurrentTab(id); setExposedVariable('currentTab', id); fireEvent('onTabSwitch'); setSelectedComponents([]); } }, setTabDisable: async function (id, value) { setTabItems((prevTabItems) => { return prevTabItems.map((tab) => { if (tab.id == id) { return { ...tab, disable: value }; } return tab; }); }); setSelectedComponents([]); }, setTabLoading: async function (id, value) { setTabItems((prevTabItems) => { return prevTabItems.map((tab) => { if (tab.id == id) { return { ...tab, loading: value }; } return tab; }); }); }, setTabVisibility: async function (id, value) { setTabItems((prevTabItems) => { return prevTabItems.map((tab) => { if (tab.id == id) { return { ...tab, visible: value }; } return tab; }); }); }, currentTab: currentTab, }; setExposedVariables(exposedVariables); // eslint-disable-next-line react-hooks/exhaustive-deps }, [setCurrentTab, currentTab]); const containerRef = useRef(null); const tabsRef = React.useRef(null); const [canScrollLeft, setCanScrollLeft] = useState(false); const [canScrollRight, setCanScrollRight] = useState(false); const [canScroll, setCanScroll] = useState(false); const [isHovered, setIsHovered] = useState(false); const [hoveredTabId, setHoveredTabId] = useState(null); const [isTransitioning, setIsTransitioning] = useState(false); const checkScroll = () => { if (tabsRef.current) { const { scrollLeft, scrollWidth, clientWidth } = tabsRef.current; setCanScroll(scrollLeft > 0 || scrollLeft + clientWidth < scrollWidth); setCanScrollLeft(scrollLeft > 0); setCanScrollRight(scrollLeft + clientWidth < scrollWidth); } }; const scrollTabs = (direction) => { if (tabsRef.current) { const scrollAmount = tabsRef.current.clientWidth / 2; tabsRef.current.scrollBy({ left: direction === 'left' ? -scrollAmount : scrollAmount, behavior: 'smooth', }); } checkScroll(); }; useEffect(() => { checkScroll(); const onScroll = () => checkScroll(); const currentTabsRef = tabsRef.current; if (currentTabsRef) { currentTabsRef.addEventListener('scroll', onScroll); } return () => { if (currentTabsRef) { currentTabsRef.removeEventListener('scroll', onScroll); } }; }, [tabsRef.current, tabWidth, tabItems]); useEffect(() => { checkScroll(); const resizeObserver = new ResizeObserver(() => { checkScroll(); }); if (containerRef.current) { resizeObserver.observe(containerRef.current); } return () => { if (containerRef.current) { resizeObserver.unobserve(containerRef.current); } }; }, []); function shouldRenderTabContent(tab) { if (parsedRenderOnlyActiveTab) { return tab.id == currentTab; } return true; } const findTabIndex = (tabId) => { return tabItems.findIndex((tab) => tab.id === tabId); }; const handleMouseEnter = (id) => { setHoveredTabId(id); setIsHovered(true); }; const handleMouseLeave = () => { setIsHovered(false); }; function getTabIcon(tab) { const iconName = tab?.icon; // eslint-disable-next-line import/namespace const IconElement = Icons[iconName] == undefined ? Icons['IconHome2'] : Icons[iconName]; return tab?.iconVisibility ? ( ) : null; } const equalSplitWidth = 100 / tabItems?.length || 1; const someTabsVisible = tabItems?.filter((tab) => tab?.visible !== false); return (
{isLoading ? ( ) : (
0 && `0.5px solid ${divider}`, alignItems: 'center', width: '100%', backgroundColor: headerBackground, height: '50px', display: parsedHideTabs ? 'none' : 'flex', }} > {canScroll && (
scrollTabs('left')} style={{ cursor: canScrollLeft ? 'pointer' : 'default' }} >
)} {/* this started change */}
    {tabItems ?.filter((tab) => tab?.visible !== false) ?.map((tab) => (
  • { if (currentTab == tab.id) return; if (tab?.disable) return; if (transition !== 'none') { setIsTransitioning(true); setTimeout(() => setIsTransitioning(false), 300); // Match transition duration } !tab?.disabled && setCurrentTab(tab.id); !tab?.disabled && setExposedVariable('currentTab', tab.id); fireEvent('onTabSwitch'); }} onMouseEnter={() => handleMouseEnter(tab?.id)} onMouseLeave={handleMouseLeave} ref={(el) => { if (el && currentTab == tab.id) { el.style.setProperty('color', parsedHighlightColor, 'important'); } }} key={tab.id} >
    {tabWidth === 'split' ? ( <> {getTabIcon(tab)} <>{getSafeRenderableValue(tab.title)} ) : ( {getTabIcon(tab)} {getSafeRenderableValue(tab.title)} )}
  • ))}
{/* this ended change */} {canScroll && (
scrollTabs('right')} style={{ cursor: canScrollRight ? 'pointer' : 'default' }} >
)}
)} {isLoading ? (
) : (
{transition === 'none' ? ( // Simple show/hide when no transition tabItems.map((tab) => (
{shouldRenderTabContent(tab) && ( )}
)) ) : ( // Sliding animation when transition is enabled
{tabItems.map((tab) => (
{shouldRenderTabContent(tab) && ( )}
))}
)}
)}
); }; const areEqual = (prevProps, nextProps) => { const allKeys = new Set([...Object.keys(prevProps), ...Object.keys(nextProps)]); let hasChanges = false; for (let key of allKeys) { if (prevProps[key] !== nextProps[key]) { hasChanges = true; } } return !hasChanges; }; const TabContent = memo(function TabContent({ id, tab, height, width, parsedHideTabs, bgColor, darkMode, dynamicHeight, currentTab, isTransitioning, }) { const loading = tab?.loading; const disable = tab?.disable; const visible = tab?.visible; const fieldBackgroundColor = tab?.fieldBackgroundColor; if (visible === false) return null; return (
{loading ? (
) : ( )}
); }, areEqual);