@principal-ade/industry-theme 0.1.20 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -1631,7 +1631,7 @@ var slateNeonTheme = {
1631
1631
  colors: {
1632
1632
  text: "#d0d6e0",
1633
1633
  background: "#1a1c1e",
1634
- primary: "#ff6b35",
1634
+ primary: "#F36F41",
1635
1635
  secondary: "#ff8257",
1636
1636
  accent: "#00ff00",
1637
1637
  highlight: "#2a1f18",
@@ -1650,8 +1650,8 @@ var slateNeonTheme = {
1650
1650
  textTertiary: "#6b7280",
1651
1651
  textMuted: "#4b5563",
1652
1652
  highlightBg: "#2a1f18",
1653
- highlightBorder: "#ff6b35",
1654
- textOnPrimary: "#ffffff",
1653
+ highlightBorder: "#F36F41",
1654
+ textOnPrimary: "#1a1c1e",
1655
1655
  textOnSecondary: "#ffffff",
1656
1656
  textOnAccent: "#1a1c1e"
1657
1657
  },
@@ -1872,7 +1872,7 @@ var iceTangerineDarkTheme = {
1872
1872
  zIndices: [0, 1, 10, 20, 30, 40, 50],
1873
1873
  colors: {
1874
1874
  text: "#d0e5ea",
1875
- background: "#0d274d",
1875
+ background: "#0a1829",
1876
1876
  primary: "#ff6b35",
1877
1877
  secondary: "#ff8257",
1878
1878
  accent: "#0893d2",
@@ -1882,26 +1882,26 @@ var iceTangerineDarkTheme = {
1882
1882
  warning: "#f59e0b",
1883
1883
  error: "#ef4444",
1884
1884
  info: "#0893d2",
1885
- border: "#1e3a5f",
1885
+ border: "#5a82aa",
1886
1886
  backgroundSecondary: "#0f2e58",
1887
1887
  backgroundTertiary: "#123461",
1888
1888
  backgroundLight: "#0b1f3f",
1889
- backgroundDark: "#0a1829",
1889
+ backgroundDark: "#040b15",
1890
1890
  backgroundHover: "#2a1f18",
1891
1891
  primaryBlade: "#0e2b53",
1892
1892
  surface: "#0f2e58",
1893
1893
  textSecondary: "#9fc4d4",
1894
1894
  textTertiary: "#7ba8bc",
1895
- textMuted: "#5a8a9e",
1895
+ textMuted: "#73a0b3",
1896
1896
  highlightBg: "#2a1f18",
1897
1897
  highlightBorder: "#ff6b35",
1898
- textOnPrimary: "#ffffff",
1899
- textOnSecondary: "#ffffff",
1900
- textOnAccent: "#ffffff"
1898
+ textOnPrimary: "#0d274d",
1899
+ textOnSecondary: "#0d274d",
1900
+ textOnAccent: "#0a1829"
1901
1901
  },
1902
1902
  buttons: {
1903
1903
  primary: {
1904
- color: "#ffffff",
1904
+ color: "#0d274d",
1905
1905
  bg: "primary",
1906
1906
  borderWidth: 0,
1907
1907
  "&:hover": {
@@ -2805,6 +2805,272 @@ var ThemeShowcase = ({
2805
2805
  }
2806
2806
  }, modeName)))));
2807
2807
  };
2808
+ // src/contrast.ts
2809
+ var WCAG_THRESHOLDS = {
2810
+ text: { AA: 4.5, AAA: 7 },
2811
+ large: { AA: 3, AAA: 4.5 },
2812
+ ui: { AA: 3, AAA: 3 }
2813
+ };
2814
+ function parseColor(input) {
2815
+ if (!input)
2816
+ return null;
2817
+ const color = input.trim().toLowerCase();
2818
+ if (color.startsWith("#")) {
2819
+ let hex = color.slice(1);
2820
+ if (hex.length === 3 || hex.length === 4) {
2821
+ hex = hex.split("").map((c) => c + c).join("");
2822
+ }
2823
+ if (hex.length === 6 || hex.length === 8) {
2824
+ const r = parseInt(hex.slice(0, 2), 16);
2825
+ const g = parseInt(hex.slice(2, 4), 16);
2826
+ const b = parseInt(hex.slice(4, 6), 16);
2827
+ if ([r, g, b].some((n) => Number.isNaN(n)))
2828
+ return null;
2829
+ return [r, g, b];
2830
+ }
2831
+ return null;
2832
+ }
2833
+ const rgbMatch = color.match(/^rgba?\(([^)]+)\)$/);
2834
+ if (rgbMatch) {
2835
+ const parts = rgbMatch[1].split(/[,/\s]+/).filter(Boolean).slice(0, 3);
2836
+ if (parts.length < 3)
2837
+ return null;
2838
+ const channels = parts.map((p) => p.endsWith("%") ? Math.round(parseFloat(p) / 100 * 255) : parseFloat(p));
2839
+ if (channels.some((n) => Number.isNaN(n)))
2840
+ return null;
2841
+ return [channels[0], channels[1], channels[2]];
2842
+ }
2843
+ return null;
2844
+ }
2845
+ function relativeLuminance([r, g, b]) {
2846
+ const toLinear = (c) => {
2847
+ const s = c / 255;
2848
+ return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
2849
+ };
2850
+ return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
2851
+ }
2852
+ function contrastRatio(fg, bg) {
2853
+ const a = parseColor(fg);
2854
+ const b = parseColor(bg);
2855
+ if (!a || !b)
2856
+ return null;
2857
+ const l1 = relativeLuminance(a);
2858
+ const l2 = relativeLuminance(b);
2859
+ const lighter = Math.max(l1, l2);
2860
+ const darker = Math.min(l1, l2);
2861
+ return (lighter + 0.05) / (darker + 0.05);
2862
+ }
2863
+ function gradeContrast(ratio, use) {
2864
+ const t = WCAG_THRESHOLDS[use];
2865
+ if (ratio >= t.AAA)
2866
+ return "AAA";
2867
+ if (ratio >= t.AA)
2868
+ return "AA";
2869
+ return "fail";
2870
+ }
2871
+ var CONTRAST_PAIRS = [
2872
+ { label: "text on background", fg: "text", bg: "background", use: "text" },
2873
+ { label: "text on surface", fg: "text", bg: "surface", use: "text" },
2874
+ { label: "text on backgroundSecondary", fg: "text", bg: "backgroundSecondary", use: "text" },
2875
+ { label: "textSecondary on background", fg: "textSecondary", bg: "background", use: "text" },
2876
+ { label: "textTertiary on background", fg: "textTertiary", bg: "background", use: "text" },
2877
+ { label: "textMuted on background", fg: "textMuted", bg: "background", use: "text" },
2878
+ { label: "textOnPrimary on primary", fg: "textOnPrimary", bg: "primary", use: "text" },
2879
+ { label: "textOnSecondary on secondary", fg: "textOnSecondary", bg: "secondary", use: "text" },
2880
+ { label: "textOnAccent on accent", fg: "textOnAccent", bg: "accent", use: "text" },
2881
+ { label: "primary on background", fg: "primary", bg: "background", use: "large" },
2882
+ { label: "success on background", fg: "success", bg: "background", use: "ui" },
2883
+ { label: "warning on background", fg: "warning", bg: "background", use: "ui" },
2884
+ { label: "error on background", fg: "error", bg: "background", use: "ui" },
2885
+ { label: "info on background", fg: "info", bg: "background", use: "ui" },
2886
+ { label: "border on background", fg: "border", bg: "background", use: "ui" },
2887
+ { label: "border on surface", fg: "border", bg: "surface", use: "ui" }
2888
+ ];
2889
+ function evaluateThemeContrast(theme2) {
2890
+ const results = CONTRAST_PAIRS.map((pair) => {
2891
+ const fgColor = theme2.colors[pair.fg] ?? "";
2892
+ const bgColor = theme2.colors[pair.bg] ?? "";
2893
+ const ratio = contrastRatio(fgColor, bgColor);
2894
+ const required = WCAG_THRESHOLDS[pair.use].AA;
2895
+ const level = ratio === null ? "fail" : gradeContrast(ratio, pair.use);
2896
+ return { pair, fgColor, bgColor, ratio, required, level };
2897
+ });
2898
+ const failures = results.filter((r) => r.level === "fail");
2899
+ return { results, failures, passesAA: failures.length === 0 };
2900
+ }
2901
+ // src/ContrastReport.tsx
2902
+ import React3 from "react";
2903
+ var LEVEL_STYLE = {
2904
+ AAA: { bg: "#0f7b3f", fg: "#ffffff", label: "AAA" },
2905
+ AA: { bg: "#1f6feb", fg: "#ffffff", label: "AA" },
2906
+ fail: { bg: "#cf222e", fg: "#ffffff", label: "FAIL" }
2907
+ };
2908
+ var ui = {
2909
+ fontFamily: "ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
2910
+ text: "#1c2128",
2911
+ subtle: "#57606a",
2912
+ border: "#d0d7de",
2913
+ surface: "#ffffff",
2914
+ panel: "#f6f8fa"
2915
+ };
2916
+ var Badge = ({ level }) => {
2917
+ const s = LEVEL_STYLE[level];
2918
+ return /* @__PURE__ */ React3.createElement("span", {
2919
+ style: {
2920
+ display: "inline-block",
2921
+ minWidth: 44,
2922
+ textAlign: "center",
2923
+ padding: "2px 8px",
2924
+ borderRadius: 999,
2925
+ fontSize: 11,
2926
+ fontWeight: 700,
2927
+ letterSpacing: 0.4,
2928
+ backgroundColor: s.bg,
2929
+ color: s.fg
2930
+ }
2931
+ }, s.label);
2932
+ };
2933
+ var Swatch = ({ fg, bg }) => /* @__PURE__ */ React3.createElement("span", {
2934
+ title: `${fg} on ${bg}`,
2935
+ style: {
2936
+ display: "inline-flex",
2937
+ alignItems: "center",
2938
+ justifyContent: "center",
2939
+ width: 40,
2940
+ height: 24,
2941
+ borderRadius: 4,
2942
+ border: `1px solid ${ui.border}`,
2943
+ backgroundColor: bg,
2944
+ color: fg,
2945
+ fontSize: 13,
2946
+ fontWeight: 700
2947
+ }
2948
+ }, "Aa");
2949
+ var Row = ({ r }) => {
2950
+ const failed = r.level === "fail";
2951
+ return /* @__PURE__ */ React3.createElement("tr", {
2952
+ style: { backgroundColor: failed ? "#fff5f5" : "transparent" }
2953
+ }, /* @__PURE__ */ React3.createElement("td", {
2954
+ style: cell
2955
+ }, /* @__PURE__ */ React3.createElement(Swatch, {
2956
+ fg: r.fgColor,
2957
+ bg: r.bgColor
2958
+ })), /* @__PURE__ */ React3.createElement("td", {
2959
+ style: { ...cell, fontWeight: 500 }
2960
+ }, r.pair.label, /* @__PURE__ */ React3.createElement("div", {
2961
+ style: { fontSize: 11, color: ui.subtle, fontFamily: "monospace" }
2962
+ }, r.fgColor, " / ", r.bgColor)), /* @__PURE__ */ React3.createElement("td", {
2963
+ style: { ...cell, textAlign: "right", fontVariantNumeric: "tabular-nums" }
2964
+ }, /* @__PURE__ */ React3.createElement("span", {
2965
+ style: { fontWeight: 700, color: failed ? "#cf222e" : ui.text }
2966
+ }, r.ratio === null ? "—" : `${r.ratio.toFixed(2)}:1`)), /* @__PURE__ */ React3.createElement("td", {
2967
+ style: {
2968
+ ...cell,
2969
+ textAlign: "right",
2970
+ color: ui.subtle,
2971
+ fontVariantNumeric: "tabular-nums"
2972
+ }
2973
+ }, r.required, ":1"), /* @__PURE__ */ React3.createElement("td", {
2974
+ style: { ...cell, textAlign: "center", textTransform: "capitalize", color: ui.subtle }
2975
+ }, r.pair.use), /* @__PURE__ */ React3.createElement("td", {
2976
+ style: { ...cell, textAlign: "center" }
2977
+ }, /* @__PURE__ */ React3.createElement(Badge, {
2978
+ level: r.level
2979
+ })));
2980
+ };
2981
+ var cell = {
2982
+ padding: "8px 12px",
2983
+ borderBottom: `1px solid ${ui.border}`,
2984
+ fontSize: 13,
2985
+ verticalAlign: "middle"
2986
+ };
2987
+ var headCell = {
2988
+ padding: "8px 12px",
2989
+ textAlign: "left",
2990
+ fontSize: 11,
2991
+ fontWeight: 700,
2992
+ textTransform: "uppercase",
2993
+ letterSpacing: 0.5,
2994
+ color: ui.subtle,
2995
+ borderBottom: `2px solid ${ui.border}`
2996
+ };
2997
+ var ThemeTable = ({ name, theme: theme2 }) => {
2998
+ const report = evaluateThemeContrast(theme2);
2999
+ const fails = report.failures.length;
3000
+ return /* @__PURE__ */ React3.createElement("section", {
3001
+ style: {
3002
+ marginBottom: 28,
3003
+ border: `1px solid ${ui.border}`,
3004
+ borderRadius: 8,
3005
+ overflow: "hidden",
3006
+ backgroundColor: ui.surface
3007
+ }
3008
+ }, /* @__PURE__ */ React3.createElement("header", {
3009
+ style: {
3010
+ display: "flex",
3011
+ alignItems: "center",
3012
+ justifyContent: "space-between",
3013
+ gap: 12,
3014
+ padding: "12px 16px",
3015
+ backgroundColor: ui.panel,
3016
+ borderBottom: `1px solid ${ui.border}`
3017
+ }
3018
+ }, /* @__PURE__ */ React3.createElement("h3", {
3019
+ style: { margin: 0, fontSize: 16, color: ui.text }
3020
+ }, name), /* @__PURE__ */ React3.createElement("span", {
3021
+ style: {
3022
+ fontSize: 12,
3023
+ fontWeight: 700,
3024
+ padding: "4px 10px",
3025
+ borderRadius: 999,
3026
+ backgroundColor: report.passesAA ? "#dafbe1" : "#ffebe9",
3027
+ color: report.passesAA ? "#0f7b3f" : "#cf222e"
3028
+ }
3029
+ }, report.passesAA ? "✓ Passes AA" : `${fails} AA failure${fails === 1 ? "" : "s"}`)), /* @__PURE__ */ React3.createElement("table", {
3030
+ style: { width: "100%", borderCollapse: "collapse" }
3031
+ }, /* @__PURE__ */ React3.createElement("thead", null, /* @__PURE__ */ React3.createElement("tr", null, /* @__PURE__ */ React3.createElement("th", {
3032
+ style: headCell
3033
+ }, "Sample"), /* @__PURE__ */ React3.createElement("th", {
3034
+ style: headCell
3035
+ }, "Pair"), /* @__PURE__ */ React3.createElement("th", {
3036
+ style: { ...headCell, textAlign: "right" }
3037
+ }, "Ratio"), /* @__PURE__ */ React3.createElement("th", {
3038
+ style: { ...headCell, textAlign: "right" }
3039
+ }, "Req. (AA)"), /* @__PURE__ */ React3.createElement("th", {
3040
+ style: { ...headCell, textAlign: "center" }
3041
+ }, "Use"), /* @__PURE__ */ React3.createElement("th", {
3042
+ style: { ...headCell, textAlign: "center" }
3043
+ }, "Grade"))), /* @__PURE__ */ React3.createElement("tbody", null, report.results.map((r, i) => /* @__PURE__ */ React3.createElement(Row, {
3044
+ key: i,
3045
+ r
3046
+ })))));
3047
+ };
3048
+ var ContrastReport = ({
3049
+ theme: theme2,
3050
+ themes,
3051
+ title = "WCAG Contrast Report"
3052
+ }) => {
3053
+ const list = themes ?? (theme2 ? [{ name: title, theme: theme2 }] : []);
3054
+ return /* @__PURE__ */ React3.createElement("div", {
3055
+ style: {
3056
+ fontFamily: ui.fontFamily,
3057
+ color: ui.text,
3058
+ backgroundColor: ui.panel,
3059
+ padding: 24,
3060
+ minHeight: "100vh"
3061
+ }
3062
+ }, /* @__PURE__ */ React3.createElement("header", {
3063
+ style: { marginBottom: 20 }
3064
+ }, /* @__PURE__ */ React3.createElement("h1", {
3065
+ style: { margin: "0 0 6px", fontSize: 22 }
3066
+ }, title), /* @__PURE__ */ React3.createElement("p", {
3067
+ style: { margin: 0, fontSize: 13, color: ui.subtle, maxWidth: 720 }
3068
+ }, "WCAG 2.x contrast ratios for each theme's declared color roles. Text pairs require AA", " ", WCAG_THRESHOLDS.text.AA, ":1 (AAA ", WCAG_THRESHOLDS.text.AAA, ":1); large text requires", " ", WCAG_THRESHOLDS.large.AA, ":1; borders and other non-text UI require", " ", WCAG_THRESHOLDS.ui.AA, ":1.")), list.map((t) => /* @__PURE__ */ React3.createElement(ThemeTable, {
3069
+ key: t.name,
3070
+ name: t.name,
3071
+ theme: t.theme
3072
+ })));
3073
+ };
2808
3074
 
2809
3075
  // src/index.ts
2810
3076
  var theme = terminalTheme;
@@ -2843,7 +3109,9 @@ export {
2843
3109
  scaleThemeFonts,
2844
3110
  responsive,
2845
3111
  resetFontScale,
3112
+ relativeLuminance,
2846
3113
  regalTheme,
3114
+ parseColor,
2847
3115
  overrideColors,
2848
3116
  neuralPulseTheme,
2849
3117
  mergeThemes,
@@ -2856,6 +3124,7 @@ export {
2856
3124
  iceTangerineTheme,
2857
3125
  iceTangerineDarkTheme,
2858
3126
  humanCentricTheme,
3127
+ gradeContrast,
2859
3128
  getZIndex,
2860
3129
  getSpace,
2861
3130
  getShadow,
@@ -2863,6 +3132,7 @@ export {
2863
3132
  getMode,
2864
3133
  getFontSize,
2865
3134
  getColor,
3135
+ evaluateThemeContrast,
2866
3136
  enterpriseTheme,
2867
3137
  defaultTerminalTheme,
2868
3138
  defaultMarkdownTheme,
@@ -2870,7 +3140,11 @@ export {
2870
3140
  src_default as default,
2871
3141
  decreaseFontScale,
2872
3142
  createStyle,
3143
+ contrastRatio,
2873
3144
  addMode,
3145
+ WCAG_THRESHOLDS,
2874
3146
  ThemeShowcase,
2875
- ThemeProvider
3147
+ ThemeProvider,
3148
+ ContrastReport,
3149
+ CONTRAST_PAIRS
2876
3150
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principal-ade/industry-theme",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "description": "Theme components and styles for industry-themed markdown",
6
6
  "main": "./dist/cjs/index.js",
@@ -41,10 +41,11 @@
41
41
  "devDependencies": {
42
42
  "@chromatic-com/storybook": "^4.1.3",
43
43
  "@eslint/js": "^9.32.0",
44
- "@storybook/addon-docs": "^10.1.2",
45
- "@storybook/addon-links": "^10.1.2",
46
- "@storybook/addon-onboarding": "^10.1.2",
47
- "@storybook/react-vite": "^10.1.2",
44
+ "@storybook/addon-a11y": "^10.4.2",
45
+ "@storybook/addon-docs": "^10.4.2",
46
+ "@storybook/addon-links": "^10.4.2",
47
+ "@storybook/addon-onboarding": "^10.4.2",
48
+ "@storybook/react-vite": "^10.4.2",
48
49
  "@types/bun": "latest",
49
50
  "@types/react": "^19.1.12",
50
51
  "@types/react-dom": "^19.0.0",
@@ -53,11 +54,11 @@
53
54
  "eslint-config-prettier": "^10.1.8",
54
55
  "eslint-import-resolver-typescript": "^4.4.4",
55
56
  "eslint-plugin-import": "^2.32.0",
56
- "eslint-plugin-storybook": "^10.1.2",
57
+ "eslint-plugin-storybook": "^10.4.2",
57
58
  "prettier": "^3.6.2",
58
59
  "react": "^19.2.4",
59
60
  "react-dom": "^19.2.4",
60
- "storybook": "^10.1.2",
61
+ "storybook": "^10.4.2",
61
62
  "typescript": "^5.0.4",
62
63
  "typescript-eslint": "^8.38.0",
63
64
  "vite": "^6.0.7"
@@ -0,0 +1,77 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ContrastReport } from './ContrastReport';
3
+ import {
4
+ terminalTheme,
5
+ regalTheme,
6
+ matrixTheme,
7
+ matrixMinimalTheme,
8
+ slateTheme,
9
+ slateNeonTheme,
10
+ slateGoldTheme,
11
+ iceTangerineTheme,
12
+ iceTangerineDarkTheme,
13
+ enterpriseTheme,
14
+ neuralPulseTheme,
15
+ humanCentricTheme,
16
+ landingPageTheme,
17
+ landingPageLightTheme,
18
+ } from './themes';
19
+
20
+ const allThemes = [
21
+ { name: 'Terminal', theme: terminalTheme },
22
+ { name: 'Regal', theme: regalTheme },
23
+ { name: 'Matrix', theme: matrixTheme },
24
+ { name: 'Matrix Minimal', theme: matrixMinimalTheme },
25
+ { name: 'Slate', theme: slateTheme },
26
+ { name: 'Slate Neon', theme: slateNeonTheme },
27
+ { name: 'Slate Gold', theme: slateGoldTheme },
28
+ { name: 'Ice Tangerine', theme: iceTangerineTheme },
29
+ { name: 'Ice Tangerine Dark', theme: iceTangerineDarkTheme },
30
+ { name: 'Enterprise', theme: enterpriseTheme },
31
+ { name: 'Neural Pulse', theme: neuralPulseTheme },
32
+ { name: 'Human-Centric', theme: humanCentricTheme },
33
+ { name: 'Landing Page (Dark)', theme: landingPageTheme },
34
+ { name: 'Landing Page (Light)', theme: landingPageLightTheme },
35
+ ];
36
+
37
+ const meta: Meta<typeof ContrastReport> = {
38
+ title: 'Themes/Contrast Report',
39
+ component: ContrastReport,
40
+ parameters: {
41
+ layout: 'fullscreen',
42
+ // This story renders raw color swatches by design; axe would double-report
43
+ // the same contrast findings the table already surfaces.
44
+ a11y: { disable: true },
45
+ },
46
+ };
47
+
48
+ export default meta;
49
+ type Story = StoryObj<typeof ContrastReport>;
50
+
51
+ /** WCAG audit across every theme in the library. */
52
+ export const AllThemes: Story = {
53
+ args: {
54
+ title: 'WCAG Contrast Report — All Themes',
55
+ themes: allThemes,
56
+ },
57
+ };
58
+
59
+ /** Tangerine family + Slate Neon side by side. */
60
+ export const TangerineAndSlateNeon: Story = {
61
+ args: {
62
+ title: 'Ice Tangerine, Ice Tangerine Dark & Slate Neon',
63
+ themes: [
64
+ { name: 'Ice Tangerine', theme: iceTangerineTheme },
65
+ { name: 'Ice Tangerine Dark', theme: iceTangerineDarkTheme },
66
+ { name: 'Slate Neon', theme: slateNeonTheme },
67
+ ],
68
+ },
69
+ };
70
+
71
+ export const Terminal: Story = {
72
+ args: { title: 'Terminal Theme', theme: terminalTheme },
73
+ };
74
+
75
+ export const LandingPageDark: Story = {
76
+ args: { title: 'Landing Page (Dark)', theme: landingPageTheme },
77
+ };