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
Original file line number Diff line number Diff line change
@@ -1,28 +1,12 @@
.tedi-closing-button {
--general-icon-primary: var(--button-close-text-default);

display: flex;
align-items: center;
justify-content: center;
padding: 0;
cursor: pointer;
background-color: var(--button-close-background-default);
border: 1px solid var(--button-close-background-default);
border: 0;
border-radius: var(--button-radius-default);
transition: background 0.5s ease;

@each $state in hover, active {
&:#{ $state } {
--general-icon-primary: var(--button-close-text-#{$state});

background-color: var(--button-close-background-#{$state});
}
}

&:focus-visible {
outline: 2px solid var(--button-main-primary-border-focus);
outline-offset: -2px;
}
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;

&--default {
width: var(--button-sm-icon-size);
Expand All @@ -33,4 +17,62 @@
width: var(--button-xs-icon-size);
height: var(--button-xs-icon-size);
}

&--color-primary {
color: var(--button-close-text-default);
background-color: var(--button-close-background-default);

&:hover {
color: var(--button-close-text-hover);
background-color: var(--button-close-background-hover);
}

&:active {
color: var(--button-close-text-active);
background-color: var(--button-close-background-active);
}

&:focus-visible {
color: var(--button-close-text-focus);
outline: 2px solid var(--button-main-primary-border-focus);
}
}

&--color-brand {
color: var(--button-main-neutral-text-default);
background-color: var(--button-main-neutral-icon-only-background-default);

&:hover {
color: var(--button-main-neutral-text-hover);
background-color: var(--button-main-neutral-icon-only-background-hover);
}

&:active {
color: var(--button-main-neutral-text-active);
background-color: var(--button-main-neutral-icon-only-background-active);
}

&:focus-visible {
outline: 2px solid var(--button-main-primary-border-focus);
}
}

&--color-white {
color: var(--button-neutral-inverted-text-default);
background-color: var(--button-neutral-inverted-icon-only-background-default);

&:hover {
color: var(--button-neutral-inverted-text-hover);
background-color: var(--button-main-neutral-inverted-icon-only-background-hover);
}

&:active {
color: var(--button-neutral-inverted-text-active);
background-color: var(--button-main-neutral-inverted-icon-only-background-hover);
}

&:focus-visible {
outline: 2px solid var(--button-main-neutral-inverted-text-focus);
}
}
}
52 changes: 34 additions & 18 deletions src/tedi/components/buttons/closing-button/closing-button.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,35 @@ jest.mock('../../../providers/label-provider', () => ({

describe('ClosingButton component', () => {
it('renders the ClosingButton with default props', () => {
render(
<ClosingButton
onClick={() => {
console.log('Button pressed');
}}
/>
);
render(<ClosingButton />);

const button = screen.getByRole('button', { name: /close/i });
expect(button).toBeInTheDocument();
expect(button).toHaveClass('tedi-closing-button');
expect(button).toHaveClass('tedi-closing-button--default');
expect(button).toHaveClass('tedi-closing-button tedi-closing-button--default tedi-closing-button--color-primary');

const icon = button.querySelector('span[data-name="icon"]');
expect(icon).toBeInTheDocument();
expect(icon).toHaveClass('tedi-icon--size-24');
});

it('renders with small size when size="small"', () => {
it('renders with the correct small size class and icon size', () => {
render(<ClosingButton size="small" />);

const button = screen.getByRole('button', { name: /close/i });
expect(button).toHaveClass('tedi-closing-button tedi-closing-button--small tedi-closing-button--color-primary');
render(
<ClosingButton
onClick={() => {
console.log('Button pressed');
}}
/>
);

expect(button).toBeInTheDocument();
expect(button).toHaveClass('tedi-closing-button');
expect(button).toHaveClass('tedi-closing-button--small');

const icon = button.querySelector('span[data-name="icon"]');
expect(icon).toBeInTheDocument();
// Small button still uses default icon size
expect(icon).toHaveClass('tedi-icon--size-24');
});

Expand All @@ -54,25 +56,29 @@ describe('ClosingButton component', () => {
it('applies custom class names', () => {
render(<ClosingButton className="custom-class" />);

const button = screen.getByRole('button', { name: /Close/i });
expect(button).toHaveClass('tedi-closing-button');
expect(button).toHaveClass('tedi-closing-button--default');
expect(button).toHaveClass('custom-class');
const button = screen.getByRole('button', { name: /close/i });
expect(button).toHaveClass(
'tedi-closing-button tedi-closing-button--default tedi-closing-button--color-primary custom-class'
);
const customButton = screen.getByRole('button', { name: /Close/i });
expect(customButton).toHaveClass('tedi-closing-button');
expect(customButton).toHaveClass('tedi-closing-button--default');
expect(customButton).toHaveClass('custom-class');
});

it('triggers onClick handler when clicked', () => {
const onClickMock = jest.fn();
render(<ClosingButton onClick={onClickMock} />);

const button = screen.getByRole('button', { name: /Close/i });
const button = screen.getByRole('button', { name: /close/i });
fireEvent.click(button);
expect(onClickMock).toHaveBeenCalledTimes(1);
});

it('uses fallback label from label provider when title is not provided', () => {
render(<ClosingButton />);

const button = screen.getByRole('button', { name: /Close/i });
const button = screen.getByRole('button', { name: /close/i });
expect(button).toHaveAttribute('title', 'Close');
expect(button).toHaveAttribute('aria-label', 'Close');
});
Expand All @@ -84,4 +90,14 @@ describe('ClosingButton component', () => {
expect(button).toHaveAttribute('title', 'Custom Close');
expect(button).toHaveAttribute('aria-label', 'Custom Close');
});

it('applies the correct color classes', () => {
const colors: Array<'primary' | 'brand' | 'white'> = ['primary', 'brand', 'white'];

colors.forEach((color) => {
const { container } = render(<ClosingButton color={color} />);
const button = container.querySelector('button[data-name="closing-button"]');
expect(button).toHaveClass(`tedi-closing-button--color-${color}`);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Meta, StoryFn, StoryObj } from '@storybook/react';

import { Text } from '../../base/typography/text/text';
import { Col, Row } from '../../layout/grid';
import ClosingButton, { ClosingButtonProps } from './closing-button';

Expand Down Expand Up @@ -42,16 +43,18 @@ const SizeTemplate: StoryFn = () => {

const stateArray = ['Default', 'Hover', 'Active', 'Focus'];

const StatesTemplate: StoryFn = () => {
const StatesTemplate: StoryFn = (args) => {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', maxWidth: '200px' }}>
{stateArray.map((state) => (
<Row key={state}>
<Col>
<b>{state}</b>
<Text color={args.color === 'white' ? 'white' : 'primary'} modifiers="bold">
{state}
</Text>
</Col>
<Col>
<ClosingButton id={state} />
<ClosingButton id={state} {...args} />
</Col>
</Row>
))}
Expand Down Expand Up @@ -116,3 +119,32 @@ export const States: Story = {
},
},
};

export const ColorBrand: Story = {
render: StatesTemplate,
args: {
color: 'brand',
},
parameters: {
pseudo: {
hover: '#Hover',
active: '#Active',
focusVisible: '#Focus',
},
},
};

export const ColorInverted: Story = {
render: StatesTemplate,
args: {
color: 'white',
},
parameters: {
pseudo: {
hover: '#Hover',
active: '#Active',
focusVisible: '#Focus',
},
backgrounds: { default: 'brand' },
},
};
23 changes: 19 additions & 4 deletions src/tedi/components/buttons/closing-button/closing-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useLabels } from '../../../providers/label-provider';
import { Icon } from '../../base/icon/icon';
import styles from './closing-button.module.scss';

type ClosingButtonColor = 'primary' | 'brand' | 'white';
export type ClosingButtonSize = 'default' | 'small';
export type ClosingButtonIconSize = 18 | 24;

Expand All @@ -16,7 +17,7 @@ export interface ClosingButtonProps extends ButtonHTMLAttributes<HTMLButtonEleme
className?: string;
/**
* Size of the ClosingButton
* @default 'default'
* @default default
*/
size?: ClosingButtonSize;
/**
Expand All @@ -29,7 +30,11 @@ export interface ClosingButtonProps extends ButtonHTMLAttributes<HTMLButtonEleme
*/
title?: string;
/*
* Size of the icon inside the button
* Color variant of the ClosingButton
* @default default
*/
color?: ClosingButtonColor;
/* Size of the icon inside the button
* @default 24
* If iconSize is set to 18, the button size will automatically adjust to 'small'.
*/
Expand All @@ -38,13 +43,23 @@ export interface ClosingButtonProps extends ButtonHTMLAttributes<HTMLButtonEleme

export const ClosingButton = (props: ClosingButtonProps): JSX.Element => {
const { getLabel } = useLabels();
const { title = getLabel('close'), onClick, size = 'default', iconSize = 24, className, ...rest } = props;
const {
title = getLabel('close'),
onClick,
size = 'default',
iconSize = 24,
color = 'primary',
className,
...rest
} = props;

const resolvedSize: ClosingButtonSize = iconSize === 18 ? 'small' : size;

const buttonClass = cn(
styles['tedi-closing-button'],
{
[styles[`tedi-closing-button--${size}`]]: size,
[styles[`tedi-closing-button--color-${color}`]]: color,
[styles[`tedi-closing-button--${resolvedSize}`]]: resolvedSize,
},
className
Expand All @@ -62,7 +77,7 @@ export const ClosingButton = (props: ClosingButtonProps): JSX.Element => {
title={title}
aria-label={title}
>
<Icon name="close" size={resolvedIconSize} />
<Icon name="close" size={resolvedIconSize} color="inherit" />
</button>
);
};
Expand Down
Loading