@judarn/ui 0.0.0-stage → 0.7.1
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/LICENSE +21 -0
- package/README.md +47 -2
- package/dist/Badge/Badge.d.ts +26 -0
- package/dist/Button/Button.d.ts +19 -0
- package/dist/Card/Card.d.ts +22 -0
- package/dist/DataMatrix/DataMatrix.d.ts +26 -0
- package/dist/DataTable/DataTable.d.ts +21 -0
- package/dist/Footer/Footer.d.ts +21 -0
- package/dist/LogoLockup/LogoLockup.d.ts +20 -0
- package/dist/Navbar/Navbar.d.ts +21 -0
- package/dist/TextField/TextField.d.ts +31 -0
- package/dist/index.cjs +676 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.mjs +650 -0
- package/dist/sdui/index.cjs +572 -0
- package/dist/sdui/index.d.ts +1 -0
- package/dist/sdui/index.mjs +540 -0
- package/dist/sdui/schema.json +729 -0
- package/dist/sdui/serializer.d.ts +54 -0
- package/package.json +82 -4
- package/src/tailwind.d.ts +11 -0
- package/src/tailwind.js +61 -0
- package/styles/colors_and_type.css +543 -0
- package/styles/components.css +243 -0
- package/styles/styles.css +52 -0
- package/styles/tailwind-theme.css +59 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// src/sdui/index.js
|
|
30
|
+
var sdui_exports = {};
|
|
31
|
+
__export(sdui_exports, {
|
|
32
|
+
SDUI_PROP_ALLOWLIST: () => SDUI_PROP_ALLOWLIST,
|
|
33
|
+
VALID_NODE_TYPES: () => VALID_NODE_TYPES,
|
|
34
|
+
VALID_TOKEN_NAMES: () => VALID_TOKEN_NAMES,
|
|
35
|
+
deserializeComponent: () => deserializeComponent,
|
|
36
|
+
serializeComponent: () => serializeComponent,
|
|
37
|
+
serializeTokens: () => serializeTokens,
|
|
38
|
+
validateSDUINode: () => validateSDUINode
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(sdui_exports);
|
|
41
|
+
|
|
42
|
+
// src/sdui/serializer.js
|
|
43
|
+
var React2 = __toESM(require("react"));
|
|
44
|
+
|
|
45
|
+
// src/internal/cx.js
|
|
46
|
+
function cx(...parts) {
|
|
47
|
+
return parts.filter(Boolean).join(" ");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/Button/Button.jsx
|
|
51
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
52
|
+
function Button({
|
|
53
|
+
children,
|
|
54
|
+
variant = "primary",
|
|
55
|
+
size = "md",
|
|
56
|
+
block = false,
|
|
57
|
+
disabled = false,
|
|
58
|
+
type = "button",
|
|
59
|
+
onClick,
|
|
60
|
+
className,
|
|
61
|
+
...rest
|
|
62
|
+
}) {
|
|
63
|
+
const cls = cx(
|
|
64
|
+
"btn",
|
|
65
|
+
variant === "outline" && "btn--outline",
|
|
66
|
+
variant === "secondary" && "btn--secondary",
|
|
67
|
+
variant === "ghost" && "btn--ghost",
|
|
68
|
+
variant === "accent" && "btn--accent",
|
|
69
|
+
size === "sm" && "btn--sm",
|
|
70
|
+
size === "lg" && "btn--lg",
|
|
71
|
+
block && "btn--block",
|
|
72
|
+
className
|
|
73
|
+
);
|
|
74
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { ...rest, className: cls, type, disabled, onClick, children });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/Badge/Badge.jsx
|
|
78
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
79
|
+
var linearize = (c) => c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
80
|
+
function oklchToLinearSrgb(l, c, h, clip) {
|
|
81
|
+
const hr = h * Math.PI / 180;
|
|
82
|
+
const a = c * Math.cos(hr);
|
|
83
|
+
const b = c * Math.sin(hr);
|
|
84
|
+
const l_ = Math.pow(l + 0.3963377774 * a + 0.2158037573 * b, 3);
|
|
85
|
+
const m_ = Math.pow(l - 0.1055613458 * a - 0.0638541728 * b, 3);
|
|
86
|
+
const s_ = Math.pow(l - 0.0894841775 * a - 1.291485548 * b, 3);
|
|
87
|
+
const rgb = [
|
|
88
|
+
4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_,
|
|
89
|
+
-1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_,
|
|
90
|
+
-0.0041960863 * l_ - 0.7034186147 * m_ + 1.707614701 * s_
|
|
91
|
+
];
|
|
92
|
+
return clip ? rgb.map((v) => Math.min(1, Math.max(0, v))) : rgb;
|
|
93
|
+
}
|
|
94
|
+
function oklchToLuminance(l, c, h) {
|
|
95
|
+
if (l <= 0) return 0;
|
|
96
|
+
if (l >= 1) return 1;
|
|
97
|
+
const inGamut = (rgb2) => rgb2.every((v) => v >= -1e-6 && v <= 1 + 1e-6);
|
|
98
|
+
let rgb = oklchToLinearSrgb(l, c, h, false);
|
|
99
|
+
if (!inGamut(rgb)) {
|
|
100
|
+
let low = 0;
|
|
101
|
+
let high = c;
|
|
102
|
+
for (let i = 0; i < 24; i++) {
|
|
103
|
+
const mid = 0.5 * (low + high);
|
|
104
|
+
if (inGamut(oklchToLinearSrgb(l, mid, h, false))) low = mid;
|
|
105
|
+
else high = mid;
|
|
106
|
+
}
|
|
107
|
+
rgb = oklchToLinearSrgb(l, low, h, false);
|
|
108
|
+
}
|
|
109
|
+
const [r, g, b] = rgb.map((v) => Math.min(1, Math.max(0, v)));
|
|
110
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
111
|
+
}
|
|
112
|
+
function parseLuminance(input) {
|
|
113
|
+
const value = input.trim().toLowerCase();
|
|
114
|
+
if (value.startsWith("#")) {
|
|
115
|
+
const hex = value.slice(1);
|
|
116
|
+
if (!/^([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(hex)) return null;
|
|
117
|
+
const full = hex.length <= 4 ? [...hex].map((ch) => ch + ch).join("") : hex;
|
|
118
|
+
const [r, g, b] = [0, 2, 4].map((i) => linearize(parseInt(full.slice(i, i + 2), 16) / 255));
|
|
119
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
120
|
+
}
|
|
121
|
+
if (value.startsWith("rgb")) {
|
|
122
|
+
const nums = value.replace(/\/.*$/, "").match(/\d+(\.\d+)?/g);
|
|
123
|
+
if (!nums || nums.length < 3) return null;
|
|
124
|
+
const [r, g, b] = nums.slice(0, 3).map((n) => linearize(parseFloat(n) / 255));
|
|
125
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
126
|
+
}
|
|
127
|
+
if (value.startsWith("oklch(")) {
|
|
128
|
+
const m = value.match(/^oklch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(?:deg)?\s*(?:\/[^)]*)?\)$/);
|
|
129
|
+
if (!m) return null;
|
|
130
|
+
const l = parseFloat(m[1]) / (m[2] ? 100 : 1);
|
|
131
|
+
return oklchToLuminance(l, parseFloat(m[3]), parseFloat(m[4]));
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
function getContrastingInk(color) {
|
|
136
|
+
const lum = typeof color === "string" ? parseLuminance(color) : null;
|
|
137
|
+
if (lum === null) {
|
|
138
|
+
throw new TypeError(`getContrastingInk: unsupported colour "${String(color)}". Use #hex, rgb() or oklch().`);
|
|
139
|
+
}
|
|
140
|
+
const perceptualCrossover = 0.5;
|
|
141
|
+
return lum > perceptualCrossover ? "#000000" : "#ffffff";
|
|
142
|
+
}
|
|
143
|
+
function Badge({ children, variant = "outline", accentColor, style, className, ...rest }) {
|
|
144
|
+
const cls = cx(
|
|
145
|
+
"badge",
|
|
146
|
+
variant === "solid" && "badge--solid",
|
|
147
|
+
variant === "accent" && "badge--accent",
|
|
148
|
+
variant === "accent-outline" && "badge--accent-outline",
|
|
149
|
+
className
|
|
150
|
+
);
|
|
151
|
+
const inlineStyle = { ...style };
|
|
152
|
+
if (accentColor) {
|
|
153
|
+
if (variant === "accent") {
|
|
154
|
+
inlineStyle.backgroundColor = accentColor;
|
|
155
|
+
inlineStyle.borderColor = accentColor;
|
|
156
|
+
inlineStyle.color = getContrastingInk(accentColor);
|
|
157
|
+
} else if (variant === "accent-outline") {
|
|
158
|
+
inlineStyle.borderColor = accentColor;
|
|
159
|
+
inlineStyle.color = accentColor;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { ...rest, className: cls, style: Object.keys(inlineStyle).length > 0 ? inlineStyle : void 0, children });
|
|
163
|
+
}
|
|
164
|
+
function Tag({ children, className, ...rest }) {
|
|
165
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { ...rest, className: cx("tag", className), children });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/Card/Card.jsx
|
|
169
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
170
|
+
function Card({
|
|
171
|
+
eyebrow,
|
|
172
|
+
eyebrowCasing = "uppercase",
|
|
173
|
+
title,
|
|
174
|
+
children,
|
|
175
|
+
media,
|
|
176
|
+
actions,
|
|
177
|
+
flat = false,
|
|
178
|
+
compact = false,
|
|
179
|
+
className,
|
|
180
|
+
...rest
|
|
181
|
+
}) {
|
|
182
|
+
const cls = cx("card", flat && "card--flat", compact && "card--compact", className);
|
|
183
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { ...rest, className: cls, children: [
|
|
184
|
+
media ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "card__media", children: media }) : null,
|
|
185
|
+
eyebrow ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: cx("card__eyebrow", "t-label", eyebrowCasing === "asWritten" && "t-as-written"), children: eyebrow }) : null,
|
|
186
|
+
title ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "card__title", children: title }) : null,
|
|
187
|
+
children ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "card__body", children }) : null,
|
|
188
|
+
actions ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "flex", gap: "var(--gap-tight)" }, children: actions }) : null
|
|
189
|
+
] });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/DataMatrix/DataMatrix.jsx
|
|
193
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
194
|
+
function DataMatrix({ columns: columns2 = [], rows: rows2 = [], label = "Data Matrix", className, ...rest }) {
|
|
195
|
+
const cols = columns2.map((c) => typeof c === "string" ? { key: c, label: c } : c);
|
|
196
|
+
const totalColumns = cols.length;
|
|
197
|
+
const totalRows = rows2.length;
|
|
198
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
199
|
+
"table",
|
|
200
|
+
{
|
|
201
|
+
...rest,
|
|
202
|
+
className: cx("matrix", className),
|
|
203
|
+
role: "table",
|
|
204
|
+
"aria-label": label,
|
|
205
|
+
"aria-colcount": totalColumns,
|
|
206
|
+
"aria-rowcount": totalRows + 1,
|
|
207
|
+
children: [
|
|
208
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("thead", { role: "rowgroup", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tr", { role: "row", "aria-rowindex": 1, children: cols.map((c, colIndex) => {
|
|
209
|
+
const widthStyle = c.width != null ? {
|
|
210
|
+
width: typeof c.width === "number" ? `${c.width}px` : c.width,
|
|
211
|
+
minWidth: typeof c.width === "number" ? `${c.width}px` : c.width,
|
|
212
|
+
maxWidth: typeof c.width === "number" ? `${c.width}px` : c.width
|
|
213
|
+
} : void 0;
|
|
214
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
215
|
+
"th",
|
|
216
|
+
{
|
|
217
|
+
scope: "col",
|
|
218
|
+
role: "columnheader",
|
|
219
|
+
"aria-colindex": colIndex + 1,
|
|
220
|
+
"aria-label": c.label,
|
|
221
|
+
className: [c.numeric && "num", c.casing === "asWritten" && "t-as-written"].filter(Boolean).join(" ") || void 0,
|
|
222
|
+
style: widthStyle,
|
|
223
|
+
children: c.label
|
|
224
|
+
},
|
|
225
|
+
c.key
|
|
226
|
+
);
|
|
227
|
+
}) }) }),
|
|
228
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tbody", { role: "rowgroup", children: rows2.map((row, rowIndex) => {
|
|
229
|
+
const rowNum = rowIndex + 2;
|
|
230
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
231
|
+
"tr",
|
|
232
|
+
{
|
|
233
|
+
role: "row",
|
|
234
|
+
"aria-rowindex": rowNum,
|
|
235
|
+
"aria-label": `Row ${rowIndex + 1} of ${totalRows}`,
|
|
236
|
+
children: cols.map((c, colIndex) => {
|
|
237
|
+
const rawVal = row[c.key];
|
|
238
|
+
const isEmpty = rawVal == null || rawVal === "";
|
|
239
|
+
const displayVal = isEmpty ? "\u200B" : rawVal;
|
|
240
|
+
const widthStyle = c.width != null ? {
|
|
241
|
+
width: typeof c.width === "number" ? `${c.width}px` : c.width,
|
|
242
|
+
minWidth: typeof c.width === "number" ? `${c.width}px` : c.width,
|
|
243
|
+
maxWidth: typeof c.width === "number" ? `${c.width}px` : c.width
|
|
244
|
+
} : void 0;
|
|
245
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
246
|
+
"td",
|
|
247
|
+
{
|
|
248
|
+
role: "cell",
|
|
249
|
+
"aria-colindex": colIndex + 1,
|
|
250
|
+
"aria-label": isEmpty ? `${c.label}: Empty` : void 0,
|
|
251
|
+
className: c.numeric ? "num" : void 0,
|
|
252
|
+
style: widthStyle,
|
|
253
|
+
children: displayVal
|
|
254
|
+
},
|
|
255
|
+
c.key
|
|
256
|
+
);
|
|
257
|
+
})
|
|
258
|
+
},
|
|
259
|
+
rowIndex
|
|
260
|
+
);
|
|
261
|
+
}) })
|
|
262
|
+
]
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/DataTable/DataTable.jsx
|
|
268
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
269
|
+
var headerClass = (c) => [c.numeric && "num", c.casing === "asWritten" && "t-as-written"].filter(Boolean).join(" ") || void 0;
|
|
270
|
+
function DataTable({ columns: columns2 = [], rows: rows2 = [], className, ...rest }) {
|
|
271
|
+
const cols = columns2.map((c) => typeof c === "string" ? { key: c, label: c } : c);
|
|
272
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("table", { ...rest, className: cx("table", className), children: [
|
|
273
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("tr", { children: cols.map((c) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("th", { className: headerClass(c), children: c.label }, c.key)) }) }),
|
|
274
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("tbody", { children: rows2.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("tr", { children: cols.map((c) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("td", { className: c.numeric ? "num" : void 0, children: row[c.key] }, c.key)) }, i)) })
|
|
275
|
+
] });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/TextField/TextField.jsx
|
|
279
|
+
var React = __toESM(require("react"));
|
|
280
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
281
|
+
function TextField({
|
|
282
|
+
label,
|
|
283
|
+
hint,
|
|
284
|
+
error,
|
|
285
|
+
as = "input",
|
|
286
|
+
id,
|
|
287
|
+
invalid = false,
|
|
288
|
+
disabled = false,
|
|
289
|
+
labelCasing = "uppercase",
|
|
290
|
+
className,
|
|
291
|
+
children,
|
|
292
|
+
"aria-describedby": describedByProp,
|
|
293
|
+
...rest
|
|
294
|
+
}) {
|
|
295
|
+
const generated = React.useId();
|
|
296
|
+
const fid = id || generated;
|
|
297
|
+
const hintId = hint ? `${fid}-hint` : void 0;
|
|
298
|
+
const errorId = error ? `${fid}-error` : void 0;
|
|
299
|
+
const describedBy = [describedByProp, hintId, errorId].filter(Boolean).join(" ") || void 0;
|
|
300
|
+
const common = {
|
|
301
|
+
...rest,
|
|
302
|
+
id: fid,
|
|
303
|
+
className: cx("field__input", className),
|
|
304
|
+
"aria-invalid": invalid || error ? true : void 0,
|
|
305
|
+
"aria-describedby": describedBy,
|
|
306
|
+
disabled
|
|
307
|
+
};
|
|
308
|
+
let control;
|
|
309
|
+
if (as === "textarea") control = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("textarea", { ...common });
|
|
310
|
+
else if (as === "select") control = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("select", { ...common, children });
|
|
311
|
+
else control = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { ...common });
|
|
312
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "field", children: [
|
|
313
|
+
label ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("label", { className: cx("field__label", labelCasing === "asWritten" && "t-as-written"), htmlFor: fid, children: label }) : null,
|
|
314
|
+
control,
|
|
315
|
+
hint ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "field__hint", id: hintId, children: hint }) : null,
|
|
316
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "field__hint is-error", id: errorId, children: error }) : null
|
|
317
|
+
] });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/sdui/serializer.js
|
|
321
|
+
var VALID_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
322
|
+
"button",
|
|
323
|
+
"badge",
|
|
324
|
+
"tag",
|
|
325
|
+
"card",
|
|
326
|
+
"data-matrix",
|
|
327
|
+
"data-table",
|
|
328
|
+
"text-field",
|
|
329
|
+
"container",
|
|
330
|
+
"text"
|
|
331
|
+
]);
|
|
332
|
+
var BUTTON_VARIANTS = ["primary", "outline", "secondary", "ghost", "accent"];
|
|
333
|
+
var BADGE_VARIANTS = ["outline", "solid", "accent", "accent-outline"];
|
|
334
|
+
var SIZES = ["sm", "md", "lg"];
|
|
335
|
+
var CASINGS = ["uppercase", "asWritten"];
|
|
336
|
+
var VALID_TOKEN_NAMES = /* @__PURE__ */ new Set([
|
|
337
|
+
// color
|
|
338
|
+
"substrate",
|
|
339
|
+
"surfaceElevated",
|
|
340
|
+
"surfaceRecessed",
|
|
341
|
+
"ink100",
|
|
342
|
+
"ink60",
|
|
343
|
+
"ink30",
|
|
344
|
+
"signalError",
|
|
345
|
+
// radius
|
|
346
|
+
"subtle",
|
|
347
|
+
"control",
|
|
348
|
+
"card",
|
|
349
|
+
"badge",
|
|
350
|
+
// space
|
|
351
|
+
"half",
|
|
352
|
+
"1",
|
|
353
|
+
"2",
|
|
354
|
+
"3",
|
|
355
|
+
"4",
|
|
356
|
+
"6",
|
|
357
|
+
"8",
|
|
358
|
+
"12",
|
|
359
|
+
"16",
|
|
360
|
+
"touch",
|
|
361
|
+
// measure
|
|
362
|
+
"body",
|
|
363
|
+
"compact",
|
|
364
|
+
"sidebar"
|
|
365
|
+
]);
|
|
366
|
+
var string = { kind: "string" };
|
|
367
|
+
var boolean = { kind: "boolean" };
|
|
368
|
+
var oneOf = (values) => ({ kind: "enum", values });
|
|
369
|
+
var columns = { kind: "columns" };
|
|
370
|
+
var rows = { kind: "rows" };
|
|
371
|
+
var colorValue = { kind: "color" };
|
|
372
|
+
var SDUI_PROP_ALLOWLIST = {
|
|
373
|
+
button: { variant: oneOf(BUTTON_VARIANTS), size: oneOf(SIZES), block: boolean, disabled: boolean, type: oneOf(["button", "submit", "reset"]), token: string },
|
|
374
|
+
badge: { variant: oneOf(BADGE_VARIANTS), accentColor: colorValue, token: string },
|
|
375
|
+
tag: { token: string },
|
|
376
|
+
card: { eyebrow: string, eyebrowCasing: oneOf(CASINGS), title: string, flat: boolean, compact: boolean, token: string },
|
|
377
|
+
"data-matrix": { columns, rows, label: string, token: string },
|
|
378
|
+
"data-table": { columns, rows, token: string },
|
|
379
|
+
"text-field": {
|
|
380
|
+
label: string,
|
|
381
|
+
hint: string,
|
|
382
|
+
error: string,
|
|
383
|
+
as: oneOf(["input", "textarea", "select"]),
|
|
384
|
+
invalid: boolean,
|
|
385
|
+
disabled: boolean,
|
|
386
|
+
labelCasing: oneOf(CASINGS),
|
|
387
|
+
placeholder: string,
|
|
388
|
+
name: string,
|
|
389
|
+
type: oneOf(["text", "email", "number", "password", "search", "tel", "url"]),
|
|
390
|
+
defaultValue: string,
|
|
391
|
+
token: string
|
|
392
|
+
},
|
|
393
|
+
container: { token: string },
|
|
394
|
+
text: { token: string }
|
|
395
|
+
};
|
|
396
|
+
var COLOR_PATTERN = /^(#[0-9a-f]{3,8}|rgba?\([\d\s.,%/]+\)|oklch\([\d\s.%/deg]+\))$/i;
|
|
397
|
+
var COLUMN_KEYS = /* @__PURE__ */ new Set(["key", "label", "numeric", "width", "casing"]);
|
|
398
|
+
var fail = (message) => {
|
|
399
|
+
throw new Error(message);
|
|
400
|
+
};
|
|
401
|
+
function validateColumns(value, where) {
|
|
402
|
+
if (!Array.isArray(value)) fail(`${where}: "columns" must be an array.`);
|
|
403
|
+
for (const col of value) {
|
|
404
|
+
if (typeof col === "string") continue;
|
|
405
|
+
if (!col || typeof col !== "object" || Array.isArray(col)) fail(`${where}: a column must be a string or an object.`);
|
|
406
|
+
for (const [k, v] of Object.entries(col)) {
|
|
407
|
+
if (!COLUMN_KEYS.has(k)) fail(`${where}: unsupported column key "${k}".`);
|
|
408
|
+
if ((k === "key" || k === "label") && typeof v !== "string") fail(`${where}: column "${k}" must be a string.`);
|
|
409
|
+
if (k === "numeric" && typeof v !== "boolean") fail(`${where}: column "numeric" must be a boolean.`);
|
|
410
|
+
if (k === "width" && typeof v !== "number" && typeof v !== "string") fail(`${where}: column "width" must be a number or string.`);
|
|
411
|
+
if (k === "casing" && !CASINGS.includes(v)) fail(`${where}: column "casing" must be one of ${CASINGS.join(", ")}.`);
|
|
412
|
+
}
|
|
413
|
+
if (typeof col.key !== "string" || typeof col.label !== "string") fail(`${where}: a column needs "key" and "label" strings.`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function validateRows(value, where) {
|
|
417
|
+
if (!Array.isArray(value)) fail(`${where}: "rows" must be an array.`);
|
|
418
|
+
for (const row of value) {
|
|
419
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) fail(`${where}: a row must be an object.`);
|
|
420
|
+
for (const cell of Object.values(row)) {
|
|
421
|
+
if (cell !== null && !["string", "number", "boolean"].includes(typeof cell)) fail(`${where}: row cells must be strings, numbers, booleans or null.`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function validateProp(type, name, spec, value) {
|
|
426
|
+
const where = `SDUI ${type} prop "${name}"`;
|
|
427
|
+
switch (spec.kind) {
|
|
428
|
+
case "string":
|
|
429
|
+
if (typeof value !== "string") fail(`${where} must be a string.`);
|
|
430
|
+
break;
|
|
431
|
+
case "boolean":
|
|
432
|
+
if (typeof value !== "boolean") fail(`${where} must be a boolean.`);
|
|
433
|
+
break;
|
|
434
|
+
case "enum":
|
|
435
|
+
if (!spec.values.includes(value)) fail(`Unsupported ${where}: "${value}". Allowed: ${spec.values.join(", ")}`);
|
|
436
|
+
break;
|
|
437
|
+
case "color":
|
|
438
|
+
if (typeof value !== "string" || !COLOR_PATTERN.test(value)) fail(`${where} must be a #hex, rgb() or oklch() colour.`);
|
|
439
|
+
break;
|
|
440
|
+
case "columns":
|
|
441
|
+
validateColumns(value, where);
|
|
442
|
+
break;
|
|
443
|
+
case "rows":
|
|
444
|
+
validateRows(value, where);
|
|
445
|
+
break;
|
|
446
|
+
default:
|
|
447
|
+
fail(`${where} has an unknown spec.`);
|
|
448
|
+
}
|
|
449
|
+
if (name === "token" && !VALID_TOKEN_NAMES.has(value)) fail(`Unsupported SDUI token reference: "${value}".`);
|
|
450
|
+
}
|
|
451
|
+
function serializeTokens(tokens) {
|
|
452
|
+
if (!tokens || typeof tokens !== "object") {
|
|
453
|
+
throw new Error("Invalid tokens object provided for SDUI serialization.");
|
|
454
|
+
}
|
|
455
|
+
const sduiTokens = {
|
|
456
|
+
$schema: "https://judarn.design/schemas/sdui/tokens/v1.json",
|
|
457
|
+
space: {},
|
|
458
|
+
measure: {},
|
|
459
|
+
border: {},
|
|
460
|
+
radius: {},
|
|
461
|
+
color: {}
|
|
462
|
+
};
|
|
463
|
+
if (tokens.space) {
|
|
464
|
+
for (const [key, val] of Object.entries(tokens.space)) {
|
|
465
|
+
sduiTokens.space[key] = val.$value;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (tokens.measure) {
|
|
469
|
+
for (const [key, val] of Object.entries(tokens.measure)) {
|
|
470
|
+
sduiTokens.measure[key] = val.$value;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (tokens.border) {
|
|
474
|
+
for (const [key, val] of Object.entries(tokens.border)) {
|
|
475
|
+
sduiTokens.border[key] = val.$value;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (tokens.radius) {
|
|
479
|
+
for (const [key, val] of Object.entries(tokens.radius)) {
|
|
480
|
+
sduiTokens.radius[key] = val.$value;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (tokens.color) {
|
|
484
|
+
for (const [key, val] of Object.entries(tokens.color)) {
|
|
485
|
+
sduiTokens.color[key] = {
|
|
486
|
+
light: val.light?.$value,
|
|
487
|
+
dark: val.dark?.$value,
|
|
488
|
+
lightHighContrast: val.lightHighContrast?.$value,
|
|
489
|
+
darkHighContrast: val.darkHighContrast?.$value
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return sduiTokens;
|
|
494
|
+
}
|
|
495
|
+
function validateSDUINode(node) {
|
|
496
|
+
if (!node || typeof node !== "object" || Array.isArray(node)) {
|
|
497
|
+
fail("SDUI Node must be a non-null object.");
|
|
498
|
+
}
|
|
499
|
+
if (!node.type || typeof node.type !== "string") {
|
|
500
|
+
fail('SDUI Node missing required "type" string property.');
|
|
501
|
+
}
|
|
502
|
+
if (!VALID_NODE_TYPES.has(node.type)) {
|
|
503
|
+
fail(`Unsupported SDUI node type: "${node.type}". Allowed: ${Array.from(VALID_NODE_TYPES).join(", ")}`);
|
|
504
|
+
}
|
|
505
|
+
if (node.props !== void 0) {
|
|
506
|
+
if (!node.props || typeof node.props !== "object" || Array.isArray(node.props)) fail("SDUI Node props must be an object.");
|
|
507
|
+
const allowed = SDUI_PROP_ALLOWLIST[node.type];
|
|
508
|
+
for (const [name, value] of Object.entries(node.props)) {
|
|
509
|
+
if (!Object.prototype.hasOwnProperty.call(allowed, name)) {
|
|
510
|
+
fail(`Unsupported SDUI prop "${name}" on "${node.type}". Allowed: ${Object.keys(allowed).join(", ") || "none"}`);
|
|
511
|
+
}
|
|
512
|
+
validateProp(node.type, name, allowed[name], value);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (node.content !== void 0 && typeof node.content !== "string") {
|
|
516
|
+
fail("SDUI Node content must be a string.");
|
|
517
|
+
}
|
|
518
|
+
if (node.children !== void 0) {
|
|
519
|
+
if (!Array.isArray(node.children)) fail("SDUI Node children must be an array.");
|
|
520
|
+
for (const child of node.children) validateSDUINode(child);
|
|
521
|
+
}
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
function serializeComponent(type, props = {}, children = [], content = void 0) {
|
|
525
|
+
const node = { type, props: { ...props } };
|
|
526
|
+
if (content !== void 0) node.content = String(content);
|
|
527
|
+
if (Array.isArray(children) && children.length > 0) node.children = children;
|
|
528
|
+
validateSDUINode(node);
|
|
529
|
+
return node;
|
|
530
|
+
}
|
|
531
|
+
function deserializeComponent(node, customComponents = {}, reactInstance = React2) {
|
|
532
|
+
validateSDUINode(node);
|
|
533
|
+
const components = {
|
|
534
|
+
Button,
|
|
535
|
+
Badge,
|
|
536
|
+
Tag,
|
|
537
|
+
Card,
|
|
538
|
+
DataMatrix,
|
|
539
|
+
DataTable,
|
|
540
|
+
TextField,
|
|
541
|
+
...customComponents
|
|
542
|
+
};
|
|
543
|
+
const h = (type, props2, children) => reactInstance.createElement(type, props2, ...children);
|
|
544
|
+
const childElements = node.children ? node.children.map((child, i) => {
|
|
545
|
+
const el = deserializeComponent(child, customComponents, reactInstance);
|
|
546
|
+
return reactInstance.isValidElement(el) ? reactInstance.cloneElement(el, { key: i }) : el;
|
|
547
|
+
}) : [];
|
|
548
|
+
const contentOrChildren = node.content !== void 0 ? [node.content] : childElements;
|
|
549
|
+
const { token: _token, ...props } = node.props || {};
|
|
550
|
+
switch (node.type) {
|
|
551
|
+
case "button":
|
|
552
|
+
return h(components.Button, props, contentOrChildren);
|
|
553
|
+
case "badge":
|
|
554
|
+
return h(components.Badge, props, contentOrChildren);
|
|
555
|
+
case "tag":
|
|
556
|
+
return h(components.Tag, props, contentOrChildren);
|
|
557
|
+
case "card":
|
|
558
|
+
return h(components.Card, props, contentOrChildren);
|
|
559
|
+
case "data-matrix":
|
|
560
|
+
return h(components.DataMatrix, props, []);
|
|
561
|
+
case "data-table":
|
|
562
|
+
return h(components.DataTable, props, []);
|
|
563
|
+
case "text-field":
|
|
564
|
+
return h(components.TextField, props, []);
|
|
565
|
+
case "container":
|
|
566
|
+
return h("div", props, contentOrChildren);
|
|
567
|
+
case "text":
|
|
568
|
+
return h("span", props, [node.content || ""]);
|
|
569
|
+
default:
|
|
570
|
+
return fail(`Unhandled SDUI node type: ${node.type}`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './serializer';
|