@judarn/ui 0.0.0-stage → 0.7.2

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