diff --git a/src/Shared/Components/TabGroup/TabGroup.component.tsx b/src/Shared/Components/TabGroup/TabGroup.component.tsx index 801a4d806..d7bcba5f8 100644 --- a/src/Shared/Components/TabGroup/TabGroup.component.tsx +++ b/src/Shared/Components/TabGroup/TabGroup.component.tsx @@ -14,17 +14,23 @@ * limitations under the License. */ -import { Link, NavLink } from 'react-router-dom' +import { useMemo } from 'react' +import { Link, NavLink, useRouteMatch } from 'react-router-dom' +import { motion } from 'framer-motion' import { Tooltip } from '@Common/Tooltip' import { ComponentSizeType } from '@Shared/constants' -import { getTabBadge, getTabDescription, getTabIcon, getTabIndicator } from './TabGroup.helpers' -import { TabGroupProps, TabProps } from './TabGroup.types' +import { getPathnameToMatch, getTabBadge, getTabDescription, getTabIcon, getTabIndicator } from './TabGroup.helpers' +import { AdditionalTabProps, TabGroupProps, TabProps } from './TabGroup.types' import { getClassNameBySizeMap, tabGroupClassMap } from './TabGroup.utils' import './TabGroup.scss' +const MotionLayoutUnderline = ({ layoutId }: { layoutId: string }) => ( + +) + const Tab = ({ label, props, @@ -33,7 +39,6 @@ const Tab = ({ icon, size, badge = null, - alignActiveBorderWithContainer, hideTopPadding, showIndicator, showError, @@ -42,10 +47,19 @@ const Tab = ({ description, shouldWrapTooltip, tooltipProps, -}: TabProps & Pick) => { + uniqueGroupId, +}: TabProps & Pick & AdditionalTabProps) => { + const { path } = useRouteMatch() + const pathToMatch = tabType === 'navLink' || tabType === 'link' ? getPathnameToMatch(props.to, path) : '' + + // using match to define if tab is active as useRouteMatch return an object if path is matched otherwise return null/undefined + const match = useRouteMatch(pathToMatch) + + const isTabActive = tabType === 'button' ? active : !!match + const { tabClassName, iconClassName, badgeClassName } = getClassNameBySizeMap({ hideTopPadding, - alignActiveBorderWithContainer, + isTabActive, })[size] const onClickHandler = ( @@ -121,9 +135,10 @@ const Tab = ({ const renderTabContainer = () => (
  • {getTabComponent()} + {isTabActive && }
  • ) @@ -138,22 +153,27 @@ export const TabGroup = ({ tabs = [], size = ComponentSizeType.large, rightComponent, - alignActiveBorderWithContainer, hideTopPadding, -}: TabGroupProps) => ( -
    -
      - {tabs.map(({ id, ...resProps }) => ( - - ))} -
    - {rightComponent || null} -
    -) +}: TabGroupProps) => { + // Unique layoutId for motion.div to handle multiple tab groups on same page + // Using tab labels so that id remains same on re mount as well + const uniqueGroupId = useMemo(() => tabs.map((tab) => tab.label).join('-'), []) + + return ( +
    +
      + {tabs.map(({ id, ...resProps }) => ( + + ))} +
    + {rightComponent || null} +
    + ) +} diff --git a/src/Shared/Components/TabGroup/TabGroup.helpers.tsx b/src/Shared/Components/TabGroup/TabGroup.helpers.tsx index 3d515b910..9aec1c1da 100644 --- a/src/Shared/Components/TabGroup/TabGroup.helpers.tsx +++ b/src/Shared/Components/TabGroup/TabGroup.helpers.tsx @@ -14,6 +14,8 @@ * limitations under the License. */ +import { LinkProps, NavLinkProps } from 'react-router-dom' + import { ReactComponent as ICErrorExclamation } from '@Icons/ic-error-exclamation.svg' import { ReactComponent as ICWarning } from '@Icons/ic-warning.svg' @@ -65,3 +67,14 @@ export const getTabDescription = (description: TabProps['description']) => : description} ) + +const replaceTrailingSlash = (pathname: string) => pathname.replace(/\/+$/, '') + +export const getPathnameToMatch = (to: NavLinkProps['to'] | LinkProps['to'], currentPathname: string): string => { + if (typeof to === 'string' || (to && typeof to === 'object' && 'pathname' in to)) { + const pathname = typeof to === 'string' ? to : to.pathname || '' + // handling absolute and relative paths + return pathname.startsWith('/') ? pathname : `${replaceTrailingSlash(currentPathname)}/${pathname}` + } + return '' +} diff --git a/src/Shared/Components/TabGroup/TabGroup.scss b/src/Shared/Components/TabGroup/TabGroup.scss index c5a352898..2ddaaf6b9 100644 --- a/src/Shared/Components/TabGroup/TabGroup.scss +++ b/src/Shared/Components/TabGroup/TabGroup.scss @@ -33,20 +33,14 @@ @include svg-styles(var(--N700)); - &::after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - width: 100%; + .underline { height: 2px; - background-color: transparent; border-top-left-radius: 2px; border-top-right-radius: 2px; } - &--align-active-border::after { - bottom: -1px; + &--active { + @include svg-styles(var(--B500)); } &:hover:not(.tab-group__tab--block):not(.dc__disabled) { @@ -58,14 +52,6 @@ } } - &--active { - @include svg-styles(var(--B500)); - - &::after { - background-color: var(--B500); - } - } - &__badge { border-radius: 10px; min-width: 20px; @@ -104,11 +90,5 @@ color: var(--B500); } } - - &:has(.active) { - &::after { - background-color: var(--B500); - } - } } } diff --git a/src/Shared/Components/TabGroup/TabGroup.types.ts b/src/Shared/Components/TabGroup/TabGroup.types.ts index 0ddb160f9..022b83254 100644 --- a/src/Shared/Components/TabGroup/TabGroup.types.ts +++ b/src/Shared/Components/TabGroup/TabGroup.types.ts @@ -152,14 +152,13 @@ export interface TabGroupProps { * Optional component to be rendered on the right side of the tab list. */ rightComponent?: React.ReactElement - /** - * Set to `true` to align the active tab's border with the bottom border of the parent container. - * @default false - */ - alignActiveBorderWithContainer?: boolean /** * Determines if the top padding of the tab group should be hidden. * @default false */ hideTopPadding?: boolean } + +export type AdditionalTabProps = { + uniqueGroupId: string +} diff --git a/src/Shared/Components/TabGroup/TabGroup.utils.ts b/src/Shared/Components/TabGroup/TabGroup.utils.ts index e2f73bb93..5bc8aeb6f 100644 --- a/src/Shared/Components/TabGroup/TabGroup.utils.ts +++ b/src/Shared/Components/TabGroup/TabGroup.utils.ts @@ -21,8 +21,8 @@ import { TabGroupProps } from './TabGroup.types' export const getClassNameBySizeMap = ({ hideTopPadding, - alignActiveBorderWithContainer, -}: Pick): Record< + isTabActive, +}: Pick & { isTabActive: boolean }): Record< TabGroupProps['size'], { tabClassName: string @@ -31,17 +31,17 @@ export const getClassNameBySizeMap = ({ } > => ({ [ComponentSizeType.medium]: { - tabClassName: `fs-12 ${!hideTopPadding ? 'pt-6' : ''} ${alignActiveBorderWithContainer ? 'pb-5' : 'pb-6'}`, + tabClassName: `fs-12 ${!hideTopPadding ? 'pt-6' : ''} ${isTabActive ? 'pb-3' : 'pb-5'}`, iconClassName: 'icon-dim-14', badgeClassName: 'fs-11 lh-18 tab-group__tab__badge--medium', }, [ComponentSizeType.large]: { - tabClassName: `fs-13 ${!hideTopPadding ? 'pt-8' : ''} ${alignActiveBorderWithContainer ? 'pb-7' : 'pb-8'}`, + tabClassName: `fs-13 ${!hideTopPadding ? 'pt-8' : ''} ${isTabActive ? 'pb-5' : 'pb-7'}`, iconClassName: 'icon-dim-16', badgeClassName: 'fs-12 lh-20', }, [ComponentSizeType.xl]: { - tabClassName: `min-w-200 fs-13 ${!hideTopPadding ? 'pt-10' : ''} ${alignActiveBorderWithContainer ? 'pb-9' : 'pb-10'}`, + tabClassName: `min-w-200 fs-13 ${!hideTopPadding ? 'pt-10' : ''} ${isTabActive ? 'pb-7' : 'pb-9'}`, iconClassName: 'icon-dim-16', badgeClassName: 'fs-12 lh-20', },