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

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

const meta: Meta<typeof Chip> = {
args: { children: 'Production' },
Expand All @@ -11,7 +12,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, and `ChipDot` adds a leading dot in `currentColor`. `variant` covers neutral, accent, the five status colours and `solid`. 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.',
},
},
layout: 'centered',
Expand Down Expand Up @@ -59,3 +60,42 @@ export const Group: Story = {
</div>
),
}

const STATUSES: ChipVariant[] = [
'success',
'warning',
'danger',
'info',
'muted',
]

export const Statuses: Story = {
render: () => (
<div className='d-flex flex-wrap gap-2'>
{STATUSES.map((variant) => (
<Chip key={variant} variant={variant}>
{variant}
</Chip>
))}
</div>
),
}

// The dot takes currentColor, so it follows the variant with nothing to wire.
export const WithDot: Story = {
render: () => (
<div className='d-flex flex-wrap gap-2'>
{STATUSES.map((variant) => (
<Chip key={variant} variant={variant}>
<ChipDot />
{variant}
</Chip>
))}
</div>
),
}

// The design system's Primary chip.
export const Solid: Story = {
args: { children: 'Live', variant: 'solid' },
}
20 changes: 20 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,33 @@
border-color: var(--color-border-action);
}

// 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;
}

// currentColor, so the dot follows the variant with nothing to wire.
&__dot {
width: 6px;
height: 6px;
background: currentColor;
}

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

// Stated, not derived from the line-height: 24px is fixed in the tags frame.
&--xs {
height: 24px;
padding: 1px 6px;
font-size: 0.6875rem;
}
Expand Down
23 changes: 20 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,15 @@ 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 design system's Primary chip. text-white, not a token: there is no
// inverse-text token yet. 5.93:1, AA.
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 +74,11 @@ const Chip = ({
<span
ref={ref}
className={classNames(
'ds-chip d-inline-flex align-items-center align-middle gap-1 rounded-sm',
// 6px, fixed by the tags frame. Not a prop: every chip is one 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
11 changes: 11 additions & 0 deletions frontend/web/components/base/Chip/ChipDot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { FC } from 'react'

// Decorative: the label carries the meaning.
const ChipDot: FC = () => (
<span
className='ds-chip__dot rounded-circle flex-shrink-0'
aria-hidden='true'
/>
)

export default ChipDot
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