[Dashboards] - Tabs reordering (#14798)

closes https://github.com/twentyhq/core-team-issues/issues/1544

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
nitin
2025-10-23 22:58:00 +05:30
committed by GitHub
parent f301f07e43
commit 06a195af41
26 changed files with 1614 additions and 342 deletions
@@ -1,125 +0,0 @@
import isPropValid from '@emotion/is-prop-valid';
import styled from '@emotion/styled';
import { Pill } from '@ui/components/Pill/Pill';
import { Avatar, type IconComponent } from '@ui/display';
import { ThemeContext } from '@ui/theme';
import { type ReactElement, useContext } from 'react';
import { Link } from 'react-router-dom';
const StyledTabButton = styled('button', {
shouldForwardProp: (prop) => isPropValid(prop) && prop !== 'active',
})<{
active?: boolean;
disabled?: boolean;
to?: string;
}>`
all: unset;
align-items: center;
color: ${({ theme, active, disabled }) =>
active
? theme.font.color.primary
: disabled
? theme.font.color.light
: theme.font.color.secondary};
cursor: pointer;
background-color: transparent;
border: none;
font-family: inherit;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
justify-content: center;
pointer-events: ${({ disabled }) => (disabled ? 'none' : '')};
text-decoration: none;
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background-color: ${({ theme, active }) =>
active ? theme.border.color.inverted : 'transparent'};
z-index: 1;
}
`;
const StyledTabHover = styled.span<{
contentSize?: 'sm' | 'md';
}>`
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
padding: ${({ theme, contentSize }) =>
contentSize === 'sm'
? `${theme.spacing(1)} ${theme.spacing(2)}`
: `${theme.spacing(2)} ${theme.spacing(2)}`};
font-weight: ${({ theme }) => theme.font.weight.medium};
width: 100%;
white-space: nowrap;
border-radius: ${({ theme }) => theme.border.radius.sm};
&:hover {
background: ${({ theme }) => theme.background.tertiary};
}
&:active {
background: ${({ theme }) => theme.background.quaternary};
}
`;
type TabButtonProps = {
id: string;
active?: boolean;
disabled?: boolean;
to?: string;
LeftIcon?: IconComponent;
className?: string;
title?: string;
onClick?: () => void;
logo?: string;
RightIcon?: IconComponent;
pill?: string | ReactElement;
contentSize?: 'sm' | 'md';
disableTestId?: boolean;
};
export const TabButton = ({
id,
active,
disabled,
to,
LeftIcon,
className,
title,
onClick,
logo,
RightIcon,
pill,
contentSize = 'sm',
disableTestId = false,
}: TabButtonProps) => {
const { theme } = useContext(ThemeContext);
const iconColor = active
? theme.font.color.primary
: disabled
? theme.font.color.extraLight
: theme.font.color.secondary;
return (
<StyledTabButton
data-testid={disableTestId ? undefined : `tab-${id}`}
active={active}
disabled={disabled}
as={to ? Link : 'button'}
to={to}
className={className}
onClick={onClick}
>
<StyledTabHover contentSize={contentSize}>
{LeftIcon && <LeftIcon color={iconColor} size={theme.icon.size.md} />}
{logo && <Avatar avatarUrl={logo} size="md" placeholder={title} />}
{title}
{RightIcon && <RightIcon color={iconColor} size={theme.icon.size.md} />}
{pill && (typeof pill === 'string' ? <Pill label={pill} /> : pill)}
</StyledTabHover>
</StyledTabButton>
);
};
@@ -0,0 +1,61 @@
import { type IconComponent } from '@ui/display';
import { StyledTabButton } from '@ui/input/button/components/TabButton/internals/components/StyledTabBase';
import { TabContent } from '@ui/input/button/components/TabButton/internals/components/TabContent';
import { type ReactElement } from 'react';
import { Link } from 'react-router-dom';
type TabButtonProps = {
id: string;
active?: boolean;
disabled?: boolean;
to?: string;
LeftIcon?: IconComponent;
className?: string;
title?: string;
onClick?: () => void;
logo?: string;
RightIcon?: IconComponent;
pill?: string | ReactElement;
contentSize?: 'sm' | 'md';
disableTestId?: boolean;
};
export const TabButton = ({
id,
active,
disabled,
to,
LeftIcon,
className,
title,
onClick,
logo,
RightIcon,
pill,
contentSize = 'sm',
disableTestId = false,
}: TabButtonProps) => {
return (
<StyledTabButton
data-testid={disableTestId ? undefined : `tab-${id}`}
active={active}
disabled={disabled}
as={to ? Link : 'button'}
to={to}
className={className}
onClick={onClick}
>
<TabContent
id={id}
active={active}
disabled={disabled}
LeftIcon={LeftIcon}
title={title}
logo={logo}
RightIcon={RightIcon}
pill={pill}
contentSize={contentSize}
/>
</StyledTabButton>
);
};
@@ -0,0 +1,93 @@
import isPropValid from '@emotion/is-prop-valid';
import styled from '@emotion/styled';
export const StyledTabButton = styled('button', {
shouldForwardProp: (prop) => isPropValid(prop) && prop !== 'active',
})<{
active?: boolean;
disabled?: boolean;
to?: string;
}>`
all: unset;
align-items: center;
color: ${({ theme, active, disabled }) =>
active
? theme.font.color.primary
: disabled
? theme.font.color.light
: theme.font.color.secondary};
cursor: pointer;
background-color: transparent;
border: none;
font-family: inherit;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
justify-content: center;
pointer-events: ${({ disabled }) => (disabled ? 'none' : '')};
text-decoration: none;
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background-color: ${({ theme, active }) =>
active ? theme.border.color.inverted : 'transparent'};
z-index: 1;
}
`;
export const StyledTabContainer = styled.div<{
active?: boolean;
disabled?: boolean;
}>`
align-items: center;
color: ${({ theme, active, disabled }) =>
active
? theme.font.color.primary
: disabled
? theme.font.color.light
: theme.font.color.secondary};
cursor: pointer;
background-color: transparent;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
justify-content: center;
text-decoration: none;
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background-color: ${({ theme, active }) =>
active ? theme.border.color.inverted : 'transparent'};
z-index: 1;
}
`;
export const StyledTabHover = styled.span<{
contentSize?: 'sm' | 'md';
}>`
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
padding: ${({ theme, contentSize }) =>
contentSize === 'sm'
? `${theme.spacing(1)} ${theme.spacing(2)}`
: `${theme.spacing(2)} ${theme.spacing(2)}`};
font-weight: ${({ theme }) => theme.font.weight.medium};
width: 100%;
white-space: nowrap;
border-radius: ${({ theme }) => theme.border.radius.sm};
&:hover {
background: ${({ theme }) => theme.background.tertiary};
}
&:active {
background: ${({ theme }) => theme.background.quaternary};
}
`;
@@ -0,0 +1,45 @@
import { Pill } from '@ui/components/Pill/Pill';
import { Avatar, type IconComponent } from '@ui/display';
import { ThemeContext } from '@ui/theme';
import { type ReactElement, useContext } from 'react';
import { StyledTabHover } from './StyledTabBase';
export type TabContentProps = {
id: string;
active?: boolean;
disabled?: boolean;
LeftIcon?: IconComponent;
title?: string;
logo?: string;
RightIcon?: IconComponent;
pill?: string | ReactElement;
contentSize?: 'sm' | 'md';
};
export const TabContent = ({
active,
disabled,
LeftIcon,
title,
logo,
RightIcon,
pill,
contentSize = 'sm',
}: TabContentProps) => {
const { theme } = useContext(ThemeContext);
const iconColor = active
? theme.font.color.primary
: disabled
? theme.font.color.extraLight
: theme.font.color.secondary;
return (
<StyledTabHover contentSize={contentSize}>
{LeftIcon && <LeftIcon color={iconColor} size={theme.icon.size.md} />}
{logo && <Avatar avatarUrl={logo} size="md" placeholder={title} />}
{title}
{RightIcon && <RightIcon color={iconColor} size={theme.icon.size.md} />}
{pill && (typeof pill === 'string' ? <Pill label={pill} /> : pill)}
</StyledTabHover>
);
};
@@ -8,13 +8,13 @@ import {
IconSettings,
IconUser,
} from '@ui/display';
import { TabButton } from '@ui/input/button/components/TabButton/TabButton';
import {
CatalogDecorator,
type CatalogStory,
ComponentWithRouterDecorator,
RecoilRootDecorator,
} from '@ui/testing';
import { TabButton } from '../TabButton';
// Mimic the TabList container styling for proper positioning
const StyledTabContainer = styled.div`