Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tabs)!: add delete tabs #747

Merged
merged 23 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5f3d5be
feat(tabs): add delete tabs
savutsang Feb 26, 2024
99127fb
fix: panel border color
savutsang Feb 26, 2024
daa2520
fix: border-radius
savutsang Feb 26, 2024
c5a7bb8
fix: comments
savutsang Mar 8, 2024
ebb6bc0
Merge branch 'master' into dev/DS-764
savutsang Mar 11, 2024
c734259
fix: comments
savutsang Mar 15, 2024
959c6e7
fix: comments
savutsang Mar 15, 2024
7f25032
fix: extract scrollable to a hook
savutsang Mar 18, 2024
55ae42a
Merge branch 'master' of https://github.com/kronostechnologies/design…
savutsang Mar 19, 2024
d65c4e1
fix: post-reviewed ui
savutsang Mar 19, 2024
8613509
fix: x focusable seulement le tab active
savutsang Mar 19, 2024
ed09561
fix: focus on panel
savutsang Mar 21, 2024
66eba8e
fix: remove stylelint comment
savutsang Mar 21, 2024
b49b45b
fix: simplify tests and testid
savutsang Mar 22, 2024
bc89408
fix: tabs focus inside
savutsang Mar 27, 2024
a1d0641
fix: post review comments
savutsang Apr 18, 2024
8122639
Merge branch 'master' of https://github.com/kronostechnologies/design…
savutsang Apr 18, 2024
a267b75
fix: better story for tabs
savutsang Apr 18, 2024
a19dd20
Merge branch 'master' into dev/DS-764
savutsang Apr 24, 2024
575c8a6
fix: focus after delete, rename tokens, fix comments
savutsang Apr 29, 2024
f28f563
Merge branch 'master' of https://github.com/kronostechnologies/design…
savutsang Apr 29, 2024
1e414d6
fix: comments
savutsang Apr 30, 2024
dff79dd
Merge branch 'master' of https://github.com/kronostechnologies/design…
savutsang Apr 30, 2024
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
4 changes: 2 additions & 2 deletions packages/react/src/components/carousel/carousel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('Carousel', () => {
it('should add padding slides so looping does not jump', () => {
const wrapper = shallow(<Carousel>{slides}</Carousel>);

const renderedSlides = getByTestId(wrapper, 'carousel-slides', '$');
const renderedSlides = getByTestId(wrapper, 'carousel-slides', { modifier: '$' });

expect(renderedSlides.children().length).toBe(numberOfSlides + 2);
});
Expand Down Expand Up @@ -131,7 +131,7 @@ describe('Carousel', () => {
it('should display one dot per slide', () => {
const wrapper = shallow(<Carousel>{slides}</Carousel>);

const dots = findByTestId(wrapper, 'carousel-dot-', '^');
const dots = findByTestId(wrapper, 'carousel-dot-', { modifier: '^' });

expect(dots.length).toBe(5);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('Pagination', () => {
describe('pages list', () => {
test('should display pages', () => {
const wrapper = shallow(<Pagination resultsPerPage={5} numberOfResults={25} pagesShown={5} />);
const pages = findByTestId(wrapper, 'page-', '^');
const pages = findByTestId(wrapper, 'page-', { modifier: '^' });

expect(pages).toHaveLength(5);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('Progress Component', () => {

const wrapper = mountWithTheme(<ProgressTracker steps={steps} value={1} />);

const allStepsLabels = findByTestId(wrapper, 'progress-tracker-step-', '^')
const allStepsLabels = findByTestId(wrapper, 'progress-tracker-step-', { modifier: '^' })
.map((w) => getByTestId(w, 'progress-tracker-label').text());
expect(allStepsLabels).toEqual(expect.arrayContaining(['Step 1', 'Step 2', 'Step 3']));
});
Expand Down
219 changes: 122 additions & 97 deletions packages/react/src/components/tabs/tab-button.tsx
Original file line number Diff line number Diff line change
@@ -1,90 +1,99 @@
import { forwardRef, KeyboardEvent, ReactElement, Ref } from 'react';
import styled, { css } from 'styled-components';
import { IconButton } from '../buttons/icon-button';
import { useDataAttributes } from '../../hooks/use-data-attributes';
import { useTranslation } from '../../i18n/use-translation';
import { focus, focusVisibleReset } from '../../utils/css-state';
import { useDeviceContext } from '../device-context-provider/device-context-provider';
import { Icon, IconName } from '../icon/icon';

interface IsSelected {
$isSelected: boolean;
}

interface StyledButtonProps extends IsSelected {
$isGlobal?: boolean;
$isMobile: boolean;
}
const selectedIndicatorPosition = (global: boolean | undefined): string => (global ? 'bottom: 0' : 'top: 0');

const StyledButton = styled.button<StyledButtonProps>`
const StyledButton = styled.button<{ $global?: boolean; $isSelected?: boolean; $removable?: boolean; }>`
align-items: center;
border-bottom: ${({ $isGlobal }) => ($isGlobal ? 'none' : '1px solid #878f9a')}; /* TODO change colors when updating thematization */
bottom: -1px;
color: ${({ $isGlobal }) => ($isGlobal ? '#1B1C1E' : '#878f9a')}; /* TODO change colors when updating thematization */
color: ${({ $isSelected, theme }) => ($isSelected ? theme.greys['neutral-90'] : '#60666e')};
display: flex;
justify-content: center;
line-height: 1.5rem;
min-height: ${({ $isMobile }) => ($isMobile ? 'var(--size-3halfx)' : 'var(--size-3x)')};
min-width: 82px;
font-family: var(--font-family);
font-size: 0.875rem;
gap: var(--spacing-half);
padding: 0 var(--spacing-2x);
padding-right: ${({ $removable }) => ($removable && 'var(--spacing-4x)')};
position: relative;

&:hover {
background-color: ${({ theme }) => theme.greys.grey};
user-select: none;

&::after {
content: '';
display: block;
height: 4px;
left: 0;
position: absolute;
width: 100%;
${({ $global }) => selectedIndicatorPosition($global)};
}

${focus};
${({ theme }) => focus({ theme }, false, ':focus-visible')};
${focusVisibleReset};
${({ $isSelected, theme }) => !$isSelected && css`
&:active {
color: ${theme.greys['neutral-90']};
font-weight: var(--font-semi-bold);

&:focus {
z-index: 2;
}
&::after {
background-color: ${theme.main['primary-1.3']} !important;
}
}
`}

${({ $isGlobal, $isSelected, theme }) => ($isGlobal && $isSelected) && css`
z-index: 1;
${({ $isSelected, theme }) => $isSelected && css`
background: ${theme.greys.white};
font-weight: var(--font-semi-bold);

::after {
&::after {
background-color: ${theme.main['primary-1.1']};
bottom: 0;
content: '';
display: block;
height: 4px;
left: 0;
position: absolute;
width: 100%;
}
`}

${({ $isGlobal, $isSelected, theme }) => (!$isGlobal && $isSelected) && css`
background-color: ${theme.greys.white};
border: 1px solid #878f9a; /* TODO change colors when updating thematization */
border-bottom: 1px solid transparent;
border-radius: var(--border-radius-2x) var(--border-radius-2x) 0 0;
color: #1b1c1e; /* TODO change colors when updating thematization */
z-index: 1;
`}
${focus};
${focusVisibleReset};
`;

const StyledButtonText = styled.span<IsSelected & { $isMobile: boolean; }>`
color: ${({ theme }) => theme.greys.black};
font-family: var(--font-family);
font-size: ${({ $isMobile }) => ($isMobile ? 1 : 0.875)}rem;
font-weight: ${({ $isSelected }) => ($isSelected ? 'var(--font-semi-bold)' : 'var(--font-normal)')};
line-height: 1.5rem;
const StyledButtonIcon = styled(Icon)`
color: ${({ theme }) => theme.greys['dark-grey']};
vertical-align: middle;
`;

const StyledTab = styled.div<{ $isSelected: boolean; }>`
display: flex;
position: relative;

${({ $isSelected }) => !$isSelected && css`
&:hover {
${StyledButton} {
&::after {
background-color: ${({ theme }) => theme.greys.grey};
}
}
}
`};
`;

const LeftIcon = styled(Icon)<IsSelected>`
color: ${({ theme }) => theme.greys.black};
height: 1rem;
min-width: fit-content;
padding-right: var(--spacing-half);
width: 1rem;
const DeleteButton = styled(IconButton)`
min-height: var(--size-1x);
min-width: var(--size-1x);
padding: 0;
position: absolute;
right: var(--spacing-1halfx);
top: 50%;
transform: translateY(-50%);
width: var(--size-1x);
savutsang marked this conversation as resolved.
Show resolved Hide resolved
`;

const RightIcon = styled(Icon)<IsSelected>`
color: ${({ theme }) => theme.greys.black};
height: 1rem;
min-width: fit-content;
padding-left: var(--spacing-half);
width: 1rem;
const ButtonLabel = styled.span`
// Prevent width shifting between normal and semi-bold
&::after {
content: attr(data-content);
display: block;
font-weight: var(--font-semi-bold);
height: 0;
visibility: hidden;
}
`;

interface TabButtonProps {
Expand All @@ -95,9 +104,8 @@ interface TabButtonProps {
leftIcon?: IconName
rightIcon?: IconName;
isSelected: boolean;

onClick(): void;

onRemove?(tabId: string): void;
onKeyDown?(event: KeyboardEvent<HTMLButtonElement>): void;
}

Expand All @@ -110,44 +118,61 @@ export const TabButton = forwardRef(({
rightIcon,
isSelected,
onClick,
onRemove,
onKeyDown,
...rest
}: TabButtonProps, ref: Ref<HTMLButtonElement>): ReactElement => {
const { isMobile } = useDeviceContext();
const { t } = useTranslation('tabs');
const dataAttributes = useDataAttributes(rest);
const dataTestId = dataAttributes['data-testid'];
const hasRemove = !!onRemove;

return (
<StyledButton
id={id}
aria-controls={panelId}
role="tab"
aria-selected={isSelected}
ref={ref}
data-testid="tab-button"
tabIndex={isSelected ? undefined : -1}
$isGlobal={global}
$isMobile={isMobile}
$isSelected={isSelected}
onClick={onClick}
onKeyDown={onKeyDown}
>
{leftIcon && (
<LeftIcon
data-testid="tab-button-left-icon"
$isSelected={isSelected}
name={leftIcon}
size="16"
/>
)}
<StyledButtonText data-testid="tab-button-text" $isSelected={isSelected} $isMobile={isMobile}>
{children}
</StyledButtonText>
{rightIcon && (
<RightIcon
data-testid="tab-button-right-icon"
$isSelected={isSelected}
name={rightIcon}
size="16"
<StyledTab $isSelected={isSelected} data-testid={dataTestId}>
<StyledButton
type="button"
id={id}
aria-controls={panelId}
role="tab"
aria-selected={isSelected}
ref={ref}
data-testid="tab-button"
tabIndex={isSelected ? undefined : -1}
onClick={onClick}
onKeyDown={onKeyDown}
$removable={hasRemove}
$isSelected={isSelected}
$global={global}
>
{leftIcon && (
<StyledButtonIcon
aria-hidden="true"
data-testid="tab-button-left-icon"
name={leftIcon}
size="16"
/>
)}
<ButtonLabel data-testid="tab-button-text" data-content={children}>
{children}
</ButtonLabel>
{rightIcon && (
<StyledButtonIcon
aria-hidden="true"
data-testid="tab-button-right-icon"
name={rightIcon}
size="16"
/>
)}
</StyledButton>
{hasRemove && (
<DeleteButton
buttonType="tertiary"
onClick={() => onRemove(id)}
data-testid="tab-delete"
aria-label={t('dismissTab', { label: children })}
savutsang marked this conversation as resolved.
Show resolved Hide resolved
iconName='x'
/>
)}
</StyledButton>
</StyledTab>
);
});
14 changes: 9 additions & 5 deletions packages/react/src/components/tabs/tab-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ interface TabPanelProps {
contained?: boolean;
hidden: boolean,
id: string,
global?: boolean;
}

const StyledDiv = styled.div<{ $contained?: boolean }>`
border: ${({ $contained }) => ($contained ? '1px solid #878F9A' : 'none')}; /* TODO change with next thematization */
const StyledDiv = styled.div<{ $contained?: boolean; $isGlobal?: boolean; }>`
border: ${({ $contained }) => ($contained ? '1px solid #DBDEE1' : 'none')}; /* TODO change with next thematization */
savutsang marked this conversation as resolved.
Show resolved Hide resolved
border-radius: ${({ $isGlobal }) => !$isGlobal && '0 0 var(--border-radius-2x) var(--border-radius-2x)'};
border-top: none;

${focus}
${({ theme }) => focus({ theme }, false, ':focus-visible')}
${focusVisibleReset}
${focus};
${focusVisibleReset};
`;

export const TabPanel: FunctionComponent<PropsWithChildren<TabPanelProps>> = ({
Expand All @@ -25,15 +26,18 @@ export const TabPanel: FunctionComponent<PropsWithChildren<TabPanelProps>> = ({
contained,
hidden,
id,
global,
savutsang marked this conversation as resolved.
Show resolved Hide resolved
}) => (
<StyledDiv
$contained={contained}
$isGlobal={global}
aria-hidden={hidden}
aria-labelledby={buttonId}
hidden={hidden}
id={id}
role="tabpanel"
tabIndex={0}

>
{children}
</StyledDiv>
Expand Down
Loading
Loading