From 24d5f78c22795c4ab0fc64e08908831bfb2cbeef Mon Sep 17 00:00:00 2001 From: Vrishabh Jasani <71686151+Vrishabhsk@users.noreply.github.com> Date: Fri, 13 Dec 2024 20:26:41 +0400 Subject: [PATCH] Enhancement : Badge Component (#66555) * Create badge component * Imports and Manifest * Add test and capability for additional props using spread operator * Enhance componenet furthermore * Generate README via manifest & Add in ignore list * Lock Badge * Convert Storybook from CSF 2 to CSF 3 Format * Improve the component * New iteration * Add new icons: Error and Caution * Utilize new icons * Update icons * decrease icon size * Address feedback * Fix SVG formatting * Fix unit test * Remove unnecessary type (already included) * Update readme * Adjust icon keywords * Add changelog --------- Co-authored-by: Vrishabhsk Co-authored-by: jameskoster Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: rogermattic Co-authored-by: jasmussen --- docs/tool/manifest.js | 1 + packages/components/CHANGELOG.md | 4 ++ packages/components/src/badge/README.md | 22 +++++++ .../components/src/badge/docs-manifest.json | 5 ++ packages/components/src/badge/index.tsx | 66 +++++++++++++++++++ .../src/badge/stories/index.story.tsx | 53 +++++++++++++++ packages/components/src/badge/styles.scss | 38 +++++++++++ packages/components/src/badge/test/index.tsx | 40 +++++++++++ packages/components/src/badge/types.ts | 12 ++++ packages/components/src/private-apis.ts | 2 + packages/components/src/style.scss | 1 + packages/icons/CHANGELOG.md | 2 + packages/icons/src/icon/stories/keywords.ts | 4 +- packages/icons/src/index.js | 2 + packages/icons/src/library/caution.js | 16 +++++ packages/icons/src/library/error.js | 16 +++++ packages/icons/src/library/info.js | 8 ++- 17 files changed, 289 insertions(+), 3 deletions(-) create mode 100644 packages/components/src/badge/README.md create mode 100644 packages/components/src/badge/docs-manifest.json create mode 100644 packages/components/src/badge/index.tsx create mode 100644 packages/components/src/badge/stories/index.story.tsx create mode 100644 packages/components/src/badge/styles.scss create mode 100644 packages/components/src/badge/test/index.tsx create mode 100644 packages/components/src/badge/types.ts create mode 100644 packages/icons/src/library/caution.js create mode 100644 packages/icons/src/library/error.js diff --git a/docs/tool/manifest.js b/docs/tool/manifest.js index 2004fae84f7ccc..569d78bc5bea8a 100644 --- a/docs/tool/manifest.js +++ b/docs/tool/manifest.js @@ -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' ) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index af71c4104b4d97..c58817a420a746 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -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 diff --git a/packages/components/src/badge/README.md b/packages/components/src/badge/README.md new file mode 100644 index 00000000000000..0be531ca6f2df8 --- /dev/null +++ b/packages/components/src/badge/README.md @@ -0,0 +1,22 @@ +# Badge + + + +

See the WordPress Storybook for more detailed, interactive documentation.

+ +## 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` diff --git a/packages/components/src/badge/docs-manifest.json b/packages/components/src/badge/docs-manifest.json new file mode 100644 index 00000000000000..3b70c0ef228432 --- /dev/null +++ b/packages/components/src/badge/docs-manifest.json @@ -0,0 +1,5 @@ +{ + "$schema": "../../schemas/docs-manifest.json", + "displayName": "Badge", + "filePath": "./index.tsx" +} diff --git a/packages/components/src/badge/index.tsx b/packages/components/src/badge/index.tsx new file mode 100644 index 00000000000000..8a55f3881215f3 --- /dev/null +++ b/packages/components/src/badge/index.tsx @@ -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 ( + + { intent !== 'default' && ( + + ) } + { children } + + ); +} + +export default Badge; diff --git a/packages/components/src/badge/stories/index.story.tsx b/packages/components/src/badge/stories/index.story.tsx new file mode 100644 index 00000000000000..aaa4bfb3c08f60 --- /dev/null +++ b/packages/components/src/badge/stories/index.story.tsx @@ -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', + }, +}; diff --git a/packages/components/src/badge/styles.scss b/packages/components/src/badge/styles.scss new file mode 100644 index 00000000000000..e1e9cd5312d11a --- /dev/null +++ b/packages/components/src/badge/styles.scss @@ -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}; + } + } +} diff --git a/packages/components/src/badge/test/index.tsx b/packages/components/src/badge/test/index.tsx new file mode 100644 index 00000000000000..47c832eb3c8300 --- /dev/null +++ b/packages/components/src/badge/test/index.tsx @@ -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( Code is Poetry ); + 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( Code is Poetry ); + 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( Code is Poetry ); + const badge = screen.getByText( 'Code is Poetry' ); + expect( badge ).toHaveClass( 'components-badge' ); + expect( badge ).toHaveClass( 'custom-class' ); + } ); + + it( 'should pass through additional props', () => { + render( Code is Poetry ); + const badge = screen.getByTestId( 'custom-badge' ); + expect( badge ).toHaveTextContent( 'Code is Poetry' ); + expect( badge ).toHaveClass( 'components-badge' ); + } ); +} ); diff --git a/packages/components/src/badge/types.ts b/packages/components/src/badge/types.ts new file mode 100644 index 00000000000000..91cd7c39b549bb --- /dev/null +++ b/packages/components/src/badge/types.ts @@ -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; +}; diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts index 2ced100dc576be..f5a9ee90519c2d 100644 --- a/packages/components/src/private-apis.ts +++ b/packages/components/src/private-apis.ts @@ -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, { @@ -17,4 +18,5 @@ lock( privateApis, { Theme, Menu, kebabCase, + Badge, } ); diff --git a/packages/components/src/style.scss b/packages/components/src/style.scss index 70317f4a2d0e0b..368dec0f5e253d 100644 --- a/packages/components/src/style.scss +++ b/packages/components/src/style.scss @@ -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"; diff --git a/packages/icons/CHANGELOG.md b/packages/icons/CHANGELOG.md index 952e3164d45072..64c1a58b549caf 100644 --- a/packages/icons/CHANGELOG.md +++ b/packages/icons/CHANGELOG.md @@ -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) diff --git a/packages/icons/src/icon/stories/keywords.ts b/packages/icons/src/icon/stories/keywords.ts index 4965bc38c3451c..4de5ae9a7dae93 100644 --- a/packages/icons/src/icon/stories/keywords.ts +++ b/packages/icons/src/icon/stories/keywords.ts @@ -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' ], diff --git a/packages/icons/src/index.js b/packages/icons/src/index.js index ab7edf65e496b9..e82b09e5d5afe9 100644 --- a/packages/icons/src/index.js +++ b/packages/icons/src/index.js @@ -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, @@ -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'; diff --git a/packages/icons/src/library/caution.js b/packages/icons/src/library/caution.js new file mode 100644 index 00000000000000..f6d23fdfc7eddf --- /dev/null +++ b/packages/icons/src/library/caution.js @@ -0,0 +1,16 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const caution = ( + + + +); + +export default caution; diff --git a/packages/icons/src/library/error.js b/packages/icons/src/library/error.js new file mode 100644 index 00000000000000..2dc2bccbf639ce --- /dev/null +++ b/packages/icons/src/library/error.js @@ -0,0 +1,16 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const error = ( + + + +); + +export default error; diff --git a/packages/icons/src/library/info.js b/packages/icons/src/library/info.js index f3425d9e950415..24d41d798263f7 100644 --- a/packages/icons/src/library/info.js +++ b/packages/icons/src/library/info.js @@ -4,8 +4,12 @@ import { SVG, Path } from '@wordpress/primitives'; const info = ( - - + + );