Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 45 additions & 25 deletions src/Shared/Components/TabGroup/TabGroup.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => (
<motion.div layout="position" layoutId={layoutId} className="underline bcb-5" />
)

const Tab = ({
label,
props,
Expand All @@ -33,7 +39,6 @@ const Tab = ({
icon,
size,
badge = null,
alignActiveBorderWithContainer,
hideTopPadding,
showIndicator,
showError,
Expand All @@ -42,10 +47,19 @@ const Tab = ({
description,
shouldWrapTooltip,
tooltipProps,
}: TabProps & Pick<TabGroupProps, 'size' | 'alignActiveBorderWithContainer' | 'hideTopPadding'>) => {
uniqueGroupId,
}: TabProps & Pick<TabGroupProps, 'size' | 'hideTopPadding'> & 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)
Comment thread
RohitRaj011 marked this conversation as resolved.

const isTabActive = tabType === 'button' ? active : !!match

const { tabClassName, iconClassName, badgeClassName } = getClassNameBySizeMap({
hideTopPadding,
alignActiveBorderWithContainer,
isTabActive,
})[size]

const onClickHandler = (
Expand Down Expand Up @@ -121,9 +135,10 @@ const Tab = ({

const renderTabContainer = () => (
<li
className={`tab-group__tab lh-20 ${active ? 'tab-group__tab--active cb-5 fw-6' : 'cn-9 fw-4'} ${alignActiveBorderWithContainer ? 'tab-group__tab--align-active-border' : ''} ${tabType === 'block' ? 'tab-group__tab--block' : ''} ${disabled ? 'dc__disabled' : 'cursor'}`}
className={`tab-group__tab lh-20 ${active ? 'cb-5 fw-6' : 'cn-9 fw-4'} ${tabType === 'block' ? 'tab-group__tab--block' : ''} ${disabled ? 'dc__disabled' : 'cursor'}`}
>
{getTabComponent()}
{isTabActive && <MotionLayoutUnderline layoutId={uniqueGroupId} />}
</li>
)

Expand All @@ -138,22 +153,27 @@ export const TabGroup = ({
tabs = [],
size = ComponentSizeType.large,
rightComponent,
alignActiveBorderWithContainer,
hideTopPadding,
}: TabGroupProps) => (
<div className="flexbox dc__align-items-center dc__content-space">
<ul role="tablist" className={`tab-group flexbox dc__align-items-center p-0 m-0 ${tabGroupClassMap[size]}`}>
{tabs.map(({ id, ...resProps }) => (
<Tab
key={id}
id={id}
size={size}
alignActiveBorderWithContainer={alignActiveBorderWithContainer}
hideTopPadding={hideTopPadding}
{...resProps}
/>
))}
</ul>
{rightComponent || null}
</div>
)
}: 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('-'), [])
Comment thread
arunjaindev marked this conversation as resolved.

return (
<div className="flexbox dc__align-items-center dc__content-space">
<ul role="tablist" className={`tab-group flexbox dc__align-items-center p-0 m-0 ${tabGroupClassMap[size]}`}>
{tabs.map(({ id, ...resProps }) => (
<Tab
key={id}
id={id}
size={size}
hideTopPadding={hideTopPadding}
uniqueGroupId={uniqueGroupId}
{...resProps}
/>
))}
</ul>
{rightComponent || null}
</div>
)
}
13 changes: 13 additions & 0 deletions src/Shared/Components/TabGroup/TabGroup.helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -65,3 +67,14 @@ export const getTabDescription = (description: TabProps['description']) =>
: description}
</ul>
)

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 ''
}
26 changes: 3 additions & 23 deletions src/Shared/Components/TabGroup/TabGroup.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -58,14 +52,6 @@
}
}

&--active {
@include svg-styles(var(--B500));
Comment thread
RohitRaj011 marked this conversation as resolved.

&::after {
background-color: var(--B500);
}
}

&__badge {
border-radius: 10px;
min-width: 20px;
Expand Down Expand Up @@ -104,11 +90,5 @@
color: var(--B500);
}
}

&:has(.active) {
&::after {
background-color: var(--B500);
}
}
}
}
9 changes: 4 additions & 5 deletions src/Shared/Components/TabGroup/TabGroup.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
10 changes: 5 additions & 5 deletions src/Shared/Components/TabGroup/TabGroup.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { TabGroupProps } from './TabGroup.types'

export const getClassNameBySizeMap = ({
hideTopPadding,
alignActiveBorderWithContainer,
}: Pick<TabGroupProps, 'hideTopPadding' | 'alignActiveBorderWithContainer'>): Record<
isTabActive,
}: Pick<TabGroupProps, 'hideTopPadding'> & { isTabActive: boolean }): Record<
TabGroupProps['size'],
{
tabClassName: string
Expand All @@ -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',
},
Expand Down