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

Enhancement : Badge Component #66555

Merged
merged 24 commits into from
Dec 13, 2024
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
1 change: 1 addition & 0 deletions docs/tool/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const componentPaths = glob( 'packages/components/src/*/**/README.md', {
'packages/components/src/menu/README.md',
'packages/components/src/tabs/README.md',
'packages/components/src/custom-select-control-v2/README.md',
'packages/components/src/badge/README.md',
],
} );
const packagePaths = glob( 'packages/*/package.json' )
Expand Down
4 changes: 4 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

- `BoxControl`: Better respect for the `min` prop in the Range Slider ([#67819](https://github.com/WordPress/gutenberg/pull/67819)).

### Experimental

- Add new `Badge` component ([#66555](https://github.com/WordPress/gutenberg/pull/66555)).

## 29.0.0 (2024-12-11)

### Breaking Changes
Expand Down
22 changes: 22 additions & 0 deletions packages/components/src/badge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Badge
Vrishabhsk marked this conversation as resolved.
Show resolved Hide resolved

<!-- This file is generated automatically and cannot be edited directly. Make edits via TypeScript types and TSDocs. -->

<p class="callout callout-info">See the <a href="https://wordpress.github.io/gutenberg/?path=/docs/components-badge--docs">WordPress Storybook</a> for more detailed, interactive documentation.</p>

## Props

### `children`

Text to display inside the badge.

- Type: `string`
- Required: Yes

### `intent`

Badge variant.

- Type: `"default" | "info" | "success" | "warning" | "error"`
- Required: No
- Default: `default`
5 changes: 5 additions & 0 deletions packages/components/src/badge/docs-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$schema": "../../schemas/docs-manifest.json",
"displayName": "Badge",
"filePath": "./index.tsx"
}
66 changes: 66 additions & 0 deletions packages/components/src/badge/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* External dependencies
*/
import clsx from 'clsx';

/**
* WordPress dependencies
*/
import { info, caution, error, published } from '@wordpress/icons';

/**
* Internal dependencies
*/
import type { BadgeProps } from './types';
import type { WordPressComponentProps } from '../context';
import Icon from '../icon';

function Badge( {
className,
intent = 'default',
children,
...props
}: WordPressComponentProps< BadgeProps, 'span', false > ) {
/**
* Returns an icon based on the badge context.
*
* @return The corresponding icon for the provided context.
*/
function contextBasedIcon() {
switch ( intent ) {
case 'info':
return info;
case 'success':
return published;
case 'warning':
return caution;
case 'error':
return error;
default:
return null;
}
}

return (
<span
className={ clsx(
'components-badge',
`is-${ intent }`,
intent !== 'default' && 'has-icon',
className
) }
{ ...props }
>
{ intent !== 'default' && (
<Icon
icon={ contextBasedIcon() }
size={ 16 }
fill="currentColor"
/>
) }
{ children }
</span>
);
}

export default Badge;
53 changes: 53 additions & 0 deletions packages/components/src/badge/stories/index.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* External dependencies
*/
import type { Meta, StoryObj } from '@storybook/react';

/**
* Internal dependencies
*/
import Badge from '..';

const meta = {
component: Badge,
title: 'Components/Containers/Badge',
tags: [ 'status-private' ],
} satisfies Meta< typeof Badge >;

export default meta;

type Story = StoryObj< typeof meta >;

export const Default: Story = {
args: {
children: 'Code is Poetry',
},
};

export const Info: Story = {
args: {
...Default.args,
intent: 'info',
},
};

export const Success: Story = {
args: {
...Default.args,
intent: 'success',
},
};

export const Warning: Story = {
args: {
...Default.args,
intent: 'warning',
},
};

export const Error: Story = {
args: {
...Default.args,
intent: 'error',
},
};
38 changes: 38 additions & 0 deletions packages/components/src/badge/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
$badge-colors: (
"info": #3858e9,
"warning": $alert-yellow,
"error": $alert-red,
"success": $alert-green,
);

.components-badge {
background-color: color-mix(in srgb, $white 90%, var(--base-color));
color: color-mix(in srgb, $black 50%, var(--base-color));
padding: 0 $grid-unit-10;
min-height: $grid-unit-30;
border-radius: $radius-small;
font-size: $font-size-small;
font-weight: 400;
flex-shrink: 0;
line-height: $font-line-height-small;
width: fit-content;
display: flex;
align-items: center;
gap: 2px;

&:where(.is-default) {
background-color: $gray-100;
color: $gray-800;
}

&.has-icon {
padding-inline-start: $grid-unit-05;
}

// Generate color variants
@each $type, $color in $badge-colors {
&.is-#{$type} {
--base-color: #{$color};
}
}
}
40 changes: 40 additions & 0 deletions packages/components/src/badge/test/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* External dependencies
*/
import { render, screen } from '@testing-library/react';

/**
* Internal dependencies
*/
import Badge from '..';

describe( 'Badge', () => {
it( 'should render correctly with default props', () => {
render( <Badge>Code is Poetry</Badge> );
const badge = screen.getByText( 'Code is Poetry' );
expect( badge ).toBeInTheDocument();
expect( badge.tagName ).toBe( 'SPAN' );
expect( badge ).toHaveClass( 'components-badge' );
} );

it( 'should render as per its intent and contain an icon', () => {
render( <Badge intent="error">Code is Poetry</Badge> );
const badge = screen.getByText( 'Code is Poetry' );
expect( badge ).toHaveClass( 'components-badge', 'is-error' );
expect( badge ).toHaveClass( 'has-icon' );
} );

it( 'should combine custom className with default class', () => {
render( <Badge className="custom-class">Code is Poetry</Badge> );
const badge = screen.getByText( 'Code is Poetry' );
expect( badge ).toHaveClass( 'components-badge' );
expect( badge ).toHaveClass( 'custom-class' );
} );

it( 'should pass through additional props', () => {
render( <Badge data-testid="custom-badge">Code is Poetry</Badge> );
const badge = screen.getByTestId( 'custom-badge' );
expect( badge ).toHaveTextContent( 'Code is Poetry' );
expect( badge ).toHaveClass( 'components-badge' );
} );
} );
12 changes: 12 additions & 0 deletions packages/components/src/badge/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type BadgeProps = {
/**
* Badge variant.
*
* @default 'default'
*/
intent?: 'default' | 'info' | 'success' | 'warning' | 'error';
/**
* Text to display inside the badge.
*/
children: string;
};
2 changes: 2 additions & 0 deletions packages/components/src/private-apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Theme from './theme';
import { Tabs } from './tabs';
import { kebabCase } from './utils/strings';
import { lock } from './lock-unlock';
import Badge from './badge';

export const privateApis = {};
lock( privateApis, {
Expand All @@ -17,4 +18,5 @@ lock( privateApis, {
Theme,
Menu,
kebabCase,
Badge,
} );
1 change: 1 addition & 0 deletions packages/components/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// Components
@import "./animate/style.scss";
@import "./autocomplete/style.scss";
@import "./badge/styles.scss";
@import "./button-group/style.scss";
@import "./button/style.scss";
@import "./checkbox-control/style.scss";
Expand Down
2 changes: 2 additions & 0 deletions packages/icons/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Add new `caution` icon ([#66555](https://github.com/WordPress/gutenberg/pull/66555)).
- Add new `error` icon ([#66555](https://github.com/WordPress/gutenberg/pull/66555)).
- Deprecate `warning` icon and rename to `cautionFilled` ([#67895](https://github.com/WordPress/gutenberg/pull/67895)).

## 10.14.0 (2024-12-11)
Expand Down
4 changes: 3 additions & 1 deletion packages/icons/src/icon/stories/keywords.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const keywords: Partial< Record< keyof typeof import('../../'), string[] > > = {
cancelCircleFilled: [ 'close' ],
cautionFilled: [ 'alert', 'caution', 'warning' ],
caution: [ 'alert', 'warning' ],
cautionFilled: [ 'alert', 'warning' ],
create: [ 'add' ],
error: [ 'alert', 'caution', 'warning' ],
file: [ 'folder' ],
seen: [ 'show' ],
thumbsDown: [ 'dislike' ],
Expand Down
2 changes: 2 additions & 0 deletions packages/icons/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export { default as caption } from './library/caption';
export { default as capturePhoto } from './library/capture-photo';
export { default as captureVideo } from './library/capture-video';
export { default as category } from './library/category';
export { default as caution } from './library/caution';
export {
/** @deprecated Import `cautionFilled` instead. */
default as warning,
Expand Down Expand Up @@ -89,6 +90,7 @@ export { default as download } from './library/download';
export { default as edit } from './library/edit';
export { default as envelope } from './library/envelope';
export { default as external } from './library/external';
export { default as error } from './library/error';
export { default as file } from './library/file';
export { default as filter } from './library/filter';
export { default as flipHorizontal } from './library/flip-horizontal';
Expand Down
16 changes: 16 additions & 0 deletions packages/icons/src/library/caution.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* WordPress dependencies
*/
import { SVG, Path } from '@wordpress/primitives';

const caution = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WordPress/gutenberg-design I just want to make sure we measure twice and cut once here because publishing a new icon is kind of irreversible 😄

Would it be worth renaming these so it follows the convention of foo + fooFilled like we do for the other pairs like this? For example, change the canonical name of warning to cautionFilled (maintaining back compat with an aliased export of course).

Caution and warning icons Help icon pair

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yes, good suggestion. Just to clarify; that means any existing instances of warning would map to cautionFilled?

We should update the cautionFilled icon so that the details match the new caution icon too. Do you think it makes sense to do that in this PR, or separately?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that means any existing instances of warning would map to cautionFilled?

Correct 👍

Do you think it makes sense to do that in this PR, or separately?

Yes, let me pull the icon changes out of this PR so we can address it separately with proper care. I'll prepare a PR so you can push up changes to the SVG.

@Vrishabhsk Keeping this PR on hold for just a bit more. Thank you for your patience!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jameskoster Updates to the SVG can be pushed here: #67895

<SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<Path
fillRule="evenodd"
clipRule="evenodd"
d="M5.5 12a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0ZM12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm-.75 12v-1.5h1.5V16h-1.5Zm0-8v5h1.5V8h-1.5Z"
/>
</SVG>
);

export default caution;
16 changes: 16 additions & 0 deletions packages/icons/src/library/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* WordPress dependencies
*/
import { SVG, Path } from '@wordpress/primitives';

const error = (
<SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<Path
fillRule="evenodd"
clipRule="evenodd"
d="M12.218 5.377a.25.25 0 0 0-.436 0l-7.29 12.96a.25.25 0 0 0 .218.373h14.58a.25.25 0 0 0 .218-.372l-7.29-12.96Zm-1.743-.735c.669-1.19 2.381-1.19 3.05 0l7.29 12.96a1.75 1.75 0 0 1-1.525 2.608H4.71a1.75 1.75 0 0 1-1.525-2.608l7.29-12.96ZM12.75 17.46h-1.5v-1.5h1.5v1.5Zm-1.5-3h1.5v-5h-1.5v5Z"
/>
</SVG>
);

export default error;
8 changes: 6 additions & 2 deletions packages/icons/src/library/info.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
import { SVG, Path } from '@wordpress/primitives';

const info = (
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<Path d="M12 3.2c-4.8 0-8.8 3.9-8.8 8.8 0 4.8 3.9 8.8 8.8 8.8 4.8 0 8.8-3.9 8.8-8.8 0-4.8-4-8.8-8.8-8.8zm0 16c-4 0-7.2-3.3-7.2-7.2C4.8 8 8 4.8 12 4.8s7.2 3.3 7.2 7.2c0 4-3.2 7.2-7.2 7.2zM11 17h2v-6h-2v6zm0-8h2V7h-2v2z" />
<SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<Path
fillRule="evenodd"
clipRule="evenodd"
d="M5.5 12a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0ZM12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm.75 4v1.5h-1.5V8h1.5Zm0 8v-5h-1.5v5h1.5Z"
/>
</SVG>
);

Expand Down
Loading