Skip to content

Commit

Permalink
feat(react): deprecate individual tooltip props for IconButton replac…
Browse files Browse the repository at this point in the history
…ing with tooltipProps (#1465)
  • Loading branch information
scurker authored Apr 24, 2024
1 parent 8dfa6b4 commit b5ed8b0
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 123 deletions.
32 changes: 18 additions & 14 deletions docs/pages/components/IconButton.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,16 @@ Error buttons are used to indicate potentially dangerous actions.
<IconButton variant="error" icon="pencil" label="Edit" disabled />
```

### Tooltip Placement
### Tooltip Customization

Tooltip placement has a default value of `auto`, but can be fixed to a certain position if needed.
The default tooltip can be modified by setting the relevant props configuration from [Tooltip](./Tooltip). This includes all props except for `children` and `target`.

```jsx example
<IconButton icon="trash" label="Delete" tooltipPlacement="bottom" />
```

### Tooltip Variant

The tooltip style can also be customized.

```jsx example
<IconButton icon="info-circle" label="More Information" tooltipVariant="info" />
<IconButton
icon="trash"
label="Delete"
tooltipProps={{ placement: 'bottom', variant: 'info' }}
/>
```

### Offscreen Text
Expand Down Expand Up @@ -112,23 +108,31 @@ If there are multiple icon buttons with the same label, the visible label text c
defaultValue: 'button',
description: 'Component to render the IconButton as.'
},
{
name: 'tooltipProps',
type: 'object',
description: 'Props to pass and configure the displayed tooltip.'
},
{
name: 'tooltipPlacement',
type: 'string',
deprecated: true,
defaultValue: 'auto',
description: 'The position of the tooltip relative to its target element.'
description: 'Deprecated, use "tooltipProps.placement" instead.'
},
{
name: 'tooltipVariant',
type: ['info', 'text', 'big'],
deprecated: true,
defaultValue: 'text',
description: 'The style of tooltip to display.'
description: 'Deprecated, use "tooltipProps.variant" instead.'
},
{
name: 'tooltipPortal',
type: ['React.Ref', 'HTMLElement'],
deprecated: true,
defaultValue: 'document.body',
description: 'The parent element to place the Tooltip in.'
description: 'Deprecated, use "tooltipProps.portal" instead.'
},
{
name: 'large',
Expand Down
93 changes: 0 additions & 93 deletions packages/react/__tests__/src/components/IconButton/index.js

This file was deleted.

135 changes: 135 additions & 0 deletions packages/react/src/components/IconButton/IconButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React, { createRef } from 'react';
import { render, screen } from '@testing-library/react';
import { axe } from 'jest-axe';
import IconButton from './';

it('should render button', () => {
render(<IconButton icon="pencil" label="Edit" />);
const button = screen.getByRole('button', { name: 'Edit' });
expect(button).toBeInTheDocument();
expect(button).toHaveAttribute('type', 'button');
expect(button).toHaveAttribute('tabIndex', '0');
expect(button).not.toHaveAttribute('role');
expect(button).toHaveTextContent('');
});

it('should render a "as" an anchor', () => {
render(<IconButton icon="pencil" label="Edit" as="a" href="/somewhere" />);
const button = screen.queryByRole('link', { name: 'Edit' });
expect(button).toBeInTheDocument();
expect(button).not.toHaveAttribute('role');
});

it('should be disabled', () => {
render(<IconButton icon="pencil" label="Edit" disabled />);
expect(screen.queryByRole('button')).toBeDisabled();
});

it('should use aria-disabled for non-buttons when disabled', () => {
render(
<IconButton icon="pencil" label="Edit" as="a" href="/somewhere" disabled />
);
expect(screen.queryByRole('link')).not.toBeDisabled();
expect(screen.queryByRole('link')).toHaveAttribute('aria-disabled', 'true');
});

it('should add button role for custom components', () => {
const CustomButton = React.forwardRef<HTMLDivElement>(function Component(
props,
ref
) {
return <div data-testid="custom" ref={ref} {...props}></div>;
});
render(
// @ts-expect-error this technically should be allowed
<IconButton icon="pencil" label="Edit" as={CustomButton} />
);
expect(screen.getByTestId('custom')).toBeInTheDocument();
expect(screen.getByTestId('custom')).toHaveAttribute('role', 'button');
expect(screen.getByTestId('custom')).toHaveAttribute('tabIndex', '0');
});

it('should add link role when component behaves like a link', () => {
const CustomLink = React.forwardRef<HTMLDivElement>(function Component(
props,
ref
) {
return <div data-testid="custom" ref={ref} {...props}></div>;
});
render(
// @ts-expect-error this technically should be allowed
<IconButton icon="pencil" label="Edit" as={CustomLink} to="/testing" />
);
expect(screen.getByTestId('custom')).toBeInTheDocument();
expect(screen.getByTestId('custom')).toHaveAttribute('role', 'link');
expect(screen.getByTestId('custom')).toHaveAttribute('tabIndex', '0');
});

it('should not render tooltip when disabled prop is true', () => {
render(<IconButton icon="pencil" label="Edit" disabled />);
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
expect(screen.queryByRole('button')).toHaveAttribute('tabIndex', '-1');
expect(screen.queryByRole('button')).toHaveAccessibleName('Edit');
});

it('should support className prop', () => {
render(<IconButton className="bananas" icon="pencil" label="Edit" />);
expect(screen.queryByRole('button')).toHaveClass('IconButton', 'bananas');
});

it('should support ref prop', () => {
const ref = createRef<HTMLButtonElement>();
render(<IconButton icon="pencil" label="Edit" ref={ref} />);
expect(ref.current).toBeTruthy();
expect(ref.current).toEqual(screen.queryByRole('button'));
});

it('should support tooltipProps', () => {
render(
<>
<div id="foo">custom name</div>
<IconButton
icon="pencil"
label="Edit"
tooltipProps={{ association: 'none' }}
aria-labelledby="foo"
/>
</>
);
// Note: this test is a bit obtuse since by default Tooltip overrides
// aria-labelledby so we're testing the "none" association to ensure
// we can set our own custom aria-label when necessary
expect(screen.queryByRole('button')).toHaveAccessibleName('custom name');
});

test('should return no axe violations', async () => {
render(<IconButton icon="pencil" label="Edit" />);
const results = await axe(screen.getByRole('button'));
expect(results).toHaveNoViolations();
});

test('should return no axe violations when rendered as anchor', async () => {
render(<IconButton icon="pencil" label="Edit" as="a" href="/somewhere" />);
const results = await axe(screen.getByRole('link'));
expect(results).toHaveNoViolations();
});

test('should return no axe violations when rendered as CustomElement', async () => {
const CustomButton = React.forwardRef<HTMLDivElement>(function Component(
props,
ref
) {
return <div data-testid="custom" ref={ref} {...props}></div>;
});
render(
<IconButton
icon="pencil"
label="Edit"
// @ts-expect-error this technically should be allowed
as={CustomButton}
href="/somewhere"
/>
);
const results = await axe(screen.getByTestId('custom'));
expect(results).toHaveNoViolations();
});
Loading

0 comments on commit b5ed8b0

Please sign in to comment.