Skip to content
Draft
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
51 changes: 49 additions & 2 deletions frontend/documentation/components/Chip.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import type { Meta, StoryObj } from 'storybook'

import Chip from 'components/base/Chip'
import Chip, { ChipDot } from 'components/base/Chip'

const meta: Meta<typeof Chip> = {
args: { children: 'Production' },
Expand All @@ -11,7 +11,7 @@ const meta: Meta<typeof Chip> = {
docs: {
description: {
component:
'Canonical token-based chip primitive: a small labelled pill token. Layout via Bootstrap utilities, colour/radius via token utilities, padding/sizes/border/truncation in SCSS. Leading/trailing icons go in as children. Selection lives in ToggleChip and count badges are a separate Badge concern. The legacy `.chip` (old SCSS vars + manual dark-mode block, ~35×) migrates onto this under #6606.',
'Canonical token-based chip primitive: a small labelled pill token. Layout via Bootstrap utilities, colour/radius via token utilities, padding/sizes/border/truncation in SCSS. Leading/trailing icons go in as children. `variant` covers neutral, accent, the five status colours and `solid`; `ChipDot` adds the leading dot in `currentColor`. Radius is a fixed 6px from the tags frame, so there is no shape prop. Selection lives in ToggleChip. The legacy `.chip` (old SCSS vars + manual dark-mode block, ~35×) migrates onto this under #6606.',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace the inaccurate “pill token” description.

The fixed 6px radius does not produce a pill shape. Change “pill token” to “chip” or “labelled token” so the description matches the component API and rendered shape.

},
},
layout: 'centered',
Expand All @@ -28,6 +28,10 @@ export const Accent: Story = {
args: { children: '"hello"', variant: 'accent' },
}

export const Solid: Story = {
args: { children: 'Enterprise', variant: 'solid' },
}

export const Sizes: Story = {
render: () => (
<div className='d-flex align-items-center gap-2'>
Expand All @@ -38,6 +42,49 @@ export const Sizes: Story = {
),
}

export const StatusVariants: Story = {
render: () => (
<div className='d-flex align-items-center gap-2'>
<Chip variant='info' size='sm'>
<ChipDot />
Draft
</Chip>
<Chip variant='success' size='sm'>
<ChipDot />
Running
</Chip>
<Chip variant='warning' size='sm'>
<ChipDot />
Paused
</Chip>
<Chip variant='danger' size='sm'>
<ChipDot />
Failed
</Chip>
<Chip variant='muted' size='sm'>
<ChipDot />
Completed
</Chip>
</div>
),
}

export const Counts: Story = {
render: () => (
<div className='d-flex align-items-center gap-2'>
<Chip variant='accent' size='xs'>
5
</Chip>
<Chip variant='neutral' size='xs'>
0
</Chip>
<Chip variant='neutral' size='xs'>
128
</Chip>
</div>
),
}

export const Removable: Story = {
args: { children: 'feature-flag', onRemove: () => undefined },
}
Expand Down
25 changes: 25 additions & 0 deletions frontend/web/components/base/Chip/Chip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,38 @@
border-color: var(--color-border-action);
}

// Status colours carry their meaning in the fill, so an outline only adds
// noise. Transparent rather than removed, so a status chip is the same
// height as a bordered one beside it.
&--success,
&--warning,
&--danger,
&--info,
&--muted,
&--solid {
border-color: transparent;
}

// Status dot. currentColor, so it follows the variant with nothing to wire.
&__dot {
width: 6px;
height: 6px;
border-radius: var(--radius-full);
background: currentColor;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use the configured currentcolor keyword spelling.

Stylelint reports currentColor as a value-keyword-case error. This prevents the touched stylesheet from passing the configured lint check.

Proposed fix
-    background: currentColor;
+    background: currentcolor;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
background: currentColor;
background: currentcolor;
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 31-31: Expected "currentColor" to be "currentcolor" (value-keyword-case)

(value-keyword-case)

Source: Linters/SAST tools

flex-shrink: 0;
}

// Sizes (default is the base above).
&--sm {
padding: 3px 8px;
font-size: 0.75rem;
}

// 24px is fixed in the tags frame, so it is stated rather than left to derive
// from the inherited line-height. The frame's 8px vertical padding is an
// artefact of a height override on the auto-layout and is not applied.
&--xs {
height: 24px;
padding: 1px 6px;
font-size: 0.6875rem;
}
Expand Down
26 changes: 23 additions & 3 deletions frontend/web/components/base/Chip/Chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ import { colorIconSecondary } from 'common/theme/tokens'
import './Chip.scss'

export type ChipSize = 'default' | 'sm' | 'xs'
export type ChipVariant = 'neutral' | 'accent'
export type ChipVariant =
| 'neutral'
| 'accent'
| 'success'
| 'warning'
| 'danger'
| 'info'
| 'muted'
| 'solid'

export type ChipProps = {
children: ReactNode
Expand All @@ -30,7 +38,17 @@ export type ChipProps = {
// bg + text come from token utilities; the variant border lives in Chip.scss.
const VARIANT_UTILITIES: Record<ChipVariant, string> = {
accent: 'bg-surface-action-subtle text-action',
danger: 'bg-surface-danger text-danger',
info: 'bg-surface-info text-info',
muted: 'bg-surface-muted text-secondary',
neutral: 'bg-surface-subtle text-default',
// The one filled variant. `text-white` rather than a token because there is
// no inverse-text token yet; white on --color-surface-action is 5.93:1, so AA
// but not AAA. Note the app has a second, darker solid (`bg-primary900`, used
// by BetaFlag and PlanBasedAccess) that this deliberately does not cover.
solid: 'bg-surface-action text-white',
success: 'bg-surface-success text-success',
warning: 'bg-surface-warning text-warning',
}

// Token-based chip primitive. Uses `ds-chip` rather than the legacy `.chip`
Expand Down Expand Up @@ -58,10 +76,12 @@ const Chip = ({
<span
ref={ref}
className={classNames(
'ds-chip d-inline-flex align-items-center align-middle gap-1 rounded-sm',
// rounded-md is 6px, fixed by the tags frame in Figma. Not a prop:
// every chip is the same shape.
'ds-chip d-inline-flex align-items-center align-middle gap-1 rounded-md',
VARIANT_UTILITIES[variant],
`ds-chip--${variant}`,
{
'ds-chip--accent': variant === 'accent',
'ds-chip--clickable': interactive,
[`ds-chip--${size}`]: size !== 'default',
'ds-chip--truncate': truncate,
Expand Down
6 changes: 6 additions & 0 deletions frontend/web/components/base/Chip/ChipDot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React, { FC } from 'react'

// The leading dot on a status chip. Decorative: the label carries the meaning.
const ChipDot: FC = () => <span className='ds-chip__dot' aria-hidden='true' />

export default ChipDot
Comment on lines +4 to +6

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move ChipDot into its required component folder.

The new ChipDot component does not follow the required component layout. Create ChipDot/ChipDot.tsx, co-locate ChipDot.scss, and add ChipDot/index.ts.

  • frontend/web/components/base/Chip/ChipDot.tsx#L4-L6: move the component into ChipDot/ChipDot.tsx and move its styles into ChipDot/ChipDot.scss.
  • frontend/web/components/base/Chip/index.ts#L2-L2: re-export ChipDot through the new ChipDot/index.ts barrel.
📍 Affects 2 files
  • frontend/web/components/base/Chip/ChipDot.tsx#L4-L6 (this comment)
  • frontend/web/components/base/Chip/index.ts#L2-L2

Source: Coding guidelines

1 change: 1 addition & 0 deletions frontend/web/components/base/Chip/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default } from './Chip'
export { default as ChipDot } from './ChipDot'
export type { ChipProps, ChipSize, ChipVariant } from './Chip'

This file was deleted.

23 changes: 14 additions & 9 deletions frontend/web/components/experiments/StatusBadge/StatusBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { FC } from 'react'
import Chip, { ChipDot, ChipVariant } from 'components/base/Chip'
import { ExperimentStatus } from 'common/types/responses'
import { EXPERIMENT_STATUS_LABELS } from 'components/experiments/constants'
import './StatusBadge.scss'

const STATUS_VARIANTS: Record<ExperimentStatus, ChipVariant> = {
completed: 'muted',
created: 'info',
paused: 'warning',
running: 'success',
}

type StatusBadgeProps = {
status: ExperimentStatus
}

const StatusBadge: FC<StatusBadgeProps> = ({ status }) => {
return (
<span className={`status-badge status-badge--${status}`}>
<span className='status-badge__dot' />
{EXPERIMENT_STATUS_LABELS[status]}
</span>
)
}
const StatusBadge: FC<StatusBadgeProps> = ({ status }) => (
<Chip variant={STATUS_VARIANTS[status]} size='sm'>
<ChipDot />
{EXPERIMENT_STATUS_LABELS[status]}
</Chip>
)

export default StatusBadge
Loading