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 */