Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
8857409
feat(bootstrap): add StatusColor and StatusVariant types
laura-b-g Aug 18, 2026
c477ee4
feat(bootstrap): add status color/variant, dot, and icon slots to Badge
laura-b-g Aug 18, 2026
b8e9edd
test(bootstrap): clarify Badge onClick+status test description
laura-b-g Aug 18, 2026
de57eb5
style(bootstrap): wrap Badge color ternary to match project line width
laura-b-g Aug 18, 2026
c0aa9ba
test(bootstrap): verify Label forwards status/icon props to Badge
laura-b-g Aug 18, 2026
0bacda5
feat(streams): preview the new Badge status style on the stream statu…
laura-b-g Aug 19, 2026
7899ef8
refactor(streams): dedupe status/toggle label ternaries in StatusCell
laura-b-g Aug 19, 2026
5afe9c9
docs(luma): add Badge Storybook stories
laura-b-g Aug 19, 2026
9f27347
fix(bootstrap): keep Badge's pill radius on status-driven Labels
laura-b-g Aug 19, 2026
82f9c4e
refactor(bootstrap): rename Badge's bsSize prop to size
laura-b-g Aug 19, 2026
ff840c9
docs(luma): add an interactive Badge Playground story
laura-b-g Aug 19, 2026
178e573
refactor(bootstrap): flatten Badge status into color/variant/dot props
laura-b-g Aug 19, 2026
c77e64f
docs(luma): hide generic pass-through props from the Badge docs table
laura-b-g Aug 19, 2026
e2bf8dc
fix(bootstrap): set Button's displayName and hide generic props in St…
laura-b-g Aug 20, 2026
9d02fd2
docs(luma): drop redundant Badge Playground story
laura-b-g Aug 20, 2026
ef8fc5b
docs(luma): declare explicit controls and descriptions for Badge props
laura-b-g Aug 20, 2026
96582ec
docs(luma): default leftIcon/rightIcon args so controls always render…
laura-b-g Aug 20, 2026
76b4225
docs(luma): drop comment on leftIcon/rightIcon default args
laura-b-g Aug 20, 2026
2777f26
fix(bootstrap): make Badge's dot indicator match the text color
laura-b-g Aug 20, 2026
525e0e5
fix(bootstrap): compute Badge's filled-variant text via contrastingCo…
laura-b-g Aug 20, 2026
a85977a
fix(bootstrap): keep bsSize as a legacy fallback on Badge
laura-b-g Aug 20, 2026
7775e16
fix(bootstrap): convert remaining long-form bsSize values to Mantine …
laura-b-g Aug 20, 2026
aebe5c2
fix(bootstrap): target Label's root node in border-radius assertions
laura-b-g Aug 25, 2026
583f00d
refactor(bootstrap): keep Label legacy-only, drop new Badge props fro…
laura-b-g Aug 25, 2026
d589b6e
refactor: migrate stream and event definition status toggles to Badge
laura-b-g Aug 25, 2026
cdbfc42
fix(bootstrap): set Badge font-weight to 600
laura-b-g Aug 25, 2026
0339044
style(event-definitions): drop dot, capitalize status badge text
laura-b-g Aug 25, 2026
0008680
refactor: migrate remaining bsStyle status labels to Badge
laura-b-g Aug 25, 2026
e5360cb
refactor: replace bespoke status color maps with Badge's color API
laura-b-g Aug 25, 2026
6d6d6ac
docs(luma): clarify when to use dot on Badge
laura-b-g Aug 25, 2026
7dc7422
refactor(inputs): migrate InputStateBadge to Badge, title-case status…
laura-b-g Aug 25, 2026
5537cf9
fix(luma): drop invalid empty-string leftIcon/rightIcon story defaults
laura-b-g Aug 25, 2026
611d6cf
fix(bootstrap): use Badge's designed filled-variant text color
laura-b-g Aug 25, 2026
fd8dc95
refactor(inputs): reuse StringUtils.toTitleCase instead of a local copy
laura-b-g Aug 25, 2026
fe323a9
fix(content-packs): give updatable its own color instead of gray
laura-b-g Aug 25, 2026
b42253a
Merge branch 'master' into issue-43/luma-badge-component
laura-b-g Aug 25, 2026
c771b9c
Merge branch 'master' into issue-43/luma-badge-component
laura-b-g Aug 26, 2026
0377e8a
Merge remote-tracking branch 'origin/master' into issue-43/luma-badge…
laura-b-g Aug 27, 2026
ef892e8
build(deps): update @graylog/sawmill to 4.0.11
laura-b-g Aug 27, 2026
3d6e655
refactor(bootstrap): derive Badge color/variant/size types from expor…
laura-b-g Aug 27, 2026
96441ed
fix(streams): widen status columns for the new badge content
laura-b-g Aug 27, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Copyright (C) 2020 Graylog, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
import * as React from 'react';
import type { Meta, StoryObj } from '@storybook/react-webpack5';
import { fn } from 'storybook/test';

import { Badge } from 'components/bootstrap';
import { BADGE_COLORS, BADGE_VARIANTS, BADGE_SIZES } from 'components/bootstrap/Badge';

const meta = {
title: 'Components/Badges/Badge',
component: Badge,
parameters: {
layout: 'centered',
docs: {
description: {
component: [
'A status badge matching the Luma design system. Use `color`/`variant`/`dot` for the 5',
'semantic colors (`primary`, `danger`, `success`, `warning`, `gray`) and the 2 style variants',
'(`light`, `filled`) — this is additive to the legacy `bsStyle` prop, which every other',
'Badge/Label consumer in the app keeps using unchanged.',
'',
'- Use `variant="light"` for most contexts (table cells, status columns) and `"filled"` for',
' emphasis (e.g. a "default" marker that should stand out from a plain informational one).',
'- Set `dot` for a small color dot instead of an icon — use it for a *live, actively-changing*',
' process state (e.g. Streams\' Running/Paused toggle, a system job\'s running/queued/complete',
' status). Leave it off for a *static, stored* attribute (Enabled/Disabled config flags,',
' install/edit state, severity) — there nothing is "happening" right now, so the dot reads as',
' noise rather than a live signal.',
'- `leftIcon`/`rightIcon` accept any `Icon` name and render into Mantine\'s native icon slots.',
'- Pass `onClick` to make the whole badge a real, keyboard-accessible `<button>` instead of a',
' decorative `<span>` — this works identically regardless of color system.',
].join('\n'),
},
},
},
tags: ['autodocs'],
argTypes: {
'aria-label': { table: { disable: true } },
bsSize: { table: { disable: true } },
bsStyle: { table: { disable: true } },
children: { control: 'text', description: 'The badge label text' },
className: { table: { disable: true } },
color: {
control: { type: 'select' },
options: BADGE_COLORS,
description: 'Semantic color — picks the background/text pair from the theme',
},
'data-testid': { table: { disable: true } },
dot: {
control: 'boolean',
description:
'Shows a small color dot as the left section (overrides leftIcon). Use for live/actively-changing process state only — not static attributes.',
},
leftIcon: { control: 'text', description: 'Icon name shown before the label, e.g. "play_arrow" — ignored when dot is set' },
onClick: { description: 'Renders the badge as a real, keyboard-accessible button and fires on click' },
onMouseEnter: { table: { disable: true } },
onMouseLeave: { table: { disable: true } },
rightIcon: { control: 'text', description: 'Icon name shown after the label, e.g. "pause"' },
role: { table: { disable: true } },
size: { control: { type: 'select' }, options: BADGE_SIZES, description: 'Badge size' },
style: { table: { disable: true } },
title: { table: { disable: true } },
uppercase: { table: { disable: true } },
variant: {
control: { type: 'select' },
options: BADGE_VARIANTS,
description: '"light" (tinted background) or "filled" (solid background)',
},
},
} satisfies Meta<typeof Badge>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Light: Story = {
args: {
children: 'Running',
color: 'success',
variant: 'light',
},
};

export const Filled: Story = {
args: {
children: 'Running',
color: 'success',
variant: 'filled',
},
};

export const WithDot: Story = {
args: {
children: 'Running',
color: 'success',
variant: 'light',
dot: true,
},
};

export const WithIcon: Story = {
args: {
children: 'Paused',
color: 'warning',
variant: 'light',
rightIcon: 'pause',
},
};

export const Clickable: Story = {
args: {
children: 'Running',
color: 'success',
variant: 'light',
dot: true,
rightIcon: 'pause',
onClick: fn(),
},
};

export const AllColors: Story = {
parameters: { controls: { disable: true } },
render: () => (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, max-content)', gap: '8px 24px', alignItems: 'center' }}>
{BADGE_COLORS.flatMap((color) =>
BADGE_VARIANTS.map((variant) => (
<Badge key={`${color}-${variant}`} color={color} variant={variant}>
{color} / {variant}
</Badge>
)),
)}
</div>
),
};

export const Sizes: Story = {
parameters: { controls: { disable: true } },
render: () => (
<div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
{BADGE_SIZES.map((size) => (
<Badge key={size} size={size} color="primary" variant="filled" dot>
{size}
</Badge>
))}
</div>
),
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ const meta = {
},
tags: ['autodocs'],
argTypes: {
active: { table: { disable: true } },
allowClickWhenDisabled: { table: { disable: true } },
'aria-disabled': { table: { disable: true } },
'aria-label': { table: { disable: true } },
bsStyle: {
control: { type: 'select' },
options: ['danger', 'default', 'info', 'primary', 'success', 'warning', 'gray'],
Expand All @@ -35,6 +39,27 @@ const meta = {
control: { type: 'select' },
options: ['xs', 'sm', 'md', 'lg', 'xsmall', 'small', 'large', 'medium'],
},
className: { table: { disable: true } },
'data-testid': { table: { disable: true } },
form: { table: { disable: true } },
href: { table: { disable: true } },
id: { table: { disable: true } },
name: { table: { disable: true } },
onBlur: { table: { disable: true } },
onFocus: { table: { disable: true } },
onMouseEnter: { table: { disable: true } },
onMouseLeave: { table: { disable: true } },
onMouseMove: { table: { disable: true } },
onPointerDown: { table: { disable: true } },
onPointerEnter: { table: { disable: true } },
rel: { table: { disable: true } },
role: { table: { disable: true } },
showOverflow: { table: { disable: true } },
style: { table: { disable: true } },
tabIndex: { table: { disable: true } },
target: { table: { disable: true } },
title: { table: { disable: true } },
type: { table: { disable: true } },
},
args: { onClick: fn() },
} satisfies Meta<typeof Button>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
],
"main": "index.js",
"dependencies": {
"@graylog/sawmill": "4.0.10",
"@graylog/sawmill": "4.0.11",
"@reduxjs/toolkit": "^2.5.1",
"@tanstack/react-query": "5.101.4",
"@types/react": "18.3.13",
Expand Down
73 changes: 73 additions & 0 deletions graylog2-web-interface/src/components/bootstrap/Badge.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (C) 2020 Graylog, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
import * as React from 'react';
import { render, screen } from 'wrappedTestingLibrary';
import userEvent from '@testing-library/user-event';

import Badge from './Badge';

describe('Badge', () => {
it('renders legacy bsStyle content unchanged', () => {
render(<Badge bsStyle="danger">Alert</Badge>);

expect(screen.getByText('Alert')).toBeInTheDocument();
});

it('renders as a button and fires onClick when both color and onClick are set', async () => {
const onClick = jest.fn();
render(
<Badge color="success" variant="light" onClick={onClick}>
Click me
</Badge>,
);

await userEvent.click(screen.getByRole('button', { name: 'Click me' }));

expect(onClick).toHaveBeenCalledTimes(1);
});

it('renders color/variant content', () => {
render(
<Badge color="success" variant="light">
Running
</Badge>,
);

expect(screen.getByText('Running')).toBeInTheDocument();
});

it('renders a dot indicator when dot is set', () => {
render(
<Badge color="success" variant="light" dot>
Running
</Badge>,
);

expect(screen.getByTestId('badge-dot')).toBeInTheDocument();
});

it('renders a left and right icon', () => {
render(
<Badge color="warning" variant="light" leftIcon="warning" rightIcon="pause">
Paused
</Badge>,
);

expect(screen.getByText('warning')).toHaveClass('material-symbols-rounded');
expect(screen.getByText('pause')).toHaveClass('material-symbols-rounded');
});
});
Loading
Loading