@work-rjkashyap/unified-ui 0.1.0
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/CHANGELOG.md +152 -0
- package/README.md +365 -0
- package/dist/chunk-2JFREULQ.cjs +29 -0
- package/dist/chunk-2RGLRX6K.cjs +279 -0
- package/dist/chunk-3HHJAYQI.cjs +469 -0
- package/dist/chunk-5S5DMH5G.cjs +5983 -0
- package/dist/chunk-BIW2RPDU.cjs +73 -0
- package/dist/chunk-CWETOWRM.mjs +456 -0
- package/dist/chunk-ECIGDEAH.cjs +140 -0
- package/dist/chunk-EO4WROWH.mjs +432 -0
- package/dist/chunk-EZ2L3XPS.mjs +83 -0
- package/dist/chunk-I74E52C5.mjs +271 -0
- package/dist/chunk-ITBG42M5.mjs +133 -0
- package/dist/chunk-IWJ5VMZ7.mjs +323 -0
- package/dist/chunk-KHON2AEM.cjs +342 -0
- package/dist/chunk-LSNKPQP7.cjs +58 -0
- package/dist/chunk-M2LNQWOB.mjs +63 -0
- package/dist/chunk-NMPHV6ZD.mjs +27 -0
- package/dist/chunk-QXR4VY7Q.cjs +268 -0
- package/dist/chunk-SSGN5QDC.mjs +248 -0
- package/dist/chunk-X2WCY4VB.mjs +5836 -0
- package/dist/chunk-XCKK6P46.cjs +91 -0
- package/dist/chunk-ZBN26FLO.mjs +46 -0
- package/dist/chunk-ZPIPKY2J.cjs +478 -0
- package/dist/components.cjs +477 -0
- package/dist/components.d.cts +3077 -0
- package/dist/components.d.ts +3077 -0
- package/dist/components.mjs +4 -0
- package/dist/index.cjs +1027 -0
- package/dist/index.d.cts +30 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.mjs +17 -0
- package/dist/motion-D9wQbcKL.d.cts +80 -0
- package/dist/motion-D9wQbcKL.d.ts +80 -0
- package/dist/motion.cjs +216 -0
- package/dist/motion.d.cts +104 -0
- package/dist/motion.d.ts +104 -0
- package/dist/motion.mjs +3 -0
- package/dist/primitives.cjs +57 -0
- package/dist/primitives.d.cts +390 -0
- package/dist/primitives.d.ts +390 -0
- package/dist/primitives.mjs +4 -0
- package/dist/theme.cjs +38 -0
- package/dist/theme.d.cts +117 -0
- package/dist/theme.d.ts +117 -0
- package/dist/theme.mjs +5 -0
- package/dist/tokens.cjs +137 -0
- package/dist/tokens.d.cts +30 -0
- package/dist/tokens.d.ts +30 -0
- package/dist/tokens.mjs +4 -0
- package/dist/typography-DlvVjEdE.d.cts +146 -0
- package/dist/typography-DlvVjEdE.d.ts +146 -0
- package/dist/utils.cjs +164 -0
- package/dist/utils.d.cts +521 -0
- package/dist/utils.d.ts +521 -0
- package/dist/utils.mjs +3 -0
- package/dist/z-index-B_nTQ3qo.d.cts +422 -0
- package/dist/z-index-B_nTQ3qo.d.ts +422 -0
- package/package.json +183 -0
- package/styles.css +500 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/utils/focus-ring.ts
|
|
4
|
+
var focusRingClasses = "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ds-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-ds-background";
|
|
5
|
+
var focusRingClassList = [
|
|
6
|
+
"focus-visible:outline-none",
|
|
7
|
+
"focus-visible:ring-2",
|
|
8
|
+
"focus-visible:ring-ds-focus-ring",
|
|
9
|
+
"focus-visible:ring-offset-2",
|
|
10
|
+
"focus-visible:ring-offset-ds-background"
|
|
11
|
+
];
|
|
12
|
+
var focusRingInsetClasses = "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ds-focus-ring";
|
|
13
|
+
var focusRingInsetClassList = [
|
|
14
|
+
"focus-visible:outline-none",
|
|
15
|
+
"focus-visible:ring-2",
|
|
16
|
+
"focus-visible:ring-ds-focus-ring"
|
|
17
|
+
];
|
|
18
|
+
var focusRingCompactClasses = "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ds-focus-ring focus-visible:ring-offset-1";
|
|
19
|
+
var focusRingCompactClassList = [
|
|
20
|
+
"focus-visible:outline-none",
|
|
21
|
+
"focus-visible:ring-2",
|
|
22
|
+
"focus-visible:ring-ds-focus-ring",
|
|
23
|
+
"focus-visible:ring-offset-1"
|
|
24
|
+
];
|
|
25
|
+
var focusRingVariantOverrides = {
|
|
26
|
+
/** Override ring color to danger red */
|
|
27
|
+
danger: "focus-visible:ring-ds-danger",
|
|
28
|
+
/** Override ring color to success green */
|
|
29
|
+
success: "focus-visible:ring-ds-success",
|
|
30
|
+
/** Override ring color to warning amber */
|
|
31
|
+
warning: "focus-visible:ring-ds-warning",
|
|
32
|
+
/** Override ring color to info blue */
|
|
33
|
+
info: "focus-visible:ring-ds-info",
|
|
34
|
+
/** Override ring color to current text color */
|
|
35
|
+
current: "focus-visible:ring-current"
|
|
36
|
+
};
|
|
37
|
+
var focusRingGroupTriggerClasses = "focus-visible:outline-none";
|
|
38
|
+
var focusRingGroupRingClasses = "group-focus-visible:ring-2 group-focus-visible:ring-ds-focus-ring group-focus-visible:ring-offset-2 group-focus-visible:ring-offset-ds-background";
|
|
39
|
+
var focusWithinRingClasses = "focus-within:outline-none focus-within:ring-2 focus-within:ring-ds-focus-ring focus-within:ring-offset-2 focus-within:ring-offset-ds-background";
|
|
40
|
+
var focusWithinRingClassList = [
|
|
41
|
+
"focus-within:outline-none",
|
|
42
|
+
"focus-within:ring-2",
|
|
43
|
+
"focus-within:ring-ds-focus-ring",
|
|
44
|
+
"focus-within:ring-offset-2",
|
|
45
|
+
"focus-within:ring-offset-ds-background"
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
exports.focusRingClassList = focusRingClassList;
|
|
49
|
+
exports.focusRingClasses = focusRingClasses;
|
|
50
|
+
exports.focusRingCompactClassList = focusRingCompactClassList;
|
|
51
|
+
exports.focusRingCompactClasses = focusRingCompactClasses;
|
|
52
|
+
exports.focusRingGroupRingClasses = focusRingGroupRingClasses;
|
|
53
|
+
exports.focusRingGroupTriggerClasses = focusRingGroupTriggerClasses;
|
|
54
|
+
exports.focusRingInsetClassList = focusRingInsetClassList;
|
|
55
|
+
exports.focusRingInsetClasses = focusRingInsetClasses;
|
|
56
|
+
exports.focusRingVariantOverrides = focusRingVariantOverrides;
|
|
57
|
+
exports.focusWithinRingClassList = focusWithinRingClassList;
|
|
58
|
+
exports.focusWithinRingClasses = focusWithinRingClasses;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
|
|
4
|
+
// src/utils/cn.ts
|
|
5
|
+
function cn(...inputs) {
|
|
6
|
+
return twMerge(clsx(inputs));
|
|
7
|
+
}
|
|
8
|
+
function mergeSlots(defaults, overrides) {
|
|
9
|
+
if (!overrides) {
|
|
10
|
+
return defaults;
|
|
11
|
+
}
|
|
12
|
+
const allKeys = /* @__PURE__ */ new Set([
|
|
13
|
+
...Object.keys(defaults),
|
|
14
|
+
...Object.keys(overrides)
|
|
15
|
+
]);
|
|
16
|
+
const result = {};
|
|
17
|
+
for (const key of allKeys) {
|
|
18
|
+
result[key] = cn(defaults[key] ?? "", overrides[key] ?? "");
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
function dsAttr(componentName) {
|
|
23
|
+
return {
|
|
24
|
+
"data-ds": "",
|
|
25
|
+
"data-ds-component": componentName
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function dsStateAttr(state, active) {
|
|
29
|
+
if (!active) return {};
|
|
30
|
+
return { [`data-ds-${state}`]: "" };
|
|
31
|
+
}
|
|
32
|
+
function dsVar(category, name, fallback) {
|
|
33
|
+
const varName = `--ds-${category}-${name}`;
|
|
34
|
+
return fallback ? `var(${varName}, ${fallback})` : `var(${varName})`;
|
|
35
|
+
}
|
|
36
|
+
function dsColorVar(name, alpha) {
|
|
37
|
+
const channels = `var(--ds-color-${name})`;
|
|
38
|
+
if (alpha !== void 0) {
|
|
39
|
+
return `rgb(${channels} / ${alpha})`;
|
|
40
|
+
}
|
|
41
|
+
return `rgb(${channels})`;
|
|
42
|
+
}
|
|
43
|
+
function setRef(ref, value) {
|
|
44
|
+
if (typeof ref === "function") {
|
|
45
|
+
ref(value);
|
|
46
|
+
} else if (ref !== null && ref !== void 0) {
|
|
47
|
+
ref.current = value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function composeRefs(...refs) {
|
|
51
|
+
return (node) => {
|
|
52
|
+
for (const ref of refs) {
|
|
53
|
+
setRef(ref, node);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function typedKeys(obj) {
|
|
58
|
+
return Object.keys(obj);
|
|
59
|
+
}
|
|
60
|
+
var noop = () => {
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export { cn, composeRefs, dsAttr, dsColorVar, dsStateAttr, dsVar, mergeSlots, noop, typedKeys };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/tokens/spacing.ts
|
|
2
|
+
var spacing = {
|
|
3
|
+
0: "0px",
|
|
4
|
+
px: "1px",
|
|
5
|
+
0.5: "2px",
|
|
6
|
+
1: "4px",
|
|
7
|
+
1.5: "6px",
|
|
8
|
+
2: "8px",
|
|
9
|
+
2.5: "10px",
|
|
10
|
+
3: "12px",
|
|
11
|
+
3.5: "14px",
|
|
12
|
+
4: "16px",
|
|
13
|
+
5: "20px",
|
|
14
|
+
6: "24px",
|
|
15
|
+
7: "28px",
|
|
16
|
+
8: "32px",
|
|
17
|
+
9: "36px",
|
|
18
|
+
10: "40px",
|
|
19
|
+
11: "44px",
|
|
20
|
+
12: "48px",
|
|
21
|
+
14: "56px",
|
|
22
|
+
16: "64px",
|
|
23
|
+
20: "80px",
|
|
24
|
+
24: "96px"
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { spacing };
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/utils/contrast.ts
|
|
4
|
+
var WCAG_AA_NORMAL = 4.5;
|
|
5
|
+
var WCAG_AA_LARGE = 3;
|
|
6
|
+
var WCAG_AAA_NORMAL = 7;
|
|
7
|
+
var WCAG_AAA_LARGE = 4.5;
|
|
8
|
+
var WCAG_NON_TEXT_AA = 3;
|
|
9
|
+
function linearize(channel) {
|
|
10
|
+
const normalized = channel / 255;
|
|
11
|
+
if (normalized <= 0.04045) {
|
|
12
|
+
return normalized / 12.92;
|
|
13
|
+
}
|
|
14
|
+
return ((normalized + 0.055) / 1.055) ** 2.4;
|
|
15
|
+
}
|
|
16
|
+
function relativeLuminance(color) {
|
|
17
|
+
const [r, g, b] = color;
|
|
18
|
+
return 0.2126 * linearize(r) + 0.7152 * linearize(g) + 0.0722 * linearize(b);
|
|
19
|
+
}
|
|
20
|
+
function contrastRatio(foreground, background) {
|
|
21
|
+
const l1 = relativeLuminance(foreground);
|
|
22
|
+
const l2 = relativeLuminance(background);
|
|
23
|
+
const lighter = Math.max(l1, l2);
|
|
24
|
+
const darker = Math.min(l1, l2);
|
|
25
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
26
|
+
}
|
|
27
|
+
function meetsAA(ratio, textSize = "normal") {
|
|
28
|
+
return ratio >= (textSize === "large" ? WCAG_AA_LARGE : WCAG_AA_NORMAL);
|
|
29
|
+
}
|
|
30
|
+
function meetsAAA(ratio, textSize = "normal") {
|
|
31
|
+
return ratio >= (textSize === "large" ? WCAG_AAA_LARGE : WCAG_AAA_NORMAL);
|
|
32
|
+
}
|
|
33
|
+
function meetsNonTextAA(ratio) {
|
|
34
|
+
return ratio >= WCAG_NON_TEXT_AA;
|
|
35
|
+
}
|
|
36
|
+
function parseRGBString(rgbString) {
|
|
37
|
+
const parts = rgbString.trim().split(/\s+/).map(Number);
|
|
38
|
+
if (parts.length !== 3 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Invalid RGB string: "${rgbString}". Expected 3 space-separated integers (0\u2013255).`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return parts;
|
|
44
|
+
}
|
|
45
|
+
function toRGBString(color) {
|
|
46
|
+
return `${color[0]} ${color[1]} ${color[2]}`;
|
|
47
|
+
}
|
|
48
|
+
function parseHex(hex) {
|
|
49
|
+
let cleaned = hex.replace(/^#/, "");
|
|
50
|
+
if (cleaned.length === 3) {
|
|
51
|
+
cleaned = cleaned[0] + cleaned[0] + cleaned[1] + cleaned[1] + cleaned[2] + cleaned[2];
|
|
52
|
+
}
|
|
53
|
+
if (cleaned.length !== 6 || !/^[\da-f]{6}$/i.test(cleaned)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Invalid hex color: "${hex}". Expected a 3- or 6-digit hex string.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return [
|
|
59
|
+
Number.parseInt(cleaned.slice(0, 2), 16),
|
|
60
|
+
Number.parseInt(cleaned.slice(2, 4), 16),
|
|
61
|
+
Number.parseInt(cleaned.slice(4, 6), 16)
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
function checkDSContrast(fgRGBString, bgRGBString) {
|
|
65
|
+
const fg = parseRGBString(fgRGBString);
|
|
66
|
+
const bg = parseRGBString(bgRGBString);
|
|
67
|
+
const ratio = contrastRatio(fg, bg);
|
|
68
|
+
return {
|
|
69
|
+
ratio: Math.round(ratio * 100) / 100,
|
|
70
|
+
aa: meetsAA(ratio, "normal"),
|
|
71
|
+
aaLarge: meetsAA(ratio, "large"),
|
|
72
|
+
aaa: meetsAAA(ratio, "normal"),
|
|
73
|
+
aaaLarge: meetsAAA(ratio, "large"),
|
|
74
|
+
nonTextAA: meetsNonTextAA(ratio)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function checkHexContrast(fgHex, bgHex) {
|
|
78
|
+
const fg = parseHex(fgHex);
|
|
79
|
+
const bg = parseHex(bgHex);
|
|
80
|
+
const ratio = contrastRatio(fg, bg);
|
|
81
|
+
return {
|
|
82
|
+
ratio: Math.round(ratio * 100) / 100,
|
|
83
|
+
aa: meetsAA(ratio, "normal"),
|
|
84
|
+
aaLarge: meetsAA(ratio, "large"),
|
|
85
|
+
aaa: meetsAAA(ratio, "normal"),
|
|
86
|
+
aaaLarge: meetsAAA(ratio, "large"),
|
|
87
|
+
nonTextAA: meetsNonTextAA(ratio)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function auditContrast(pairs) {
|
|
91
|
+
return pairs.map((pair) => {
|
|
92
|
+
const result = checkDSContrast(pair.fg, pair.bg);
|
|
93
|
+
return {
|
|
94
|
+
...result,
|
|
95
|
+
label: pair.label,
|
|
96
|
+
fg: pair.fg,
|
|
97
|
+
bg: pair.bg
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
var DS_LIGHT_CRITICAL_PAIRS = [
|
|
102
|
+
// Primary text on backgrounds
|
|
103
|
+
{ label: "foreground on background", fg: "9 9 11", bg: "255 255 255" },
|
|
104
|
+
{ label: "primary on background", fg: "79 70 229", bg: "255 255 255" },
|
|
105
|
+
{
|
|
106
|
+
label: "primary-foreground on primary",
|
|
107
|
+
fg: "255 255 255",
|
|
108
|
+
bg: "79 70 229"
|
|
109
|
+
},
|
|
110
|
+
// Secondary
|
|
111
|
+
{ label: "secondary-fg on secondary", fg: "24 24 27", bg: "244 244 245" },
|
|
112
|
+
// Muted (zinc.600 = 82 82 91 — matches CSS --ds-color-muted-foreground)
|
|
113
|
+
{ label: "muted-fg on background", fg: "82 82 91", bg: "255 255 255" },
|
|
114
|
+
{ label: "muted-fg on muted", fg: "82 82 91", bg: "244 244 245" },
|
|
115
|
+
// Semantic — foreground on solid bg (dark text on bright semantic colors)
|
|
116
|
+
{ label: "success-fg on success", fg: "9 9 11", bg: "22 163 74" },
|
|
117
|
+
{ label: "warning-fg on warning", fg: "9 9 11", bg: "245 158 11" },
|
|
118
|
+
{ label: "danger-fg on danger", fg: "255 255 255", bg: "220 38 38" },
|
|
119
|
+
{ label: "info-fg on info", fg: "255 255 255", bg: "37 99 235" },
|
|
120
|
+
// Semantic — muted text on muted bg
|
|
121
|
+
{
|
|
122
|
+
label: "success-muted-fg on success-muted",
|
|
123
|
+
fg: "21 128 61",
|
|
124
|
+
bg: "240 253 244"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
label: "warning-muted-fg on warning-muted",
|
|
128
|
+
fg: "180 83 9",
|
|
129
|
+
bg: "255 251 235"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
label: "danger-muted-fg on danger-muted",
|
|
133
|
+
fg: "185 28 28",
|
|
134
|
+
bg: "254 242 242"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
label: "info-muted-fg on info-muted",
|
|
138
|
+
fg: "29 78 216",
|
|
139
|
+
bg: "239 246 255"
|
|
140
|
+
},
|
|
141
|
+
// Input (placeholder = zinc.500 = 113 113 122)
|
|
142
|
+
{ label: "input-fg on background", fg: "24 24 27", bg: "255 255 255" },
|
|
143
|
+
{
|
|
144
|
+
label: "input-placeholder on background",
|
|
145
|
+
fg: "113 113 122",
|
|
146
|
+
bg: "255 255 255"
|
|
147
|
+
},
|
|
148
|
+
// Disabled (darkened to 120 120 129 for ≥ 3:1 usability target)
|
|
149
|
+
{ label: "disabled-fg on disabled", fg: "120 120 129", bg: "244 244 245" },
|
|
150
|
+
{
|
|
151
|
+
label: "disabled-fg on background",
|
|
152
|
+
fg: "120 120 129",
|
|
153
|
+
bg: "255 255 255"
|
|
154
|
+
},
|
|
155
|
+
// Input border (non-text, darkened to 148 148 157 for ≥ 3:1)
|
|
156
|
+
{
|
|
157
|
+
label: "input border on background (non-text)",
|
|
158
|
+
fg: "148 148 157",
|
|
159
|
+
bg: "255 255 255"
|
|
160
|
+
},
|
|
161
|
+
// Border-strong (non-text, darkened to 148 148 157 for ≥ 3:1)
|
|
162
|
+
{
|
|
163
|
+
label: "border-strong on background (non-text)",
|
|
164
|
+
fg: "148 148 157",
|
|
165
|
+
bg: "255 255 255"
|
|
166
|
+
},
|
|
167
|
+
// Focus ring (non-text contrast against background)
|
|
168
|
+
{
|
|
169
|
+
label: "focus-ring on background (non-text)",
|
|
170
|
+
fg: "99 102 241",
|
|
171
|
+
bg: "255 255 255"
|
|
172
|
+
}
|
|
173
|
+
];
|
|
174
|
+
var DS_DARK_CRITICAL_PAIRS = [
|
|
175
|
+
// Primary text on backgrounds
|
|
176
|
+
{ label: "foreground on background", fg: "250 250 250", bg: "9 9 11" },
|
|
177
|
+
// Primary shifted to brand.400 (129 140 248) for AA on dark bg (6.67:1)
|
|
178
|
+
{ label: "primary on background", fg: "129 140 248", bg: "9 9 11" },
|
|
179
|
+
{
|
|
180
|
+
// Primary-fg shifted to zinc.900 (24 24 27) for AA on brand.400 (5.94:1)
|
|
181
|
+
label: "primary-foreground on primary",
|
|
182
|
+
fg: "24 24 27",
|
|
183
|
+
bg: "129 140 248"
|
|
184
|
+
},
|
|
185
|
+
// Secondary
|
|
186
|
+
{ label: "secondary-fg on secondary", fg: "244 244 245", bg: "39 39 42" },
|
|
187
|
+
// Muted
|
|
188
|
+
{ label: "muted-fg on background", fg: "161 161 170", bg: "9 9 11" },
|
|
189
|
+
{ label: "muted-fg on muted", fg: "161 161 170", bg: "39 39 42" },
|
|
190
|
+
// Semantic — foreground on solid bg (dark text on bright semantic colors)
|
|
191
|
+
{ label: "success-fg on success", fg: "9 9 11", bg: "34 197 94" },
|
|
192
|
+
{ label: "warning-fg on warning", fg: "9 9 11", bg: "251 191 36" },
|
|
193
|
+
{ label: "danger-fg on danger", fg: "9 9 11", bg: "239 68 68" },
|
|
194
|
+
{ label: "info-fg on info", fg: "9 9 11", bg: "96 165 250" },
|
|
195
|
+
// Semantic — muted text on muted bg
|
|
196
|
+
{
|
|
197
|
+
label: "success-muted-fg on success-muted",
|
|
198
|
+
fg: "134 239 172",
|
|
199
|
+
bg: "5 46 22"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
label: "warning-muted-fg on warning-muted",
|
|
203
|
+
fg: "252 211 77",
|
|
204
|
+
bg: "69 26 3"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
label: "danger-muted-fg on danger-muted",
|
|
208
|
+
fg: "252 165 165",
|
|
209
|
+
bg: "69 10 10"
|
|
210
|
+
},
|
|
211
|
+
{ label: "info-muted-fg on info-muted", fg: "147 197 253", bg: "23 37 84" },
|
|
212
|
+
// Input (placeholder = 137 137 145 for dark theme)
|
|
213
|
+
{ label: "input-fg on background", fg: "250 250 250", bg: "9 9 11" },
|
|
214
|
+
{
|
|
215
|
+
label: "input-placeholder on background",
|
|
216
|
+
fg: "137 137 145",
|
|
217
|
+
bg: "9 9 11"
|
|
218
|
+
},
|
|
219
|
+
// Disabled (lightened to zinc.500 = 113 113 122 for ≥ 3:1 usability target)
|
|
220
|
+
{ label: "disabled-fg on disabled", fg: "113 113 122", bg: "39 39 42" },
|
|
221
|
+
{ label: "disabled-fg on background", fg: "113 113 122", bg: "9 9 11" },
|
|
222
|
+
// Input border (non-text, lightened to 96 96 105 for ≥ 3:1)
|
|
223
|
+
{
|
|
224
|
+
label: "input border on background (non-text)",
|
|
225
|
+
fg: "96 96 105",
|
|
226
|
+
bg: "9 9 11"
|
|
227
|
+
},
|
|
228
|
+
// Border-strong (non-text, lightened to zinc.500 = 113 113 122 for ≥ 3:1)
|
|
229
|
+
{
|
|
230
|
+
label: "border-strong on background (non-text)",
|
|
231
|
+
fg: "113 113 122",
|
|
232
|
+
bg: "9 9 11"
|
|
233
|
+
},
|
|
234
|
+
// Focus ring (non-text contrast against background)
|
|
235
|
+
{
|
|
236
|
+
label: "focus-ring on background (non-text)",
|
|
237
|
+
fg: "129 140 248",
|
|
238
|
+
bg: "9 9 11"
|
|
239
|
+
}
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
// src/utils/types.ts
|
|
243
|
+
function dsDataAttrs(name) {
|
|
244
|
+
return {
|
|
245
|
+
"data-ds": true,
|
|
246
|
+
"data-ds-component": name
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
exports.DS_DARK_CRITICAL_PAIRS = DS_DARK_CRITICAL_PAIRS;
|
|
251
|
+
exports.DS_LIGHT_CRITICAL_PAIRS = DS_LIGHT_CRITICAL_PAIRS;
|
|
252
|
+
exports.WCAG_AAA_LARGE = WCAG_AAA_LARGE;
|
|
253
|
+
exports.WCAG_AAA_NORMAL = WCAG_AAA_NORMAL;
|
|
254
|
+
exports.WCAG_AA_LARGE = WCAG_AA_LARGE;
|
|
255
|
+
exports.WCAG_AA_NORMAL = WCAG_AA_NORMAL;
|
|
256
|
+
exports.WCAG_NON_TEXT_AA = WCAG_NON_TEXT_AA;
|
|
257
|
+
exports.auditContrast = auditContrast;
|
|
258
|
+
exports.checkDSContrast = checkDSContrast;
|
|
259
|
+
exports.checkHexContrast = checkHexContrast;
|
|
260
|
+
exports.contrastRatio = contrastRatio;
|
|
261
|
+
exports.dsDataAttrs = dsDataAttrs;
|
|
262
|
+
exports.meetsAA = meetsAA;
|
|
263
|
+
exports.meetsAAA = meetsAAA;
|
|
264
|
+
exports.meetsNonTextAA = meetsNonTextAA;
|
|
265
|
+
exports.parseHex = parseHex;
|
|
266
|
+
exports.parseRGBString = parseRGBString;
|
|
267
|
+
exports.relativeLuminance = relativeLuminance;
|
|
268
|
+
exports.toRGBString = toRGBString;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// src/utils/contrast.ts
|
|
2
|
+
var WCAG_AA_NORMAL = 4.5;
|
|
3
|
+
var WCAG_AA_LARGE = 3;
|
|
4
|
+
var WCAG_AAA_NORMAL = 7;
|
|
5
|
+
var WCAG_AAA_LARGE = 4.5;
|
|
6
|
+
var WCAG_NON_TEXT_AA = 3;
|
|
7
|
+
function linearize(channel) {
|
|
8
|
+
const normalized = channel / 255;
|
|
9
|
+
if (normalized <= 0.04045) {
|
|
10
|
+
return normalized / 12.92;
|
|
11
|
+
}
|
|
12
|
+
return ((normalized + 0.055) / 1.055) ** 2.4;
|
|
13
|
+
}
|
|
14
|
+
function relativeLuminance(color) {
|
|
15
|
+
const [r, g, b] = color;
|
|
16
|
+
return 0.2126 * linearize(r) + 0.7152 * linearize(g) + 0.0722 * linearize(b);
|
|
17
|
+
}
|
|
18
|
+
function contrastRatio(foreground, background) {
|
|
19
|
+
const l1 = relativeLuminance(foreground);
|
|
20
|
+
const l2 = relativeLuminance(background);
|
|
21
|
+
const lighter = Math.max(l1, l2);
|
|
22
|
+
const darker = Math.min(l1, l2);
|
|
23
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
24
|
+
}
|
|
25
|
+
function meetsAA(ratio, textSize = "normal") {
|
|
26
|
+
return ratio >= (textSize === "large" ? WCAG_AA_LARGE : WCAG_AA_NORMAL);
|
|
27
|
+
}
|
|
28
|
+
function meetsAAA(ratio, textSize = "normal") {
|
|
29
|
+
return ratio >= (textSize === "large" ? WCAG_AAA_LARGE : WCAG_AAA_NORMAL);
|
|
30
|
+
}
|
|
31
|
+
function meetsNonTextAA(ratio) {
|
|
32
|
+
return ratio >= WCAG_NON_TEXT_AA;
|
|
33
|
+
}
|
|
34
|
+
function parseRGBString(rgbString) {
|
|
35
|
+
const parts = rgbString.trim().split(/\s+/).map(Number);
|
|
36
|
+
if (parts.length !== 3 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Invalid RGB string: "${rgbString}". Expected 3 space-separated integers (0\u2013255).`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return parts;
|
|
42
|
+
}
|
|
43
|
+
function toRGBString(color) {
|
|
44
|
+
return `${color[0]} ${color[1]} ${color[2]}`;
|
|
45
|
+
}
|
|
46
|
+
function parseHex(hex) {
|
|
47
|
+
let cleaned = hex.replace(/^#/, "");
|
|
48
|
+
if (cleaned.length === 3) {
|
|
49
|
+
cleaned = cleaned[0] + cleaned[0] + cleaned[1] + cleaned[1] + cleaned[2] + cleaned[2];
|
|
50
|
+
}
|
|
51
|
+
if (cleaned.length !== 6 || !/^[\da-f]{6}$/i.test(cleaned)) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Invalid hex color: "${hex}". Expected a 3- or 6-digit hex string.`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return [
|
|
57
|
+
Number.parseInt(cleaned.slice(0, 2), 16),
|
|
58
|
+
Number.parseInt(cleaned.slice(2, 4), 16),
|
|
59
|
+
Number.parseInt(cleaned.slice(4, 6), 16)
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
function checkDSContrast(fgRGBString, bgRGBString) {
|
|
63
|
+
const fg = parseRGBString(fgRGBString);
|
|
64
|
+
const bg = parseRGBString(bgRGBString);
|
|
65
|
+
const ratio = contrastRatio(fg, bg);
|
|
66
|
+
return {
|
|
67
|
+
ratio: Math.round(ratio * 100) / 100,
|
|
68
|
+
aa: meetsAA(ratio, "normal"),
|
|
69
|
+
aaLarge: meetsAA(ratio, "large"),
|
|
70
|
+
aaa: meetsAAA(ratio, "normal"),
|
|
71
|
+
aaaLarge: meetsAAA(ratio, "large"),
|
|
72
|
+
nonTextAA: meetsNonTextAA(ratio)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function checkHexContrast(fgHex, bgHex) {
|
|
76
|
+
const fg = parseHex(fgHex);
|
|
77
|
+
const bg = parseHex(bgHex);
|
|
78
|
+
const ratio = contrastRatio(fg, bg);
|
|
79
|
+
return {
|
|
80
|
+
ratio: Math.round(ratio * 100) / 100,
|
|
81
|
+
aa: meetsAA(ratio, "normal"),
|
|
82
|
+
aaLarge: meetsAA(ratio, "large"),
|
|
83
|
+
aaa: meetsAAA(ratio, "normal"),
|
|
84
|
+
aaaLarge: meetsAAA(ratio, "large"),
|
|
85
|
+
nonTextAA: meetsNonTextAA(ratio)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function auditContrast(pairs) {
|
|
89
|
+
return pairs.map((pair) => {
|
|
90
|
+
const result = checkDSContrast(pair.fg, pair.bg);
|
|
91
|
+
return {
|
|
92
|
+
...result,
|
|
93
|
+
label: pair.label,
|
|
94
|
+
fg: pair.fg,
|
|
95
|
+
bg: pair.bg
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
var DS_LIGHT_CRITICAL_PAIRS = [
|
|
100
|
+
// Primary text on backgrounds
|
|
101
|
+
{ label: "foreground on background", fg: "9 9 11", bg: "255 255 255" },
|
|
102
|
+
{ label: "primary on background", fg: "79 70 229", bg: "255 255 255" },
|
|
103
|
+
{
|
|
104
|
+
label: "primary-foreground on primary",
|
|
105
|
+
fg: "255 255 255",
|
|
106
|
+
bg: "79 70 229"
|
|
107
|
+
},
|
|
108
|
+
// Secondary
|
|
109
|
+
{ label: "secondary-fg on secondary", fg: "24 24 27", bg: "244 244 245" },
|
|
110
|
+
// Muted (zinc.600 = 82 82 91 — matches CSS --ds-color-muted-foreground)
|
|
111
|
+
{ label: "muted-fg on background", fg: "82 82 91", bg: "255 255 255" },
|
|
112
|
+
{ label: "muted-fg on muted", fg: "82 82 91", bg: "244 244 245" },
|
|
113
|
+
// Semantic — foreground on solid bg (dark text on bright semantic colors)
|
|
114
|
+
{ label: "success-fg on success", fg: "9 9 11", bg: "22 163 74" },
|
|
115
|
+
{ label: "warning-fg on warning", fg: "9 9 11", bg: "245 158 11" },
|
|
116
|
+
{ label: "danger-fg on danger", fg: "255 255 255", bg: "220 38 38" },
|
|
117
|
+
{ label: "info-fg on info", fg: "255 255 255", bg: "37 99 235" },
|
|
118
|
+
// Semantic — muted text on muted bg
|
|
119
|
+
{
|
|
120
|
+
label: "success-muted-fg on success-muted",
|
|
121
|
+
fg: "21 128 61",
|
|
122
|
+
bg: "240 253 244"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
label: "warning-muted-fg on warning-muted",
|
|
126
|
+
fg: "180 83 9",
|
|
127
|
+
bg: "255 251 235"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
label: "danger-muted-fg on danger-muted",
|
|
131
|
+
fg: "185 28 28",
|
|
132
|
+
bg: "254 242 242"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
label: "info-muted-fg on info-muted",
|
|
136
|
+
fg: "29 78 216",
|
|
137
|
+
bg: "239 246 255"
|
|
138
|
+
},
|
|
139
|
+
// Input (placeholder = zinc.500 = 113 113 122)
|
|
140
|
+
{ label: "input-fg on background", fg: "24 24 27", bg: "255 255 255" },
|
|
141
|
+
{
|
|
142
|
+
label: "input-placeholder on background",
|
|
143
|
+
fg: "113 113 122",
|
|
144
|
+
bg: "255 255 255"
|
|
145
|
+
},
|
|
146
|
+
// Disabled (darkened to 120 120 129 for ≥ 3:1 usability target)
|
|
147
|
+
{ label: "disabled-fg on disabled", fg: "120 120 129", bg: "244 244 245" },
|
|
148
|
+
{
|
|
149
|
+
label: "disabled-fg on background",
|
|
150
|
+
fg: "120 120 129",
|
|
151
|
+
bg: "255 255 255"
|
|
152
|
+
},
|
|
153
|
+
// Input border (non-text, darkened to 148 148 157 for ≥ 3:1)
|
|
154
|
+
{
|
|
155
|
+
label: "input border on background (non-text)",
|
|
156
|
+
fg: "148 148 157",
|
|
157
|
+
bg: "255 255 255"
|
|
158
|
+
},
|
|
159
|
+
// Border-strong (non-text, darkened to 148 148 157 for ≥ 3:1)
|
|
160
|
+
{
|
|
161
|
+
label: "border-strong on background (non-text)",
|
|
162
|
+
fg: "148 148 157",
|
|
163
|
+
bg: "255 255 255"
|
|
164
|
+
},
|
|
165
|
+
// Focus ring (non-text contrast against background)
|
|
166
|
+
{
|
|
167
|
+
label: "focus-ring on background (non-text)",
|
|
168
|
+
fg: "99 102 241",
|
|
169
|
+
bg: "255 255 255"
|
|
170
|
+
}
|
|
171
|
+
];
|
|
172
|
+
var DS_DARK_CRITICAL_PAIRS = [
|
|
173
|
+
// Primary text on backgrounds
|
|
174
|
+
{ label: "foreground on background", fg: "250 250 250", bg: "9 9 11" },
|
|
175
|
+
// Primary shifted to brand.400 (129 140 248) for AA on dark bg (6.67:1)
|
|
176
|
+
{ label: "primary on background", fg: "129 140 248", bg: "9 9 11" },
|
|
177
|
+
{
|
|
178
|
+
// Primary-fg shifted to zinc.900 (24 24 27) for AA on brand.400 (5.94:1)
|
|
179
|
+
label: "primary-foreground on primary",
|
|
180
|
+
fg: "24 24 27",
|
|
181
|
+
bg: "129 140 248"
|
|
182
|
+
},
|
|
183
|
+
// Secondary
|
|
184
|
+
{ label: "secondary-fg on secondary", fg: "244 244 245", bg: "39 39 42" },
|
|
185
|
+
// Muted
|
|
186
|
+
{ label: "muted-fg on background", fg: "161 161 170", bg: "9 9 11" },
|
|
187
|
+
{ label: "muted-fg on muted", fg: "161 161 170", bg: "39 39 42" },
|
|
188
|
+
// Semantic — foreground on solid bg (dark text on bright semantic colors)
|
|
189
|
+
{ label: "success-fg on success", fg: "9 9 11", bg: "34 197 94" },
|
|
190
|
+
{ label: "warning-fg on warning", fg: "9 9 11", bg: "251 191 36" },
|
|
191
|
+
{ label: "danger-fg on danger", fg: "9 9 11", bg: "239 68 68" },
|
|
192
|
+
{ label: "info-fg on info", fg: "9 9 11", bg: "96 165 250" },
|
|
193
|
+
// Semantic — muted text on muted bg
|
|
194
|
+
{
|
|
195
|
+
label: "success-muted-fg on success-muted",
|
|
196
|
+
fg: "134 239 172",
|
|
197
|
+
bg: "5 46 22"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
label: "warning-muted-fg on warning-muted",
|
|
201
|
+
fg: "252 211 77",
|
|
202
|
+
bg: "69 26 3"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
label: "danger-muted-fg on danger-muted",
|
|
206
|
+
fg: "252 165 165",
|
|
207
|
+
bg: "69 10 10"
|
|
208
|
+
},
|
|
209
|
+
{ label: "info-muted-fg on info-muted", fg: "147 197 253", bg: "23 37 84" },
|
|
210
|
+
// Input (placeholder = 137 137 145 for dark theme)
|
|
211
|
+
{ label: "input-fg on background", fg: "250 250 250", bg: "9 9 11" },
|
|
212
|
+
{
|
|
213
|
+
label: "input-placeholder on background",
|
|
214
|
+
fg: "137 137 145",
|
|
215
|
+
bg: "9 9 11"
|
|
216
|
+
},
|
|
217
|
+
// Disabled (lightened to zinc.500 = 113 113 122 for ≥ 3:1 usability target)
|
|
218
|
+
{ label: "disabled-fg on disabled", fg: "113 113 122", bg: "39 39 42" },
|
|
219
|
+
{ label: "disabled-fg on background", fg: "113 113 122", bg: "9 9 11" },
|
|
220
|
+
// Input border (non-text, lightened to 96 96 105 for ≥ 3:1)
|
|
221
|
+
{
|
|
222
|
+
label: "input border on background (non-text)",
|
|
223
|
+
fg: "96 96 105",
|
|
224
|
+
bg: "9 9 11"
|
|
225
|
+
},
|
|
226
|
+
// Border-strong (non-text, lightened to zinc.500 = 113 113 122 for ≥ 3:1)
|
|
227
|
+
{
|
|
228
|
+
label: "border-strong on background (non-text)",
|
|
229
|
+
fg: "113 113 122",
|
|
230
|
+
bg: "9 9 11"
|
|
231
|
+
},
|
|
232
|
+
// Focus ring (non-text contrast against background)
|
|
233
|
+
{
|
|
234
|
+
label: "focus-ring on background (non-text)",
|
|
235
|
+
fg: "129 140 248",
|
|
236
|
+
bg: "9 9 11"
|
|
237
|
+
}
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
// src/utils/types.ts
|
|
241
|
+
function dsDataAttrs(name) {
|
|
242
|
+
return {
|
|
243
|
+
"data-ds": true,
|
|
244
|
+
"data-ds-component": name
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export { DS_DARK_CRITICAL_PAIRS, DS_LIGHT_CRITICAL_PAIRS, WCAG_AAA_LARGE, WCAG_AAA_NORMAL, WCAG_AA_LARGE, WCAG_AA_NORMAL, WCAG_NON_TEXT_AA, auditContrast, checkDSContrast, checkHexContrast, contrastRatio, dsDataAttrs, meetsAA, meetsAAA, meetsNonTextAA, parseHex, parseRGBString, relativeLuminance, toRGBString };
|