diff --git a/src/components/StatusBadge.test.tsx b/src/components/StatusBadge.test.tsx index 9a6d30c..6120696 100644 --- a/src/components/StatusBadge.test.tsx +++ b/src/components/StatusBadge.test.tsx @@ -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(); + 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(); + 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(); + const badge = screen.getByRole('img', { name: 'Maintenance' }) as HTMLElement; + expect(badge.getAttribute('data-pattern-style')).toBe('default'); + }); + + it('applies dense pattern style modifier', () => { + render(); + 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(); + 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(); + 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(); + 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(); + 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', () => { diff --git a/src/components/StatusBadge.tsx b/src/components/StatusBadge.tsx index 02d3ddc..4faae95 100644 --- a/src/components/StatusBadge.tsx +++ b/src/components/StatusBadge.tsx @@ -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: * * * * 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"; @@ -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", diff --git a/src/styles/patterns.css b/src/styles/patterns.css index 35d0440..900e9ce 100644 --- a/src/styles/patterns.css +++ b/src/styles/patterns.css @@ -1,6 +1,6 @@ /* 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. @@ -8,6 +8,13 @@ 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 */