diff --git a/packages/kaspersky-components/src/tabs/Tabs.tsx b/packages/kaspersky-components/src/tabs/Tabs.tsx index 7b4c877..3df9e18 100644 --- a/packages/kaspersky-components/src/tabs/Tabs.tsx +++ b/packages/kaspersky-components/src/tabs/Tabs.tsx @@ -11,6 +11,7 @@ import { Tabs as AntdTabs } from 'antd' import cn from 'classnames' import React, { ReactElement, + ReactNode, FC, Children, isValidElement, @@ -31,6 +32,7 @@ import { tabPaneHeadCss, StyledTabPaneIcon, StyledTabPaneText, + StyledExtraContent, StyledTabPaneIndicator } from './tabsCss' import { TabsDropdown } from './TabsDropdown' @@ -43,11 +45,14 @@ import { TabPaneHeaderProps, GroupTabsProps, TabsVariants, + ComplexTabBarExtraContent, StyledTabPanedHeadProps } from './types' import { useThemedTabs } from './useThemedTabs' import { createGroupTabPane, extractTabPanes } from './utils' +export const TABS_GAP = 4 + const StyledTabs = styled(AntdTabs).withConfig({ shouldForwardProp: (prop) => !['cssConfig', 'hiddenTabsLength', 'containerWidth'].includes(prop as string) })` @@ -121,21 +126,23 @@ export const GroupTabHeader: FC = (props: TabPaneHeaderProps GroupTabs.TabPaneHeader = GroupTabHeader const TabViewComponent: FC & Omit = ({ - cssConfig, - tabPosition = 'top', - type = 'line', - destroyInactiveTabPane = false, - children, - activeKey, - defaultActiveKey, - onChange, - testId, - testAttributes, - className, - rootHashClass, - ...props - }: TabsViewProps) => { + cssConfig, + tabPosition = 'top', + type = 'line', + destroyInactiveTabPane = false, + children, + activeKey, + defaultActiveKey, + onChange, + testId, + testAttributes, + className, + tabBarExtraContent, + rootHashClass, + ...props +}: TabsViewProps) => { const tabsRef = useRef(null) + const extraContentRef = useRef(null) const activeTab = activeKey ?? defaultActiveKey ?? (children as ReactElement[])[0]?.key ?? '' const [activeTabKey, setActiveTabKey] = useState(activeTab) const [buttonMoreSize, setButtonMoreSize] = useState(0) @@ -155,17 +162,24 @@ const TabViewComponent: FC & Omit = }, [children] ) + let extraContentWidthWithGap = 0 + if (extraContentRef.current) { + extraContentWidthWithGap = extraContentRef.current.clientWidth + TABS_GAP + } + let lastFittingItemIndex = useIntersectionChildren( tabsRef, - buttonMoreSize, + buttonMoreSize + extraContentWidthWithGap, '.ant-tabs-nav-list', recalculateIntersectionCounter ) ?? existingTabs.length if (tabsRef.current) { const tabs = tabsRef.current.querySelectorAll('.ant-tabs-tab') + const extraContentLeft = tabsRef.current.querySelector('.ant-tabs-extra-content') + const extraContentLeftWidth = extraContentLeft ? (extraContentLeft.clientWidth + TABS_GAP) : 0 const lastTab = tabs[tabs.length - 1] - if (lastTab && ((lastTab as HTMLElement).offsetLeft + lastTab.clientWidth < containerWidth)) { + if (lastTab && ((lastTab as HTMLElement).offsetLeft + lastTab.clientWidth + extraContentWidthWithGap + extraContentLeftWidth < containerWidth)) { lastFittingItemIndex = existingTabs.length } } @@ -218,6 +232,25 @@ const TabViewComponent: FC & Omit = } }, []) + const tabBarExtraContentLeft: ComplexTabBarExtraContent = { left: undefined } + let tabBarExtraContentRight: ReactNode + if (tabBarExtraContent) { + if (typeof tabBarExtraContent === 'object') { + if ('left' in tabBarExtraContent || 'right' in tabBarExtraContent) { + if ('left' in tabBarExtraContent) { + tabBarExtraContentLeft.left = tabBarExtraContent.left + } + if ('right' in tabBarExtraContent) { + tabBarExtraContentRight = tabBarExtraContent.right + } + } else { + tabBarExtraContentRight = tabBarExtraContent + } + } else { + tabBarExtraContentRight = tabBarExtraContent + } + } + return ( & Omit = className={cn(className, rootHashClass)} cssConfig={cssConfig} selectedMoreButton={isMoreButtonActive} + extraContentWidth={extraContentWidthWithGap} shouldShowMoreButton={shouldShowMoreButton} > & Omit = tabPosition={tabPosition} destroyInactiveTabPane={destroyInactiveTabPane} {...props} + tabBarExtraContent={tabBarExtraContentLeft} activeKey={activeTabKey as string} onChange={onTabChange} - hiddenTabsLength={existingTabs.length - (lastFittingItemIndex || existingTabs.length)} + hiddenTabsLength={existingTabs.length - (lastFittingItemIndex)} containerWidth={containerWidth} {...testAttributes} > {extractTabPanes(existingTabs)} + + {tabBarExtraContentRight} + () @@ -358,10 +360,12 @@ export const tabPaneHeadCss = css<{ cssConfig: TabsCssConfig }>` export const tabsWrapperCss = css<{ cssConfig: TabsCssConfig, + extraContentWidth: number, selectedMoreButton: boolean, shouldShowMoreButton: boolean }>` width: 100%; + position: relative; display: flex; justify-content: space-between; overflow: hidden; @@ -372,13 +376,18 @@ export const tabsWrapperCss = css<{ `} } - .kl6-tabs-more-button{ + .ant-tabs-extra-content { + padding-right: 4px; + } + & ${StyledExtraContent} { + position: absolute; + right: 0px; } - && ${StyledMoreButton} { + & ${StyledMoreButton} { position: absolute; - right: 24px; + right: ${props => (props.extraContentWidth) + 'px'}; color: ${fromProps('unSelected.enabled.color')}; ${props => props.selectedMoreButton && ` color: ${fromProps('selected.enabled.color')(props)}; @@ -396,6 +405,7 @@ export const tabsWrapperCss = css<{ border-radius: 4px; min-width: fit-content; + &::before { display: none; position: absolute; diff --git a/packages/kaspersky-components/src/tabs/types.ts b/packages/kaspersky-components/src/tabs/types.ts index 5b7b82c..fde25eb 100644 --- a/packages/kaspersky-components/src/tabs/types.ts +++ b/packages/kaspersky-components/src/tabs/types.ts @@ -1,3 +1,9 @@ +import { Focus } from '@design-system/tokens/focus' +import { Theme } from '@design-system/types/Theme' +import { TestingProps, ToViewProps } from '@helpers/typesHelpers' +import { IndicatorMode } from '@src/indicator' +import { SpaceProps } from '@src/space' +import { TabPaneProps } from 'antd' import { PropsWithChildren, ReactNode, @@ -6,12 +12,6 @@ import { CSSProperties, FC } from 'react' -import { IndicatorMode } from '@src/indicator' -import { SpaceProps } from '@src/space' -import { Theme } from '@design-system/types/Theme' -import { TestingProps, ToViewProps } from '@helpers/typesHelpers' -import { TabPaneProps } from 'antd' -import { Focus } from '@design-system/tokens/focus' type StateProps = { color?: string, @@ -44,11 +44,13 @@ export type TabsVariants = { TabPaneHead: FC } +export type ComplexTabBarExtraContent = { right?: ReactNode, left?: ReactNode } + export type TabsProps = TabsThemeProps & PropsWithChildren<{ /** @deprecated Tabs type - card tabs are not supported */ type?: 'line' | 'card', /** Extra content at left and right side of tabs. Intends should be set manually (until design review) */ - tabBarExtraContent?: ReactNode | { right?: ReactNode, left?: ReactNode }, + tabBarExtraContent?: ReactNode | ComplexTabBarExtraContent, /** TabPane's className */ className?: string, /** Tabs position */