Skip to content
Merged
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
70 changes: 70 additions & 0 deletions src/components/StatusBadge.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,76 @@ describe('StatusBadge', () => {
expect(badge.getAttribute('aria-description')).toContain('diagonal stripes');
});

// ── showPattern toggle (Issue #685) ──────────────────────────────────────

it('hides pattern data when showPattern=false', () => {
render(<StatusBadge status="error" showPattern={false} />);
const badge = screen.getByRole('img', { name: 'Error' }) as HTMLElement;
expect(badge.getAttribute('data-pattern-enabled')).toBe('false');
expect(badge.getAttribute('data-pattern-description')).toBe('none');
expect(badge.getAttribute('aria-description')).toContain('no pattern');
});

it('applies sb-pattern--disabled class when showPattern=false', () => {
render(<StatusBadge status="pending" showPattern={false} />);
const badge = screen.getByRole('img', { name: 'Pending' });
expect(badge.classList.contains('sb-pattern--disabled')).toBe(true);
});

// ── patternStyle modifier (Issue #685) ───────────────────────────────────

it('applies default pattern style by default', () => {
render(<StatusBadge status="maintenance" />);
const badge = screen.getByRole('img', { name: 'Maintenance' }) as HTMLElement;
expect(badge.getAttribute('data-pattern-style')).toBe('default');
});

it('applies dense pattern style modifier', () => {
render(<StatusBadge status="maintenance" patternStyle="dense" />);
const badge = screen.getByRole('img', { name: 'Maintenance' });
expect(badge.classList.contains('sb-pattern--dense')).toBe(true);
expect(badge.getAttribute('data-pattern-style')).toBe('dense');
});

it('applies high-contrast pattern style modifier', () => {
render(<StatusBadge status="maintenance" patternStyle="high-contrast" />);
const badge = screen.getByRole('img', { name: 'Maintenance' });
expect(badge.classList.contains('sb-pattern--high-contrast')).toBe(true);
expect(badge.getAttribute('data-pattern-style')).toBe('high-contrast');
});

// ── down variant ─────────────────────────────────────────────────────────

it('applies sb-pattern-down CSS class for the down variant', () => {
render(<StatusBadge status="down" />);
const badge = screen.getByRole('img', { name: 'Down' });
expect(badge.classList.contains('sb-pattern-down')).toBe(true);
});

it('exposes data-pattern="stripes" and pattern description for down variant', () => {
render(<StatusBadge status="down" />);
const badge = screen.getByRole('img', { name: 'Down' }) as HTMLElement;
expect(badge.getAttribute('data-pattern')).toBe('stripes');
expect(badge.getAttribute('data-pattern-description')).toBe('diagonal stripes');
expect(badge.getAttribute('aria-description')).toContain('diagonal stripes');
});

// ── data-pattern-description attribute (Issue #685) ──────────────────────

it('exposes data-pattern-description for every variant', () => {
const variants: StatusVariant[] = [
'success', 'error', 'warning', 'operational',
'degraded', 'down', 'pending', 'maintenance',
];
variants.forEach((status) => {
const { unmount } = render(<StatusBadge status={status} />);
const badge = screen.getByRole('img') as HTMLElement;
expect(badge.getAttribute('data-pattern-description')).toBeTruthy();
expect(badge.getAttribute('data-pattern-description')).not.toBe('none');
unmount();
});
});

// ── maintenance variant ───────────────────────────────────────────────────

it('renders with default label "Maintenance" for the maintenance variant', () => {
Expand Down
19 changes: 15 additions & 4 deletions src/components/StatusBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,25 @@
* status is distinguishable by texture as well as by color. This satisfies
* WCAG 1.4.1 (Use of Color) and helps users with deuteranopia/protanopia.
*
* Each status belongs to a "pattern group" — related severity levels share
* a texture so users learn the relationship between patterns and meaning:
* Baseline (solid) → success, operational
* Diagonal stripes ╲ → error, down
* Opposite stripes ╱ → warning, degraded
* Dots → pending
* Crosshatch ╳ → maintenance
*
* Usage:
* <StatusBadge status="operational" />
* <StatusBadge status="error" label="API Error" />
*
* Props:
* status — one of the six supported variants (see StatusVariant below)
* label — optional override for the visible text; defaults to the
* capitalised status name
* className — passed through to the root element for layout composition
* status — one of the eight supported variants (see StatusVariant)
* label — optional override for the visible text; defaults to
* the capitalised status name
* className — passed through to the root element
* showPattern — set to false to hide the texture pattern (default: true)
* patternStyle — "default" | "dense" | "high-contrast" (default: "default")
*/

import React from "react";
Expand Down Expand Up @@ -148,6 +158,7 @@ export function StatusBadge({
data-pattern={patternKey}
data-pattern-enabled={showPattern}
data-pattern-style={patternStyle}
data-pattern-description={showPattern ? patternDescription : "none"}
style={{
display: "inline-flex",
alignItems: "center",
Expand Down
9 changes: 8 additions & 1 deletion src/styles/patterns.css
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
/* src/styles/patterns.css */

/* ── StatusBadge: color-blind-safe patterns ────────────────────────────────
/* ── StatusBadge: color-blind-safe patterns (Issue #685) ──────────────────
Each status gets a unique CSS background-image pattern so that the badge
can be distinguished by shape/texture alone, satisfying WCAG 1.4.1
(Use of Color) and supporting deuteranopia / protanopia / tritanopia.

Patterns are applied as a second background layer on top of the solid
background-color already set by the --sb-*-bg tokens. They are rendered
as inline SVG data URIs so no external assets are required.

Pattern groups (related severity levels share a texture):
Baseline (solid) → success, operational
Diagonal stripes ╲ → error, down
Opposite stripes ╱ → warning, degraded
Dots → pending
Crosshatch ╳ → maintenance
──────────────────────────────────────────────────────────────────────── */

/* Success / Operational: no pattern — solid color baseline */
Expand Down
Loading