Skip to content

Commit

Permalink
feat(react): ImpactBadge component (#1612)
Browse files Browse the repository at this point in the history
* feat: added badge component

* fix: removed unused variable

* fix: comments

* feat: export badge props to extends `ImpactBadge` interface

* feat: added `ImpactBadge` component

* fix: a11y tests

* fix: comments
  • Loading branch information
orest-s authored Aug 7, 2024
1 parent eb0a056 commit 53ae3d0
Show file tree
Hide file tree
Showing 7 changed files with 293 additions and 2 deletions.
71 changes: 71 additions & 0 deletions docs/pages/components/ImpactBadge.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: ImpactBadge
description: The ImpactBadge is an extension of the Badge component is designed to indicate the type of impact
source: https://github.com/dequelabs/cauldron/tree/develop/packages/react/src/components/ImpactBadge/index.tsx
---

import { ImpactBadge } from '@deque/cauldron-react';

```js
import { ImpactBadge } from '@deque/cauldron-react';
```

## Example

### Critical

```jsx example
<ImpactBadge type="critical" />
```

### Serious

```jsx example
<ImpactBadge type="serious" />
```

### Moderate

```jsx example
<ImpactBadge type="moderate" />
```

### Minor

```jsx example
<ImpactBadge type="minor" />
```

### Small size
```jsx example
<ImpactBadge type="minor" size="small" />
```

## Props

### ImpactBadge

<ComponentProps
className={true}
refType="HTMLDivElement"
props={[
{
name: 'type',
type: ['critical', 'serious', 'moderate', 'minor'],
description: 'Variant of impact level',
required: true
},
{
name: 'size',
type: ['default', 'small'],
defaultValue: 'default',
description: 'Visual style of the tag.'
},
{
name: 'label',
type: 'ContentNode',
required: false,
description: 'Label for the ImpactBadge element.'
},
]}
/>
125 changes: 125 additions & 0 deletions packages/react/src/components/ImpactBadge/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import axe from '../../axe';
import ImpactBadge from './';

test('should render critical `ImpactBadge`', () => {
render(<ImpactBadge type="critical" />);
expect(screen.getByText(/critical/i)).toHaveClass(
'ImpactBadge',
'ImpactBadge--critical',
'Badge'
);
});

test('should render correct icon for `critical` type', () => {
render(<ImpactBadge type="critical" />);

expect(
screen.getByText(/critical/i).querySelector('.Icon.Icon--chevron-double-up')
).toBeInTheDocument();
});

test('should render serious `ImpactBadge`', () => {
render(<ImpactBadge type="serious" />);
expect(screen.getByText(/serious/i)).toHaveClass(
'ImpactBadge',
'ImpactBadge--serious',
'Badge'
);
});

test('should render correct icon for `serious` type', () => {
render(<ImpactBadge type="serious" />);

expect(
screen.getByText(/serious/i).querySelector('.Icon.Icon--chevron-up')
).toBeInTheDocument();
});

test('should render moderate `ImpactBadge`', () => {
render(<ImpactBadge type="moderate" />);
expect(screen.getByText(/moderate/i)).toHaveClass(
'ImpactBadge',
'ImpactBadge--moderate',
'Badge'
);
});

test('should render correct icon for `moderate` type', () => {
render(<ImpactBadge type="moderate" />);

expect(
screen.getByText(/moderate/i).querySelector('.Icon.Icon--chevron-down')
).toBeInTheDocument();
});

test('should render minor `ImpactBadge`', () => {
render(<ImpactBadge type="minor" />);
expect(screen.getByText(/minor/i)).toHaveClass(
'ImpactBadge',
'ImpactBadge--minor',
'Badge'
);
});

test('should render correct icon for `minor` type', () => {
render(<ImpactBadge type="minor" />);

expect(
screen.getByText(/minor/i).querySelector('.Icon.Icon--chevron-double-down')
).toBeInTheDocument();
});

test('passes classNames through', () => {
render(<ImpactBadge type="critical" className="foo" />);
expect(screen.getByText(/critical/i)).toHaveClass(
'ImpactBadge',
'ImpactBadge--critical',
'Badge',
'foo'
);
});

test('passes arbitrary props through', () => {
render(<ImpactBadge type="critical" data-foo="true" />);
expect(screen.getByText(/critical/i)).toHaveAttribute('data-foo', 'true');
});

test('should render small `ImpactBadge`', () => {
render(<ImpactBadge type="critical" size="small" />);
const SmallBadge = screen.getByText(/critical/i);
expect(SmallBadge).toHaveClass('ImpactBadge', 'Badge--small');
});

test('should render custom label', () => {
render(<ImpactBadge type="critical" label="custom label" />);
const CustomLabel = screen.getByText(/custom label/i);
expect(CustomLabel).toBeInTheDocument();
expect(screen.queryByText(/critical/i)).not.toBeInTheDocument();
});

test('should return no axe violations', async () => {
const { container } = render(
<>
<ImpactBadge type="critical" />
<ImpactBadge type="serious" />
<ImpactBadge type="moderate" />
<ImpactBadge type="minor" />

<ImpactBadge type="critical" size="small" />
<ImpactBadge type="serious" size="small" />
<ImpactBadge type="moderate" size="small" />
<ImpactBadge type="minor" size="small" />

<ImpactBadge
type="minor"
size="small"
label={'Custom Impact: Custom Minor'}
/>
</>
);

const results = await axe(container);
expect(results).toHaveNoViolations();
});
53 changes: 53 additions & 0 deletions packages/react/src/components/ImpactBadge/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { forwardRef } from 'react';

import Badge, { BadgeLabel } from '../Badge';

import { ContentNode } from '../../types';
import Icon, { IconType } from '../Icon';
import classNames from 'classnames';

type ImpactType = 'critical' | 'serious' | 'moderate' | 'minor';

interface ImpactBadgeProps
extends Omit<React.ComponentProps<typeof Badge>, 'children'> {
type: ImpactType;
label?: ContentNode;
}

const iconByType: { [type in ImpactType]: IconType } = {
critical: 'chevron-double-up',
serious: 'chevron-up',
moderate: 'chevron-down',
minor: 'chevron-double-down'
};

const typeValues: { [key in ImpactType]: string } = {
critical: 'Critical',
serious: 'Serious',
moderate: 'Moderate',
minor: 'Minor'
};

const ImpactBadge = forwardRef<HTMLDivElement, ImpactBadgeProps>(
({ type, label, className, ...other }, ref) => {
return (
<Badge
className={classNames(`ImpactBadge`, `ImpactBadge--${type}`, className)}
ref={ref}
{...other}
>
<Icon type={iconByType[type]} />
{label || (
<>
<BadgeLabel>Impact:</BadgeLabel>
{typeValues[type]}
</>
)}
</Badge>
);
}
);

ImpactBadge.displayName = 'ImpactBadge';

export default ImpactBadge;
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export { default as LoaderOverlay } from './components/LoaderOverlay';
export { default as Line } from './components/Line';
export { default as Tag, TagLabel } from './components/Tag';
export { default as Badge, BadgeLabel } from './components/Badge';
export { default as ImpactBadge } from './components/ImpactBadge';
export { default as TagButton } from './components/TagButton';
export {
default as Table,
Expand Down
39 changes: 39 additions & 0 deletions packages/styles/impact-badge.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
:root {
--impact-badge-critical-background-color: var(--issue-critical);
--impact-badge-critical-border-color: var(--accent-error-disabled);

--impact-badge-serious-background-color: var(--issue-serious);
--impact-badge-serious-border-color: var(--accent-warning);

--impact-badge-moderate-background-color: var(--issue-moderate);
--impact-badge-moderate-border-color: #eb94ff;

--impact-badge-minor-background-color: var(--issue-minor);
--impact-badge-minor-border-color: var(--gray-30);

--impact-badge-text-color: var(--gray-90);
}

.ImpactBadge {
--badge-text-color: var(--impact-badge-text-color);
}

.ImpactBadge--critical {
--badge-background-color: var(--impact-badge-critical-background-color);
--badge-border-color: var(--impact-badge-critical-border-color);
}

.ImpactBadge--serious {
--badge-background-color: var(--impact-badge-serious-background-color);
--badge-border-color: var(--impact-badge-serious-border-color);
}

.ImpactBadge--moderate {
--badge-background-color: var(--impact-badge-moderate-background-color);
--badge-border-color: var(--impact-badge-moderate-border-color);
}

.ImpactBadge--minor {
--badge-background-color: var(--impact-badge-minor-background-color);
--badge-border-color: var(--impact-badge-minor-border-color);
}
1 change: 1 addition & 0 deletions packages/styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@
@import './search-field.css';
@import './text-ellipsis.css';
@import './badge.css';
@import './impact-badge.css';
5 changes: 3 additions & 2 deletions packages/styles/variables.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
--accent-success-dark: #57a711;
--accent-error: #d93251;
--accent-error-active: #c92e40;
--accent-danger-active: #fea7a6;
--accent-danger: #fe6d6b;
--accent-danger-light: #f7846c;
--accent-warning: #ffdd75;
Expand All @@ -45,8 +46,8 @@
--light-workspace-color: #f7f7f7;
--focus-light: #b51ad1;
--focus-dark: #f5a4ff;
--issue-critical: var(--accent-danger);
--issue-serious: var(--accent-warning);
--issue-critical: var(--accent-danger-active);
--issue-serious: var(--accent-caution);
--issue-moderate: #f0c4f8;
--issue-minor: var(--gray-20);

Expand Down

0 comments on commit 53ae3d0

Please sign in to comment.