diff --git a/.changeset/add-badge-component.md b/.changeset/add-badge-component.md new file mode 100644 index 0000000000..5f07272a44 --- /dev/null +++ b/.changeset/add-badge-component.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Added new `Badge` component for non-interactive labeling and categorization of content. It shares the visual appearance of `Tag` but renders as a plain DOM element with no interactive states. + +**Affected components:** Badge diff --git a/docs-ui/src/app/components/badge/components.tsx b/docs-ui/src/app/components/badge/components.tsx new file mode 100644 index 0000000000..a77a6b09d7 --- /dev/null +++ b/docs-ui/src/app/components/badge/components.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { Badge } from '../../../../../packages/ui/src/components/Badge/Badge'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { RiBugLine } from '@remixicon/react'; + +export const Default = () => Banana; + +export const WithIcon = () => }>Banana; + +export const Sizes = () => ( + + Banana + Banana + +); diff --git a/docs-ui/src/app/components/badge/page.mdx b/docs-ui/src/app/components/badge/page.mdx new file mode 100644 index 0000000000..fb934c57ad --- /dev/null +++ b/docs-ui/src/app/components/badge/page.mdx @@ -0,0 +1,41 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { Default, WithIcon, Sizes } from './components'; +import { badgePropDefs } from './props-definition'; +import { usage, preview, withIcons, sizes } from './snippets'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { BadgeDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} code={preview} /> + +## Usage + + + +## API reference + +### Badge + + + +## Examples + +### With icons + +} code={withIcons} /> + +### Sizes + +} code={sizes} /> + + + + diff --git a/docs-ui/src/app/components/badge/props-definition.tsx b/docs-ui/src/app/components/badge/props-definition.tsx new file mode 100644 index 0000000000..b193c1cef2 --- /dev/null +++ b/docs-ui/src/app/components/badge/props-definition.tsx @@ -0,0 +1,27 @@ +import { + classNamePropDefs, + childrenPropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const badgePropDefs: Record = { + icon: { + type: 'enum', + values: ['ReactNode'], + description: 'Icon displayed before the badge text.', + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + description: ( + <> + Visual size of the badge. Use small for inline or dense + layouts, medium for standalone badges. + + ), + }, + ...childrenPropDefs, + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/badge/snippets.ts b/docs-ui/src/app/components/badge/snippets.ts new file mode 100644 index 0000000000..dc765d2b7d --- /dev/null +++ b/docs-ui/src/app/components/badge/snippets.ts @@ -0,0 +1,12 @@ +export const usage = `import { Badge } from '@backstage/ui'; + +Badge`; + +export const preview = `Banana`; + +export const withIcons = `}>Banana`; + +export const sizes = ` + Banana + Banana +`; diff --git a/docs-ui/src/utils/data.ts b/docs-ui/src/utils/data.ts index bd37760ea4..3d958f5b7c 100644 --- a/docs-ui/src/utils/data.ts +++ b/docs-ui/src/utils/data.ts @@ -17,6 +17,10 @@ export const components: Page[] = [ title: 'Avatar', slug: 'avatar', }, + { + title: 'Badge', + slug: 'badge', + }, { title: 'Box', slug: 'box', diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 093d18e707..a2588e22de 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -318,6 +318,45 @@ export interface AvatarProps extends Omit, 'children' | 'className'>, AvatarOwnProps {} +// @public +export const Badge: ForwardRefExoticComponent< + BadgeProps & RefAttributes +>; + +// @public +export const BadgeDefinition: { + readonly styles: { + readonly [key: string]: string; + }; + readonly classNames: { + readonly root: 'bui-Badge'; + readonly icon: 'bui-BadgeIcon'; + }; + readonly bg: 'consumer'; + readonly propDefs: { + readonly icon: {}; + readonly size: { + readonly dataAttribute: true; + readonly default: 'small'; + }; + readonly children: {}; + readonly className: {}; + }; +}; + +// @public +export type BadgeOwnProps = { + icon?: React.ReactNode; + size?: 'small' | 'medium'; + children?: React.ReactNode; + className?: string; +}; + +// @public +export interface BadgeProps + extends BadgeOwnProps, + Omit, keyof BadgeOwnProps> {} + // @public (undocumented) export interface BgContextValue { // (undocumented) diff --git a/packages/ui/src/components/Badge/Badge.module.css b/packages/ui/src/components/Badge/Badge.module.css new file mode 100644 index 0000000000..ec985161ca --- /dev/null +++ b/packages/ui/src/components/Badge/Badge.module.css @@ -0,0 +1,65 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@layer tokens, base, components, utilities; + +@layer components { + .bui-Badge { + color: var(--bui-fg-primary); + background-color: var(--bui-bg-neutral-1); + border-radius: var(--bui-radius-2); + display: inline-flex; + align-items: center; + justify-content: center; + font-weight: var(--bui-font-weight-regular); + gap: var(--bui-space-1); + + &[data-on-bg='neutral-1'] { + background-color: var(--bui-bg-neutral-2); + } + + &[data-on-bg='neutral-2'] { + background-color: var(--bui-bg-neutral-3); + } + + &[data-on-bg='neutral-3'] { + background-color: var(--bui-bg-neutral-4); + } + } + + .bui-Badge[data-size='small'] { + height: 26px; + padding: 0 var(--bui-space-2); + font-size: var(--bui-font-size-1); + } + + .bui-Badge[data-size='medium'] { + height: 32px; + padding: 0 var(--bui-space-2); + font-size: var(--bui-font-size-2); + } + + .bui-BadgeIcon { + display: flex; + align-items: center; + justify-content: center; + + svg { + width: 1rem; + height: 1rem; + } + } +} diff --git a/packages/ui/src/components/Badge/Badge.stories.tsx b/packages/ui/src/components/Badge/Badge.stories.tsx new file mode 100644 index 0000000000..9e5604b263 --- /dev/null +++ b/packages/ui/src/components/Badge/Badge.stories.tsx @@ -0,0 +1,61 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import preview from '../../../../../.storybook/preview'; +import { Badge } from '.'; +import { Flex } from '../../'; +import { BUIProvider } from '../../provider'; +import { RiBugLine } from '@remixicon/react'; + +const meta = preview.meta({ + title: 'Backstage UI/Badge', + component: Badge, + decorators: [ + Story => ( + + + + ), + ], +}); + +export const Default = meta.story({ + args: { + children: 'Banana', + }, +}); + +export const Sizes = meta.story({ + render: () => ( + + Banana + Banana + + ), +}); + +export const WithIcon = meta.story({ + render: () => ( + + }> + Banana + + }> + Banana + + + ), +}); diff --git a/packages/ui/src/components/Badge/Badge.tsx b/packages/ui/src/components/Badge/Badge.tsx new file mode 100644 index 0000000000..f5b8e5918b --- /dev/null +++ b/packages/ui/src/components/Badge/Badge.tsx @@ -0,0 +1,40 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { BadgeProps } from './types'; +import { forwardRef } from 'react'; +import { useDefinition } from '../../hooks/useDefinition'; +import { BadgeDefinition } from './definition'; + +/** + * A non-interactive badge for labeling or categorizing content. + * + * @public + */ +export const Badge = forwardRef((props, ref) => { + const { ownProps, restProps, dataAttributes } = useDefinition( + BadgeDefinition, + props, + ); + const { classes, children, icon } = ownProps; + + return ( + + {icon && {icon}} + {children} + + ); +}); diff --git a/packages/ui/src/components/Badge/definition.ts b/packages/ui/src/components/Badge/definition.ts new file mode 100644 index 0000000000..cba0daeb8e --- /dev/null +++ b/packages/ui/src/components/Badge/definition.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { defineComponent } from '../../hooks/useDefinition'; +import type { BadgeOwnProps } from './types'; +import styles from './Badge.module.css'; + +/** + * Component definition for Badge + * @public + */ +export const BadgeDefinition = defineComponent()({ + styles, + classNames: { + root: 'bui-Badge', + icon: 'bui-BadgeIcon', + }, + bg: 'consumer', + propDefs: { + icon: {}, + size: { dataAttribute: true, default: 'small' }, + children: {}, + className: {}, + }, +}); diff --git a/packages/ui/src/components/Badge/index.ts b/packages/ui/src/components/Badge/index.ts new file mode 100644 index 0000000000..0041936187 --- /dev/null +++ b/packages/ui/src/components/Badge/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { Badge } from './Badge'; +export type { BadgeProps, BadgeOwnProps } from './types'; +export { BadgeDefinition } from './definition'; diff --git a/packages/ui/src/components/Badge/types.ts b/packages/ui/src/components/Badge/types.ts new file mode 100644 index 0000000000..e22c0eb345 --- /dev/null +++ b/packages/ui/src/components/Badge/types.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Own props for the Badge component. + * + * @public + */ +export type BadgeOwnProps = { + /** + * The icon to display before the badge text. + */ + icon?: React.ReactNode; + /** + * The size of the badge. + */ + size?: 'small' | 'medium'; + children?: React.ReactNode; + className?: string; +}; + +/** + * Props for the Badge component. + * + * @public + */ +export interface BadgeProps + extends BadgeOwnProps, + Omit, keyof BadgeOwnProps> {} diff --git a/packages/ui/src/definitions.ts b/packages/ui/src/definitions.ts index d90437376e..3cc94e1cde 100644 --- a/packages/ui/src/definitions.ts +++ b/packages/ui/src/definitions.ts @@ -27,6 +27,7 @@ export { } from './components/Accordion/definition'; export { AlertDefinition } from './components/Alert/definition'; export { AvatarDefinition } from './components/Avatar/definition'; +export { BadgeDefinition } from './components/Badge/definition'; export { BoxDefinition } from './components/Box/definition'; export { ButtonDefinition } from './components/Button/definition'; export { ButtonIconDefinition } from './components/ButtonIcon/definition'; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index abf4b4accb..464df3a845 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -31,6 +31,7 @@ export * from './components/FullPage'; export * from './components/Accordion'; export * from './components/Alert'; export * from './components/Avatar'; +export * from './components/Badge'; export * from './components/Button'; export * from './components/Card'; export * from './components/Dialog';