From 0a49eb6a51be6e91c1374f097271d8a1496bf7db Mon Sep 17 00:00:00 2001 From: Michal Murawski Date: Wed, 13 May 2026 13:39:00 +0200 Subject: [PATCH 1/4] feat(design-system): add StatusBadgeV2 component [AR-60237] --- .changeset/nice-falcons-worry.md | 7 + cspell/local-words.txt | 4 + .../ds-status-badge-v2.module.scss | 109 +++++ .../ds-status-badge-v2.stories.module.scss | 29 ++ .../ds-status-badge-v2.stories.tsx | 435 ++++++++++++++++++ .../ds-status-badge-v2/ds-status-badge-v2.tsx | 77 ++++ .../ds-status-badge-v2.types.ts | 77 ++++ .../components/ds-status-badge-v2/index.ts | 8 + .../ds-status-badge-v2/phase-config.ts | 15 + .../ds-status-badge/ds-status-badge.types.ts | 15 + packages/design-system/src/index.ts | 1 + .../src/__tests__/no-deprecated.test.ts | 23 +- packages/eslint-plugin/src/index.ts | 8 +- 13 files changed, 805 insertions(+), 3 deletions(-) create mode 100644 .changeset/nice-falcons-worry.md create mode 100644 packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.module.scss create mode 100644 packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.stories.module.scss create mode 100644 packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.stories.tsx create mode 100644 packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.tsx create mode 100644 packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts create mode 100644 packages/design-system/src/components/ds-status-badge-v2/index.ts create mode 100644 packages/design-system/src/components/ds-status-badge-v2/phase-config.ts diff --git a/.changeset/nice-falcons-worry.md b/.changeset/nice-falcons-worry.md new file mode 100644 index 000000000..8ee75b60e --- /dev/null +++ b/.changeset/nice-falcons-worry.md @@ -0,0 +1,7 @@ +--- +'@drivenets/design-system': minor +'@drivenets/eslint-plugin-design-system': patch +--- + +Add `DsStatusBadgeV2` component. +Deprecate `DsStatusBadge` component. diff --git a/cspell/local-words.txt b/cspell/local-words.txt index b371fb462..d9486843a 100644 --- a/cspell/local-words.txt +++ b/cspell/local-words.txt @@ -27,3 +27,7 @@ TSESTree unhover unitless unrs +Fira +scrollers +affordances +timelapse diff --git a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.module.scss b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.module.scss new file mode 100644 index 000000000..bace76329 --- /dev/null +++ b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.module.scss @@ -0,0 +1,109 @@ +@mixin phase($bg, $icon-color, $font-color: var(--font-main)) { + background-color: $bg; + color: $font-color; + --_icon-color: #{$icon-color}; +} + +$size-medium: 22px; +$size-small: 18px; +$badge-icon-size-small: 12px; + +.root { + display: inline-flex; + align-items: center; + border-radius: 9999px; + width: max-content; +} + +.icon { + display: inline-flex; + color: var(--_icon-color); + pointer-events: none; +} + +.label::first-letter { + text-transform: capitalize; +} + +.medium { + height: $size-medium; + padding: 0 var(--xs) 0 var(--3xs); + gap: var(--3xs); +} + +.small { + height: $size-small; + padding: 0 var(--2xs) 0 var(--3xs); + gap: var(--3xs); +} + +.small .icon { + --_badge-icon-size: #{$badge-icon-size-small}; +} + +.iconOnly { + padding: 0; + justify-content: center; + + &.medium { + width: $size-medium; + } + + &.small { + width: $size-small; + } +} + +.textOnly { + &.medium { + padding: 0 var(--xs); + } + + &.small { + padding: 0 var(--2xs); + } +} + +.not-started { + @include phase(var(--background-secondary), var(--icon-secondary), var(--font-secondary)); +} + +.temporary { + @include phase(var(--background-secondary), var(--icon-secondary), var(--font-secondary)); +} + +.in-review { + @include phase(var(--background-secondary), var(--icon-secondary), var(--font-secondary)); +} + +.pending { + @include phase(var(--background-pending), var(--icon-pending)); +} + +.active { + @include phase(var(--background-active-status), var(--icon-action)); +} + +.execution { + @include phase(var(--background-execution), var(--icon-execution)); +} + +.result-succeeded { + @include phase(var(--background-success), var(--icon-success)); +} + +.result-warning { + @include phase(var(--background-warning), var(--icon-warning)); +} + +.result-failed { + @include phase(var(--background-error-secondary-hover), var(--icon-error)); +} + +.deprecated { + @include phase(var(--background-secondary), var(--icon-secondary), var(--font-secondary)); +} + +.secondary { + background-color: transparent; +} diff --git a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.stories.module.scss b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.stories.module.scss new file mode 100644 index 000000000..9b4c7f540 --- /dev/null +++ b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.stories.module.scss @@ -0,0 +1,29 @@ +.sectionTitle { + font-weight: 600; + font-size: 14px; + color: var(--font-main); +} + +.docsTable { + border-collapse: collapse; + font-size: 13px; + color: var(--font-main); + + th, + td { + padding: var(--xs) var(--standard); + text-align: left; + border-bottom: 1px solid var(--border-main); + } + + th { + font-weight: 600; + } + + code { + font-size: 12px; + padding: 2px 6px; + border-radius: 4px; + background: var(--background-secondary); + } +} diff --git a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.stories.tsx b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.stories.tsx new file mode 100644 index 000000000..f6a5885f7 --- /dev/null +++ b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.stories.tsx @@ -0,0 +1,435 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import DsStatusBadgeV2 from './ds-status-badge-v2'; +import { + type StatusBadgeV2Phase, + statusBadgeV2Phases, + statusBadgeV2Sizes, + statusBadgeV2Variants, +} from './ds-status-badge-v2.types'; +import { phaseIconMap } from './phase-config'; +import { DsIcon } from '../ds-icon'; +import { DsStack } from '../ds-stack'; +import styles from './ds-status-badge-v2.stories.module.scss'; + +const phaseLabels: Record = { + 'not-started': 'Vacant', + temporary: 'Draft', + 'in-review': 'In Review', + pending: 'Reserved', + active: 'Active', + execution: 'Testing', + 'result-succeeded': 'Provisioned', + 'result-warning': 'Warning', + 'result-failed': 'Failed', + deprecated: 'Decommissioned', +}; + +const meta: Meta = { + title: 'Components/StatusBadgeV2', + component: DsStatusBadgeV2, + parameters: { + layout: 'centered', + }, + argTypes: { + phase: { + control: 'select', + options: statusBadgeV2Phases, + description: 'Lifecycle phase determining color and default icon', + }, + label: { + control: 'text', + description: 'Domain-specific text label (always required)', + }, + icon: { + control: 'text', + description: 'Icon override; pass null for text-only', + }, + iconOnly: { + control: 'boolean', + description: 'Hide label text, show as tooltip instead', + }, + variant: { + control: 'select', + options: statusBadgeV2Variants, + description: 'Visual variant: primary (tinted bg) or secondary (transparent)', + }, + size: { + control: 'select', + options: statusBadgeV2Sizes, + description: 'Badge size', + }, + className: { table: { disable: true } }, + style: { table: { disable: true } }, + ref: { table: { disable: true } }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + phase: 'active', + label: 'Active', + }, +}; + +export const AllPhases: Story = { + render: () => ( + + +
Primary — Medium
+ + + + + + + + + + + + +
+ + +
Primary — Small
+ + + + + + + + + + + + +
+ + +
Secondary — Medium
+ + + + + + + + + + + + +
+ + +
Secondary — Small
+ + + + + + + + + + + + +
+
+ ), +}; + +export const IconOnly: Story = { + render: () => ( + + +
Icon-only (hover for tooltip)
+ + + + + + + + + + + + +
+ + +
Icon-only — Small
+ + + + + + + + + + + + +
+ + +
Icon-only Secondary
+ + + + + + + + + + + + +
+
+ ), +}; + +export const TextOnly: Story = { + render: () => ( + +
Text-only (icon=null)
+ + + + + + + + + + + + +
+ ), +}; + +const phaseExamples: Record = { + 'not-started': 'Vacant, Spare', + temporary: 'Design, Draft', + 'in-review': 'PnR, L1 design complete', + pending: 'Reserved, Pending, Ordered', + active: 'Active, Installed, In-use', + execution: 'Testing, Upgrading, Initializing', + 'result-succeeded': 'Test passed, Provisioning complete', + 'result-warning': 'Domain-specific warnings', + 'result-failed': 'Cancelled, Fault, Failed, Disconnected', + deprecated: 'Decommissioned, Sacrificed', +}; + +export const PhaseReference: Story = { + render: () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PhaseBadgeIconExample Domain Statuses
+ not-started + + + + + {phaseExamples['not-started']}
+ temporary + + + + + {phaseExamples.temporary}
+ in-review + + + + + {phaseExamples['in-review']}
+ pending + + + + + {phaseExamples.pending}
+ active + + + + + {phaseExamples.active}
+ execution + + + + + {phaseExamples.execution}
+ result-succeeded + + + + + {phaseExamples['result-succeeded']}
+ result-warning + + + + + {phaseExamples['result-warning']}
+ result-failed + + + + + {phaseExamples['result-failed']}
+ deprecated + + + + + {phaseExamples.deprecated}
+
+ ), +}; diff --git a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.tsx b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.tsx new file mode 100644 index 000000000..2b7131ae3 --- /dev/null +++ b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.tsx @@ -0,0 +1,77 @@ +import type { Ref } from 'react'; +import classNames from 'classnames'; +import styles from './ds-status-badge-v2.module.scss'; +import type { DsStatusBadgeV2Props } from './ds-status-badge-v2.types'; +import { phaseIconMap } from './phase-config'; +import { DsIcon } from '../ds-icon'; +import { DsTooltip } from '../ds-tooltip'; +import { DsTypography } from '../ds-typography'; + +const DsStatusBadgeV2 = ({ + phase, + label, + icon, + iconOnly = false, + variant = 'primary', + size = 'medium', + className, + style, + ref, + 'aria-label': ariaLabel, +}: DsStatusBadgeV2Props) => { + const resolvedIcon = icon === null ? null : (icon ?? phaseIconMap[phase]); + const hasIcon = resolvedIcon !== null; + const isTextOnly = !hasIcon; + const tooltipContent = iconOnly ? label : undefined; + + const iconStyle = + size === 'small' + ? { + fontSize: 'var(--_badge-icon-size)', + width: 'var(--_badge-icon-size)', + height: 'var(--_badge-icon-size)', + } + : undefined; + + const rootClass = classNames( + styles.root, + styles[phase], + styles[size], + { + [styles.secondary]: variant === 'secondary', + [styles.iconOnly]: iconOnly, + [styles.textOnly]: isTextOnly, + }, + className, + ); + + const badge = ( +
} + className={rootClass} + style={style} + role="status" + aria-label={ariaLabel ?? label} + > + {hasIcon && ( + + + )} + + {!iconOnly && ( + + {label} + + )} +
+ ); + + if (iconOnly) { + return {badge}; + } + + return badge; +}; + +export default DsStatusBadgeV2; diff --git a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts new file mode 100644 index 000000000..9c57ad1f0 --- /dev/null +++ b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts @@ -0,0 +1,77 @@ +import type { CSSProperties, Ref } from 'react'; +import type { IconType } from '../ds-icon'; + +export const statusBadgeV2Phases = [ + 'not-started', + 'temporary', + 'in-review', + 'pending', + 'active', + 'execution', + 'result-succeeded', + 'result-warning', + 'result-failed', + 'deprecated', +] as const; + +export type StatusBadgeV2Phase = (typeof statusBadgeV2Phases)[number]; + +export const statusBadgeV2Variants = ['primary', 'secondary'] as const; +export type StatusBadgeV2Variant = (typeof statusBadgeV2Variants)[number]; + +export const statusBadgeV2Sizes = ['medium', 'small'] as const; +export type StatusBadgeV2Size = (typeof statusBadgeV2Sizes)[number]; + +export interface DsStatusBadgeV2Props { + /** + * Lifecycle phase that determines color and default icon. + * Each phase maps to a color palette and a default Material Symbols icon. + */ + phase: StatusBadgeV2Phase; + + /** + * Domain-specific text label. Always required for accessibility. + * Displayed as text in the badge unless `iconOnly` is true, + * in which case it becomes the tooltip content. + */ + label: string; + + /** + * Override the default phase icon. Pass `null` to force a text-only badge + * (no icon rendered). When omitted, the default icon for the phase is used. + */ + icon?: IconType | null; + + /** + * When true, hides the label text and renders icon-only. + * The label is used as tooltip content instead. + * @default false + */ + iconOnly?: boolean; + + /** + * Visual variant controlling background treatment. + * - `'primary'`: tinted background pill or circle + * - `'secondary'`: no background, inline presentation + * @default 'primary' + */ + variant?: StatusBadgeV2Variant; + + /** + * Badge size controlling height, padding, and icon dimensions. + * @default 'medium' + */ + size?: StatusBadgeV2Size; + + /** Additional CSS class name merged onto the root element. */ + className?: string; + + /** Inline styles applied to the root element. */ + style?: CSSProperties; + + /** Ref forwarded to the root element. */ + ref?: Ref; + + /** Accessible label for screen readers. Overrides the label. */ + 'aria-label'?: string; +} diff --git a/packages/design-system/src/components/ds-status-badge-v2/index.ts b/packages/design-system/src/components/ds-status-badge-v2/index.ts new file mode 100644 index 000000000..1b2d8eb59 --- /dev/null +++ b/packages/design-system/src/components/ds-status-badge-v2/index.ts @@ -0,0 +1,8 @@ +export { default as DsStatusBadgeV2 } from './ds-status-badge-v2'; +export type { + StatusBadgeV2Phase, + StatusBadgeV2Variant, + StatusBadgeV2Size, + DsStatusBadgeV2Props, +} from './ds-status-badge-v2.types'; +export { statusBadgeV2Phases, statusBadgeV2Variants, statusBadgeV2Sizes } from './ds-status-badge-v2.types'; diff --git a/packages/design-system/src/components/ds-status-badge-v2/phase-config.ts b/packages/design-system/src/components/ds-status-badge-v2/phase-config.ts new file mode 100644 index 000000000..a9d8992cb --- /dev/null +++ b/packages/design-system/src/components/ds-status-badge-v2/phase-config.ts @@ -0,0 +1,15 @@ +import type { IconType } from '../ds-icon'; +import type { StatusBadgeV2Phase } from './ds-status-badge-v2.types'; + +export const phaseIconMap: Record = Object.freeze({ + 'not-started': 'lasso_select', + temporary: 'hourglass_empty', + 'in-review': 'document_search', + pending: 'timer_pause', + active: 'power_settings_circle', + execution: 'timelapse', + 'result-succeeded': 'verified', + 'result-warning': 'warning', + 'result-failed': 'cancel', + deprecated: 'inventory_2', +}); diff --git a/packages/design-system/src/components/ds-status-badge/ds-status-badge.types.ts b/packages/design-system/src/components/ds-status-badge/ds-status-badge.types.ts index 28c5e3f24..e560e3b2b 100644 --- a/packages/design-system/src/components/ds-status-badge/ds-status-badge.types.ts +++ b/packages/design-system/src/components/ds-status-badge/ds-status-badge.types.ts @@ -1,13 +1,28 @@ import type { CSSProperties } from 'react'; import type { IconType } from '../ds-icon'; +/** + * @deprecated Use `statusBadgeV2Phases` and `StatusBadgeV2Phase` from `DsStatusBadgeV2` instead. + */ export const dsStatuses = ['active', 'running', 'pending', 'draft', 'inactive', 'warning', 'failed'] as const; +/** + * @deprecated Use `StatusBadgeV2Phase` from `DsStatusBadgeV2` instead. + */ export type DsStatus = (typeof dsStatuses)[number]; +/** + * @deprecated Use `statusBadgeV2Sizes` and `StatusBadgeV2Size` from `DsStatusBadgeV2` instead. + */ export const statusBadgeSizes = ['medium', 'small'] as const; +/** + * @deprecated Use `StatusBadgeV2Size` from `DsStatusBadgeV2` instead. + */ export type StatusBadgeSize = (typeof statusBadgeSizes)[number]; +/** + * @deprecated Use `DsStatusBadgeV2Props` and `DsStatusBadgeV2` instead. + */ export interface DsStatusBadgeProps { /** * The icon of the status badge diff --git a/packages/design-system/src/index.ts b/packages/design-system/src/index.ts index be0efec60..6ed242962 100644 --- a/packages/design-system/src/index.ts +++ b/packages/design-system/src/index.ts @@ -47,6 +47,7 @@ export * from './components/ds-spinner'; export * from './components/ds-split-button'; export * from './components/ds-stack'; export * from './components/ds-status-badge'; +export * from './components/ds-status-badge-v2'; export * from './components/ds-stepper'; export * from './components/ds-system-status'; export * from './components/ds-table'; diff --git a/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts b/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts index 92922f8b2..c63619fb7 100644 --- a/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts +++ b/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts @@ -91,15 +91,34 @@ ruleTester.run('no-deprecated-ds-confirmation', plugin.rules['no-deprecated-ds-c ], }); +ruleTester.run('no-deprecated-ds-status-badge', plugin.rules['no-deprecated-ds-status-badge'], { + valid: [''], + + invalid: [ + { + code: '', + errors: [ + { + message: `DsStatusBadge is deprecated. Use DsStatusBadgeV2 instead.`, + line: 1, + endLine: 1, + column: 2, + endColumn: 15, + }, + ], + }, + ], +}); + ruleTester.run('no-deprecated-ds-system-status', plugin.rules['no-deprecated-ds-system-status'], { - valid: [''], + valid: [''], invalid: [ { code: '', errors: [ { - message: `DsSystemStatus is deprecated. Use DsStatusBadge instead.`, + message: `DsSystemStatus is deprecated. Use DsStatusBadgeV2 instead.`, line: 1, endLine: 1, column: 2, diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts index afa3524f7..d5a9b88b6 100644 --- a/packages/eslint-plugin/src/index.ts +++ b/packages/eslint-plugin/src/index.ts @@ -27,10 +27,16 @@ const eslintPlugin = createPlugin( message: `DsConfirmation is deprecated. Use DsModal instead.`, }, + { + name: 'no-deprecated-ds-status-badge', + selector: JSXElementName('DsStatusBadge'), + message: `DsStatusBadge is deprecated. Use DsStatusBadgeV2 instead.`, + }, + { name: 'no-deprecated-ds-system-status', selector: JSXElementName('DsSystemStatus'), - message: `DsSystemStatus is deprecated. Use DsStatusBadge instead.`, + message: `DsSystemStatus is deprecated. Use DsStatusBadgeV2 instead.`, }, { From da8780e38c7d5d16cbb47c03a90dd715be14c2d3 Mon Sep 17 00:00:00 2001 From: Michal Murawski Date: Wed, 13 May 2026 15:30:11 +0200 Subject: [PATCH 2/4] CR changes --- .../ds-status-badge-v2.stories.tsx | 20 +++++++++++++++++ .../ds-status-badge-v2/ds-status-badge-v2.tsx | 4 ++-- .../ds-status-badge-v2.types.ts | 22 ++++++++----------- .../components/ds-status-badge-v2/index.ts | 7 +++++- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.stories.tsx b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.stories.tsx index f6a5885f7..55acf36e3 100644 --- a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.stories.tsx +++ b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import DsStatusBadgeV2 from './ds-status-badge-v2'; +import { DsStatusBadgeV2 as DsStatusBadgeV2Responsive } from './index'; import { type StatusBadgeV2Phase, statusBadgeV2Phases, @@ -433,3 +434,22 @@ export const PhaseReference: Story = { ), }; + +export const ResponsiveSize: Story = { + parameters: { layout: 'centered' }, + render: () => ( + + + + + + ), +}; diff --git a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.tsx b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.tsx index 2b7131ae3..39989f8b0 100644 --- a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.tsx +++ b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.tsx @@ -1,7 +1,7 @@ import type { Ref } from 'react'; import classNames from 'classnames'; import styles from './ds-status-badge-v2.module.scss'; -import type { DsStatusBadgeV2Props } from './ds-status-badge-v2.types'; +import type { DsStatusBadgeV2BaseProps } from './ds-status-badge-v2.types'; import { phaseIconMap } from './phase-config'; import { DsIcon } from '../ds-icon'; import { DsTooltip } from '../ds-tooltip'; @@ -18,7 +18,7 @@ const DsStatusBadgeV2 = ({ style, ref, 'aria-label': ariaLabel, -}: DsStatusBadgeV2Props) => { +}: DsStatusBadgeV2BaseProps) => { const resolvedIcon = icon === null ? null : (icon ?? phaseIconMap[phase]); const hasIcon = resolvedIcon !== null; const isTextOnly = !hasIcon; diff --git a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts index 9c57ad1f0..e30abf234 100644 --- a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts +++ b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts @@ -1,5 +1,6 @@ import type { CSSProperties, Ref } from 'react'; import type { IconType } from '../ds-icon'; +import type { ResponsiveValue } from '../../utils/responsive'; export const statusBadgeV2Phases = [ 'not-started', @@ -22,23 +23,18 @@ export type StatusBadgeV2Variant = (typeof statusBadgeV2Variants)[number]; export const statusBadgeV2Sizes = ['medium', 'small'] as const; export type StatusBadgeV2Size = (typeof statusBadgeV2Sizes)[number]; -export interface DsStatusBadgeV2Props { +export interface DsStatusBadgeV2BaseProps { /** * Lifecycle phase that determines color and default icon. * Each phase maps to a color palette and a default Material Symbols icon. */ phase: StatusBadgeV2Phase; - /** - * Domain-specific text label. Always required for accessibility. - * Displayed as text in the badge unless `iconOnly` is true, - * in which case it becomes the tooltip content. - */ label: string; /** - * Override the default phase icon. Pass `null` to force a text-only badge - * (no icon rendered). When omitted, the default icon for the phase is used. + * Override the default phase icon. Pass `null` to force a text-only badge. + * When omitted, the default icon for the phase is used. */ icon?: IconType | null; @@ -58,20 +54,20 @@ export interface DsStatusBadgeV2Props { variant?: StatusBadgeV2Variant; /** - * Badge size controlling height, padding, and icon dimensions. + * Badge size controlling height, padding, and icon dimensions. Responsive. * @default 'medium' */ size?: StatusBadgeV2Size; - /** Additional CSS class name merged onto the root element. */ className?: string; - /** Inline styles applied to the root element. */ style?: CSSProperties; - /** Ref forwarded to the root element. */ ref?: Ref; - /** Accessible label for screen readers. Overrides the label. */ 'aria-label'?: string; } + +export interface DsStatusBadgeV2Props extends Omit { + size?: ResponsiveValue; +} diff --git a/packages/design-system/src/components/ds-status-badge-v2/index.ts b/packages/design-system/src/components/ds-status-badge-v2/index.ts index 1b2d8eb59..b87eeab5e 100644 --- a/packages/design-system/src/components/ds-status-badge-v2/index.ts +++ b/packages/design-system/src/components/ds-status-badge-v2/index.ts @@ -1,8 +1,13 @@ -export { default as DsStatusBadgeV2 } from './ds-status-badge-v2'; +import { withResponsiveProps } from '../../utils/responsive'; +import DsStatusBadgeV2Base from './ds-status-badge-v2'; + +export const DsStatusBadgeV2 = withResponsiveProps(DsStatusBadgeV2Base, ['size']); + export type { StatusBadgeV2Phase, StatusBadgeV2Variant, StatusBadgeV2Size, + DsStatusBadgeV2BaseProps, DsStatusBadgeV2Props, } from './ds-status-badge-v2.types'; export { statusBadgeV2Phases, statusBadgeV2Variants, statusBadgeV2Sizes } from './ds-status-badge-v2.types'; From a71a238f1d20d30f533ab70fc278773d4d9b4851 Mon Sep 17 00:00:00 2001 From: Michal Murawski Date: Thu, 14 May 2026 15:53:48 +0200 Subject: [PATCH 3/4] CR chagnges v2 --- .../ds-status-badge-v2.types.ts | 47 ++++++++++++------- .../ds-status-badge-v2.unit.test.ts | 25 ++++++++++ 2 files changed, 56 insertions(+), 16 deletions(-) create mode 100644 packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.unit.test.ts diff --git a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts index e30abf234..053f68979 100644 --- a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts +++ b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts @@ -23,7 +23,7 @@ export type StatusBadgeV2Variant = (typeof statusBadgeV2Variants)[number]; export const statusBadgeV2Sizes = ['medium', 'small'] as const; export type StatusBadgeV2Size = (typeof statusBadgeV2Sizes)[number]; -export interface DsStatusBadgeV2BaseProps { +interface DsStatusBadgeV2SharedProps { /** * Lifecycle phase that determines color and default icon. * Each phase maps to a color palette and a default Material Symbols icon. @@ -32,19 +32,6 @@ export interface DsStatusBadgeV2BaseProps { label: string; - /** - * Override the default phase icon. Pass `null` to force a text-only badge. - * When omitted, the default icon for the phase is used. - */ - icon?: IconType | null; - - /** - * When true, hides the label text and renders icon-only. - * The label is used as tooltip content instead. - * @default false - */ - iconOnly?: boolean; - /** * Visual variant controlling background treatment. * - `'primary'`: tinted background pill or circle @@ -68,6 +55,34 @@ export interface DsStatusBadgeV2BaseProps { 'aria-label'?: string; } -export interface DsStatusBadgeV2Props extends Omit { +/** + * When `iconOnly` is true, `icon` cannot be `null` — the badge must have an icon + * (either the phase default or a custom one). When `iconOnly` is false or omitted, + * `icon` can be `null` to force a text-only badge. + */ +export type StatusBadgeV2IconOnlyProps = + | { + /** When true, hides the label text and renders icon-only. The label is used as tooltip content instead. */ + iconOnly: true; + /** Override the default phase icon. When omitted, the default icon for the phase is used. */ + icon?: IconType; + } + | { + /** + * When true, hides the label text and renders icon-only. + * The label is used as tooltip content instead. + * @default false + */ + iconOnly?: false; + /** + * Override the default phase icon. Pass `null` to force a text-only badge. + * When omitted, the default icon for the phase is used. + */ + icon?: IconType | null; + }; + +export type DsStatusBadgeV2BaseProps = DsStatusBadgeV2SharedProps & StatusBadgeV2IconOnlyProps; + +export type DsStatusBadgeV2Props = Omit & { size?: ResponsiveValue; -} +} & StatusBadgeV2IconOnlyProps; diff --git a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.unit.test.ts b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.unit.test.ts new file mode 100644 index 000000000..7237b2cc1 --- /dev/null +++ b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.unit.test.ts @@ -0,0 +1,25 @@ +/* eslint-disable vitest/expect-expect */ +import { describe, expectTypeOf, it } from 'vitest'; +import type { StatusBadgeV2IconOnlyProps } from './ds-status-badge-v2.types'; + +describe('DsStatusBadgeV2 types', () => { + it('allows iconOnly with a custom icon', () => { + expectTypeOf<{ iconOnly: true; icon: () => null }>().toExtend(); + }); + + it('allows iconOnly without icon (phase default)', () => { + expectTypeOf<{ iconOnly: true }>().toExtend(); + }); + + it('allows icon=null when iconOnly is false', () => { + expectTypeOf<{ iconOnly: false; icon: null }>().toExtend(); + }); + + it('allows icon=null when iconOnly is omitted', () => { + expectTypeOf<{ icon: null }>().toExtend(); + }); + + it('prevents iconOnly=true with icon=null', () => { + expectTypeOf<{ iconOnly: true; icon: null }>().not.toExtend(); + }); +}); From 6923281c551452f5e2fcdcc4358dc604a97ba3f3 Mon Sep 17 00:00:00 2001 From: Michal Murawski Date: Thu, 14 May 2026 16:04:18 +0200 Subject: [PATCH 4/4] CR changes v3 --- .../ds-status-badge-v2/ds-status-badge-v2.types.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts index 053f68979..2973dddd8 100644 --- a/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts +++ b/packages/design-system/src/components/ds-status-badge-v2/ds-status-badge-v2.types.ts @@ -23,7 +23,7 @@ export type StatusBadgeV2Variant = (typeof statusBadgeV2Variants)[number]; export const statusBadgeV2Sizes = ['medium', 'small'] as const; export type StatusBadgeV2Size = (typeof statusBadgeV2Sizes)[number]; -interface DsStatusBadgeV2SharedProps { +export interface DsStatusBadgeV2SharedProps { /** * Lifecycle phase that determines color and default icon. * Each phase maps to a color palette and a default Material Symbols icon. @@ -68,16 +68,7 @@ export type StatusBadgeV2IconOnlyProps = icon?: IconType; } | { - /** - * When true, hides the label text and renders icon-only. - * The label is used as tooltip content instead. - * @default false - */ iconOnly?: false; - /** - * Override the default phase icon. Pass `null` to force a text-only badge. - * When omitted, the default icon for the phase is used. - */ icon?: IconType | null; };