@ux-builder/ui 0.0.0-stage → 0.6.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/AGENTS.md +275 -0
- package/README.md +293 -2
- package/THIRD-PARTY-NOTICES.md +195 -0
- package/catalogue.json +19424 -0
- package/dist/icons.js +2 -0
- package/dist/index.js +537 -0
- package/dist/index.js.map +1 -0
- package/dist/patterns.css +900 -0
- package/dist/patterns.js +4235 -0
- package/dist/patterns.js.map +1 -0
- package/dist/shared-Bom_GJww.js +18858 -0
- package/dist/shared-Bom_GJww.js.map +1 -0
- package/dist/shared-CjrUPFqv.js +1501 -0
- package/dist/shared-CjrUPFqv.js.map +1 -0
- package/dist/ui.css +997 -0
- package/licenses/carbon.LICENSE +201 -0
- package/licenses/fluent.LICENSE +24 -0
- package/licenses/lucide.LICENSE +43 -0
- package/licenses/material.LICENSE +202 -0
- package/licenses/vendored.json +214 -0
- package/lint/core.mjs +158 -0
- package/lint/eslint.mjs +128 -0
- package/lint/stylelint.mjs +35 -0
- package/package.json +104 -4
- package/skill/ux-builder-ui/SKILL.md +280 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$description": "Third-party source shipped inside @ux-builder/ui (ADR-023 rule 6), read by tools/check-licences.mjs. Each entry's files must keep its notice; changed files must say so; its licence text must ship in licenses/. The icon glyphs are generated by scripts/gen-icons.mjs, which writes these notice lines and copies the licences. Entries with shipped: false (AI Elements, assistant-ui and shadcn/ui, copied by tools/vendor-ai-elements.mjs) are used only by Storybook and the theme builder, so they need their notices but not a place in package.json \"files\".",
|
|
3
|
+
"vendored": [
|
|
4
|
+
{
|
|
5
|
+
"name": "@material-symbols/svg-400 glyphs",
|
|
6
|
+
"source": "https://github.com/marella/material-symbols",
|
|
7
|
+
"version": "0.47.6",
|
|
8
|
+
"licence": "Apache-2.0",
|
|
9
|
+
"licenceFile": "licenses/material.LICENSE",
|
|
10
|
+
"files": [
|
|
11
|
+
"src/icons/glyphs.generated.js",
|
|
12
|
+
"src/icons/library.generated.js"
|
|
13
|
+
],
|
|
14
|
+
"notice": "@material-symbols/svg-400@0.47.6, Apache-2.0 (licenses/material.LICENSE)",
|
|
15
|
+
"changed": [
|
|
16
|
+
"src/icons/glyphs.generated.js",
|
|
17
|
+
"src/icons/library.generated.js"
|
|
18
|
+
],
|
|
19
|
+
"changeNotice": "Generated by scripts/gen-icons.mjs"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "@fluentui/svg-icons glyphs",
|
|
23
|
+
"source": "https://github.com/microsoft/fluentui-system-icons",
|
|
24
|
+
"version": "1.1.343",
|
|
25
|
+
"licence": "MIT",
|
|
26
|
+
"licenceFile": "licenses/fluent.LICENSE",
|
|
27
|
+
"files": [
|
|
28
|
+
"src/icons/glyphs.generated.js",
|
|
29
|
+
"src/icons/library.generated.js"
|
|
30
|
+
],
|
|
31
|
+
"notice": "@fluentui/svg-icons@1.1.343, MIT (licenses/fluent.LICENSE)",
|
|
32
|
+
"changed": [
|
|
33
|
+
"src/icons/glyphs.generated.js",
|
|
34
|
+
"src/icons/library.generated.js"
|
|
35
|
+
],
|
|
36
|
+
"changeNotice": "Generated by scripts/gen-icons.mjs"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "@carbon/icons glyphs",
|
|
40
|
+
"source": "https://github.com/carbon-design-system/carbon",
|
|
41
|
+
"version": "11.89.0",
|
|
42
|
+
"licence": "Apache-2.0",
|
|
43
|
+
"licenceFile": "licenses/carbon.LICENSE",
|
|
44
|
+
"files": [
|
|
45
|
+
"src/icons/glyphs.generated.js",
|
|
46
|
+
"src/icons/library.generated.js"
|
|
47
|
+
],
|
|
48
|
+
"notice": "@carbon/icons@11.89.0, Apache-2.0 (licenses/carbon.LICENSE)",
|
|
49
|
+
"changed": [
|
|
50
|
+
"src/icons/glyphs.generated.js",
|
|
51
|
+
"src/icons/library.generated.js"
|
|
52
|
+
],
|
|
53
|
+
"changeNotice": "Generated by scripts/gen-icons.mjs"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "lucide-static glyphs",
|
|
57
|
+
"source": "https://github.com/lucide-icons/lucide",
|
|
58
|
+
"version": "1.52.0",
|
|
59
|
+
"licence": "ISC",
|
|
60
|
+
"licenceFile": "licenses/lucide.LICENSE",
|
|
61
|
+
"files": [
|
|
62
|
+
"src/icons/glyphs.generated.js",
|
|
63
|
+
"src/icons/library.generated.js"
|
|
64
|
+
],
|
|
65
|
+
"notice": "lucide-static@1.52.0, ISC (licenses/lucide.LICENSE)",
|
|
66
|
+
"changed": [
|
|
67
|
+
"src/icons/glyphs.generated.js",
|
|
68
|
+
"src/icons/library.generated.js"
|
|
69
|
+
],
|
|
70
|
+
"changeNotice": "Generated by scripts/gen-icons.mjs"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "AI Elements components",
|
|
74
|
+
"source": "https://github.com/vercel/ai-elements (registry.ai-sdk.dev)",
|
|
75
|
+
"version": "registry, fetched 2026-10-19",
|
|
76
|
+
"licence": "Apache-2.0",
|
|
77
|
+
"licenceFile": "ai/licenses/ai-elements.LICENSE",
|
|
78
|
+
"shipped": false,
|
|
79
|
+
"files": [
|
|
80
|
+
"ai/vendor/ai-elements/artifact.tsx",
|
|
81
|
+
"ai/vendor/ai-elements/chain-of-thought.tsx",
|
|
82
|
+
"ai/vendor/ai-elements/code-block.tsx",
|
|
83
|
+
"ai/vendor/ai-elements/confirmation.tsx",
|
|
84
|
+
"ai/vendor/ai-elements/conversation.tsx",
|
|
85
|
+
"ai/vendor/ai-elements/inline-citation.tsx",
|
|
86
|
+
"ai/vendor/ai-elements/message.tsx",
|
|
87
|
+
"ai/vendor/ai-elements/prompt-input.tsx",
|
|
88
|
+
"ai/vendor/ai-elements/reasoning.tsx",
|
|
89
|
+
"ai/vendor/ai-elements/shimmer.tsx",
|
|
90
|
+
"ai/vendor/ai-elements/sources.tsx",
|
|
91
|
+
"ai/vendor/ai-elements/suggestion.tsx",
|
|
92
|
+
"ai/vendor/ai-elements/task.tsx",
|
|
93
|
+
"ai/vendor/ai-elements/tool.tsx"
|
|
94
|
+
],
|
|
95
|
+
"notice": "Copyright 2023 Vercel, Inc.",
|
|
96
|
+
"changed": [
|
|
97
|
+
"ai/vendor/ai-elements/artifact.tsx",
|
|
98
|
+
"ai/vendor/ai-elements/chain-of-thought.tsx",
|
|
99
|
+
"ai/vendor/ai-elements/code-block.tsx",
|
|
100
|
+
"ai/vendor/ai-elements/confirmation.tsx",
|
|
101
|
+
"ai/vendor/ai-elements/conversation.tsx",
|
|
102
|
+
"ai/vendor/ai-elements/inline-citation.tsx",
|
|
103
|
+
"ai/vendor/ai-elements/message.tsx",
|
|
104
|
+
"ai/vendor/ai-elements/prompt-input.tsx",
|
|
105
|
+
"ai/vendor/ai-elements/reasoning.tsx",
|
|
106
|
+
"ai/vendor/ai-elements/shimmer.tsx",
|
|
107
|
+
"ai/vendor/ai-elements/sources.tsx",
|
|
108
|
+
"ai/vendor/ai-elements/suggestion.tsx",
|
|
109
|
+
"ai/vendor/ai-elements/task.tsx",
|
|
110
|
+
"ai/vendor/ai-elements/tool.tsx"
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "assistant-ui components",
|
|
115
|
+
"source": "https://github.com/assistant-ui/assistant-ui (r.assistant-ui.com)",
|
|
116
|
+
"version": "registry, fetched 2026-10-19",
|
|
117
|
+
"licence": "MIT",
|
|
118
|
+
"licenceFile": "ai/licenses/assistant-ui.LICENSE",
|
|
119
|
+
"shipped": false,
|
|
120
|
+
"files": [
|
|
121
|
+
"ai/vendor/assistant-ui/attachment.aui.tsx",
|
|
122
|
+
"ai/vendor/assistant-ui/collapsible-root.tsx",
|
|
123
|
+
"ai/vendor/assistant-ui/file.tsx",
|
|
124
|
+
"ai/vendor/assistant-ui/follow-up-suggestions.aui.tsx",
|
|
125
|
+
"ai/vendor/assistant-ui/href.ts",
|
|
126
|
+
"ai/vendor/assistant-ui/image.tsx",
|
|
127
|
+
"ai/vendor/assistant-ui/markdown-text.tsx",
|
|
128
|
+
"ai/vendor/assistant-ui/media-player.tsx",
|
|
129
|
+
"ai/vendor/assistant-ui/reasoning.aui.tsx",
|
|
130
|
+
"ai/vendor/assistant-ui/reasoning.tsx",
|
|
131
|
+
"ai/vendor/assistant-ui/surfaces.tsx",
|
|
132
|
+
"ai/vendor/assistant-ui/thread.aui.tsx",
|
|
133
|
+
"ai/vendor/assistant-ui/tool-fallback.aui.tsx",
|
|
134
|
+
"ai/vendor/assistant-ui/tool-group.aui.tsx",
|
|
135
|
+
"ai/vendor/assistant-ui/tooltip-icon-button.tsx",
|
|
136
|
+
"ai/vendor/assistant-ui/use-attachment-src.ts",
|
|
137
|
+
"ai/vendor/assistant-ui/use-copy-to-clipboard.ts",
|
|
138
|
+
"ai/vendor/assistant-ui/useIsomorphicLayoutEffect.ts"
|
|
139
|
+
],
|
|
140
|
+
"notice": "Copyright (c) 2026 AgentbaseAI Inc.",
|
|
141
|
+
"changed": [
|
|
142
|
+
"ai/vendor/assistant-ui/attachment.aui.tsx",
|
|
143
|
+
"ai/vendor/assistant-ui/collapsible-root.tsx",
|
|
144
|
+
"ai/vendor/assistant-ui/file.tsx",
|
|
145
|
+
"ai/vendor/assistant-ui/follow-up-suggestions.aui.tsx",
|
|
146
|
+
"ai/vendor/assistant-ui/href.ts",
|
|
147
|
+
"ai/vendor/assistant-ui/image.tsx",
|
|
148
|
+
"ai/vendor/assistant-ui/markdown-text.tsx",
|
|
149
|
+
"ai/vendor/assistant-ui/media-player.tsx",
|
|
150
|
+
"ai/vendor/assistant-ui/reasoning.aui.tsx",
|
|
151
|
+
"ai/vendor/assistant-ui/reasoning.tsx",
|
|
152
|
+
"ai/vendor/assistant-ui/surfaces.tsx",
|
|
153
|
+
"ai/vendor/assistant-ui/thread.aui.tsx",
|
|
154
|
+
"ai/vendor/assistant-ui/tool-fallback.aui.tsx",
|
|
155
|
+
"ai/vendor/assistant-ui/tool-group.aui.tsx",
|
|
156
|
+
"ai/vendor/assistant-ui/tooltip-icon-button.tsx",
|
|
157
|
+
"ai/vendor/assistant-ui/use-attachment-src.ts",
|
|
158
|
+
"ai/vendor/assistant-ui/use-copy-to-clipboard.ts",
|
|
159
|
+
"ai/vendor/assistant-ui/useIsomorphicLayoutEffect.ts"
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"name": "shadcn/ui components",
|
|
164
|
+
"source": "https://github.com/shadcn-ui/ui (ui.shadcn.com, new-york-v4)",
|
|
165
|
+
"version": "registry, fetched 2026-10-19",
|
|
166
|
+
"licence": "MIT",
|
|
167
|
+
"licenceFile": "ai/licenses/shadcn-ui.LICENSE",
|
|
168
|
+
"shipped": false,
|
|
169
|
+
"files": [
|
|
170
|
+
"ai/vendor/ui/alert.tsx",
|
|
171
|
+
"ai/vendor/ui/avatar.tsx",
|
|
172
|
+
"ai/vendor/ui/badge.tsx",
|
|
173
|
+
"ai/vendor/ui/button-group.tsx",
|
|
174
|
+
"ai/vendor/ui/button.tsx",
|
|
175
|
+
"ai/vendor/ui/carousel.tsx",
|
|
176
|
+
"ai/vendor/ui/collapsible.tsx",
|
|
177
|
+
"ai/vendor/ui/command.tsx",
|
|
178
|
+
"ai/vendor/ui/dialog.tsx",
|
|
179
|
+
"ai/vendor/ui/dropdown-menu.tsx",
|
|
180
|
+
"ai/vendor/ui/hover-card.tsx",
|
|
181
|
+
"ai/vendor/ui/input-group.tsx",
|
|
182
|
+
"ai/vendor/ui/input.tsx",
|
|
183
|
+
"ai/vendor/ui/scroll-area.tsx",
|
|
184
|
+
"ai/vendor/ui/select.tsx",
|
|
185
|
+
"ai/vendor/ui/separator.tsx",
|
|
186
|
+
"ai/vendor/ui/skeleton.tsx",
|
|
187
|
+
"ai/vendor/ui/textarea.tsx",
|
|
188
|
+
"ai/vendor/ui/tooltip.tsx"
|
|
189
|
+
],
|
|
190
|
+
"notice": "Copyright (c) 2023 shadcn",
|
|
191
|
+
"changed": [
|
|
192
|
+
"ai/vendor/ui/alert.tsx",
|
|
193
|
+
"ai/vendor/ui/avatar.tsx",
|
|
194
|
+
"ai/vendor/ui/badge.tsx",
|
|
195
|
+
"ai/vendor/ui/button-group.tsx",
|
|
196
|
+
"ai/vendor/ui/button.tsx",
|
|
197
|
+
"ai/vendor/ui/carousel.tsx",
|
|
198
|
+
"ai/vendor/ui/collapsible.tsx",
|
|
199
|
+
"ai/vendor/ui/command.tsx",
|
|
200
|
+
"ai/vendor/ui/dialog.tsx",
|
|
201
|
+
"ai/vendor/ui/dropdown-menu.tsx",
|
|
202
|
+
"ai/vendor/ui/hover-card.tsx",
|
|
203
|
+
"ai/vendor/ui/input-group.tsx",
|
|
204
|
+
"ai/vendor/ui/input.tsx",
|
|
205
|
+
"ai/vendor/ui/scroll-area.tsx",
|
|
206
|
+
"ai/vendor/ui/select.tsx",
|
|
207
|
+
"ai/vendor/ui/separator.tsx",
|
|
208
|
+
"ai/vendor/ui/skeleton.tsx",
|
|
209
|
+
"ai/vendor/ui/textarea.tsx",
|
|
210
|
+
"ai/vendor/ui/tooltip.tsx"
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
}
|
package/lint/core.mjs
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/* ux-builder/no-hard-coded-values (FEAT-004 SDK-10, SDK-11; US-013): the checks
|
|
2
|
+
shared by the stylelint plugin and the ESLint rule.
|
|
3
|
+
|
|
4
|
+
A literal colour, font family or border radius in app code looks right
|
|
5
|
+
under one theme and wrong under every other. This finds them in a CSS
|
|
6
|
+
declaration (property + value), names the role token to use where the
|
|
7
|
+
app's theme has one with that exact value, and honours a reviewed
|
|
8
|
+
allow-list: each entry needs a reason.
|
|
9
|
+
|
|
10
|
+
Not reported: var(--…) (including literals in a var() fallback, which
|
|
11
|
+
only apply when a token is missing), transparent, currentColor, the
|
|
12
|
+
CSS-wide keywords, none, 0, generic font families on their own, and
|
|
13
|
+
fully round radii (50%, 100%, 999px or more), which are geometry rather
|
|
14
|
+
than a theme's choice. */
|
|
15
|
+
|
|
16
|
+
import { createRequire } from "node:module";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
|
|
19
|
+
export const RULE = "ux-builder/no-hard-coded-values";
|
|
20
|
+
|
|
21
|
+
const NAMED = new Set(("aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat white whitesmoke yellow yellowgreen").split(" "));
|
|
22
|
+
const GENERIC = new Set(["serif", "sans-serif", "monospace", "cursive", "fantasy", "system-ui", "ui-serif", "ui-sans-serif", "ui-monospace", "ui-rounded", "emoji", "math", "fangsong", "inherit", "initial", "unset", "revert", "revert-layer"]);
|
|
23
|
+
const COLOUR_FN = /\b(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color)\(/gi;
|
|
24
|
+
const RADIUS = /^border(?:-(?:top|bottom|start|end)-(?:left|right|start|end))?-radius$/;
|
|
25
|
+
const COLOUR_PROPS = /^(?:--.*|color|background(?:-color|-image)?|border(?:-(?:top|right|bottom|left|block|inline)(?:-(?:start|end))?)?(?:-color)?|outline(?:-color)?|fill|stroke|(?:box|text)-shadow|caret-color|accent-color|text-decoration(?:-color)?|text-emphasis(?:-color)?|column-rule(?:-color)?|scrollbar-color|stop-color|flood-color|lighting-color)$/i;
|
|
26
|
+
|
|
27
|
+
/* Remove var(…) (with its fallback) and url(…), keeping positions roughly
|
|
28
|
+
stable by blanking them, so what is left is the literal part. */
|
|
29
|
+
export function stripVars(value) {
|
|
30
|
+
let out = "", i = 0;
|
|
31
|
+
const s = String(value);
|
|
32
|
+
while (i < s.length) {
|
|
33
|
+
const m = /^(var|url|env)\(/i.exec(s.slice(i));
|
|
34
|
+
if (m) {
|
|
35
|
+
let depth = 0, j = i + m[1].length;
|
|
36
|
+
for (; j < s.length; j++) { if (s[j] === "(") depth++; else if (s[j] === ")" && --depth === 0) break; }
|
|
37
|
+
out += " ".repeat(j + 1 - i); i = j + 1; continue;
|
|
38
|
+
}
|
|
39
|
+
out += s[i++];
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function balanced(s, start) {
|
|
45
|
+
let depth = 0;
|
|
46
|
+
for (let j = start; j < s.length; j++) { if (s[j] === "(") depth++; else if (s[j] === ")" && --depth === 0) return s.slice(start, j + 1); }
|
|
47
|
+
return s.slice(start);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* The literal colours, fonts and radii in one declaration: [{ kind, literal, index }]. */
|
|
51
|
+
export function literals(property, value) {
|
|
52
|
+
const prop = String(property).toLowerCase();
|
|
53
|
+
const raw = String(value);
|
|
54
|
+
const bare = stripVars(raw);
|
|
55
|
+
const out = [];
|
|
56
|
+
if (COLOUR_PROPS.test(prop)) {
|
|
57
|
+
const noStrings = bare.replace(/(["'])(?:\\.|(?!\1).)*\1/g, (m) => " ".repeat(m.length));
|
|
58
|
+
for (const m of noStrings.matchAll(/#[0-9a-f]{3,8}\b/gi)) if ([4, 5, 7, 9].includes(m[0].length)) out.push({ kind: "colour", literal: m[0], index: m.index });
|
|
59
|
+
for (const m of noStrings.matchAll(COLOUR_FN)) out.push({ kind: "colour", literal: balanced(noStrings, m.index + m[0].length - 1).replace(/^\(/, m[0].slice(0, -1) + "("), index: m.index });
|
|
60
|
+
for (const m of noStrings.matchAll(/[a-z]+/gi)) if (NAMED.has(m[0].toLowerCase()) && !/[-\w]$/.test(noStrings.slice(0, m.index)) && !/^[-\w(]/.test(noStrings.slice(m.index + m[0].length))) out.push({ kind: "colour", literal: m[0], index: m.index });
|
|
61
|
+
}
|
|
62
|
+
if (prop === "font-family" || prop === "font") {
|
|
63
|
+
// In the font shorthand the families follow the size (and optional /line-height).
|
|
64
|
+
const families = prop === "font" ? (/(?:^|\s)(?:[\d.]+(?:px|rem|em|%|pt|vw|vh|ch)|xx?-small|small|medium|large|xx?-large|larger|smaller)(?:\/\S+)?\s+(.+)$/i.exec(bare.trim())?.[1] ?? "") : bare;
|
|
65
|
+
for (const f of families.split(",").map((x) => x.trim()).filter(Boolean)) {
|
|
66
|
+
const name = f.replace(/^["']|["']$/g, "");
|
|
67
|
+
if (!GENERIC.has(name.toLowerCase())) { out.push({ kind: "font", literal: f, index: Math.max(0, raw.indexOf(f)) }); break; }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (RADIUS.test(prop)) {
|
|
71
|
+
for (const m of bare.matchAll(/-?\d*\.?\d+(px|rem|em|%|vw|vh|ch)?/g)) {
|
|
72
|
+
const n = parseFloat(m[0]), unit = m[1] ?? "";
|
|
73
|
+
if (n === 0) continue;
|
|
74
|
+
if ((unit === "%" && n >= 50) || (unit === "px" && n >= 999) || ((unit === "rem" || unit === "em") && n >= 99)) continue;
|
|
75
|
+
out.push({ kind: "radius", literal: m[0], index: m.index });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out.sort((a, b) => a.index - b.index);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* ---------------------------------------------------------- suggestions */
|
|
82
|
+
|
|
83
|
+
function hexOf(literal) {
|
|
84
|
+
const s = literal.trim().toLowerCase();
|
|
85
|
+
if (/^#[0-9a-f]{3,4}$/.test(s)) return "#" + [...s.slice(1, 4)].map((c) => c + c).join("").toUpperCase();
|
|
86
|
+
if (/^#[0-9a-f]{6}([0-9a-f]{2})?$/.test(s)) return s.slice(0, 7).toUpperCase();
|
|
87
|
+
const m = /^rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)/.exec(s);
|
|
88
|
+
if (m) return "#" + m.slice(1, 4).map((v) => Math.min(255, +v).toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const firstFamily = (stack) => String(stack).split(",")[0].trim().replace(/^["']|["']$/g, "").toLowerCase();
|
|
92
|
+
const PREFER = [/^piq-color-text$/, /^piq-color-/, /^piq-state-/, /^piq-font-body$/, /^piq-font-/, /^piq-radius-/, /-radius$/];
|
|
93
|
+
const rank = (k) => { const i = PREFER.findIndex((r) => r.test(k)); return i < 0 ? PREFER.length : i; };
|
|
94
|
+
|
|
95
|
+
/* Role tokens whose value in the app's theme equals this literal, best first. */
|
|
96
|
+
export function suggest(finding, tokens) {
|
|
97
|
+
if (!tokens) return [];
|
|
98
|
+
const entries = Object.entries(tokens).filter(([k]) => k.startsWith("piq-"));
|
|
99
|
+
let hits = [];
|
|
100
|
+
if (finding.kind === "colour") {
|
|
101
|
+
const hex = hexOf(finding.literal);
|
|
102
|
+
if (hex) hits = entries.filter(([k, v]) => /^piq-(color|state)-/.test(k) && String(v).toUpperCase() === hex);
|
|
103
|
+
} else if (finding.kind === "font") {
|
|
104
|
+
const f = firstFamily(finding.literal);
|
|
105
|
+
hits = entries.filter(([k, v]) => /^piq-font-(body|heading|display|mono)$/.test(k) && firstFamily(v) === f);
|
|
106
|
+
} else if (finding.kind === "radius") {
|
|
107
|
+
hits = entries.filter(([k, v]) => /radius/.test(k) && String(v).trim() === finding.literal.trim());
|
|
108
|
+
}
|
|
109
|
+
return hits.map(([k]) => k).sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const KIND = { colour: "colour", font: "font family", radius: "radius" };
|
|
113
|
+
export function message(finding, suggestions) {
|
|
114
|
+
const head = `Hard-coded ${KIND[finding.kind]} ${finding.literal}`;
|
|
115
|
+
if (!suggestions.length) return `${head}: use a --piq-* role token (${RULE})`;
|
|
116
|
+
const [best, ...rest] = suggestions;
|
|
117
|
+
return `${head}: use var(--${best})${rest.length ? ` (same value: ${rest.slice(0, 2).map((r) => `--${r}`).join(", ")})` : ""} (${RULE})`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ------------------------------------------------------------ options */
|
|
121
|
+
|
|
122
|
+
/* The resolved tokens of the app's theme, from the @ux-builder/tokens the app has
|
|
123
|
+
installed (else this package's own), for suggestions. */
|
|
124
|
+
export function loadTokens(theme, appearance = "light", from = process.cwd()) {
|
|
125
|
+
if (!theme) return null;
|
|
126
|
+
for (const base of [join(from, "noop.js"), import.meta.url]) {
|
|
127
|
+
try { return createRequire(base)(`@ux-builder/tokens/resolved/${theme}.${appearance}.json`).tokens; } catch { /* try the next */ }
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* Allow-list entries: { value?, property?, file?, reason }. Returns errors for
|
|
133
|
+
entries without a reason or without anything to match. */
|
|
134
|
+
export function checkAllow(allow = []) {
|
|
135
|
+
const errors = [];
|
|
136
|
+
if (!Array.isArray(allow)) return ["allow must be a list of { value?, property?, file?, reason }"];
|
|
137
|
+
allow.forEach((e, i) => {
|
|
138
|
+
if (!e || typeof e !== "object") errors.push(`allow[${i}] is not an object`);
|
|
139
|
+
else if (!String(e.reason ?? "").trim()) errors.push(`allow[${i}] has no reason: every exception is reviewed, so say why`);
|
|
140
|
+
else if (!e.value && !e.property && !e.file) errors.push(`allow[${i}] matches nothing: give value, property or file`);
|
|
141
|
+
});
|
|
142
|
+
return errors;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const globRe = (g) => new RegExp("(^|/)" + g.split("**").map((p) => p.split("*").map((x) => x.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")).join(".*") + "$");
|
|
146
|
+
export function allowed(finding, property, file, allow = []) {
|
|
147
|
+
return allow.some((e) => e?.reason
|
|
148
|
+
&& (!e.value || String(e.value).toLowerCase().replace(/\s+/g, "") === finding.literal.toLowerCase().replace(/\s+/g, ""))
|
|
149
|
+
&& (!e.property || String(e.property).toLowerCase() === String(property).toLowerCase())
|
|
150
|
+
&& (!e.file || globRe(e.file).test(String(file ?? "").split("\\").join("/"))));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Everything to report for one declaration. */
|
|
154
|
+
export function check(property, value, { tokens, allow = [], file } = {}) {
|
|
155
|
+
return literals(property, value)
|
|
156
|
+
.filter((f) => !allowed(f, property, file, allow))
|
|
157
|
+
.map((f) => ({ ...f, message: message(f, suggest(f, tokens)) }));
|
|
158
|
+
}
|
package/lint/eslint.mjs
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// ux-builder/no-hard-coded-values for ESLint: the same check on JSX style props
|
|
2
|
+
// (FEAT-004 SDK-10, SDK-11); and ux-builder/no-foreign-ui and
|
|
3
|
+
// ux-builder/no-inline-svg, below (US-022).
|
|
4
|
+
//
|
|
5
|
+
// // eslint.config.js
|
|
6
|
+
// import uxBuilder from "@ux-builder/ui/eslint";
|
|
7
|
+
// export default [{
|
|
8
|
+
// files: ["src/**/*.{js,jsx,ts,tsx}"],
|
|
9
|
+
// plugins: { "ux-builder": uxBuilder },
|
|
10
|
+
// rules: {
|
|
11
|
+
// "ux-builder/no-hard-coded-values": ["error", { theme: "databricks", allow: [] }],
|
|
12
|
+
// "ux-builder/no-foreign-ui": "error",
|
|
13
|
+
// "ux-builder/no-inline-svg": "error",
|
|
14
|
+
// },
|
|
15
|
+
// }];
|
|
16
|
+
//
|
|
17
|
+
// Only literal values are checked (strings, numbers for radii, template
|
|
18
|
+
// strings without expressions); a value computed at run time is the app's own
|
|
19
|
+
// data, not a hard-coded style.
|
|
20
|
+
|
|
21
|
+
import { RULE, check, checkAllow, loadTokens } from "./core.mjs";
|
|
22
|
+
|
|
23
|
+
const kebab = (k) => (k.startsWith("--") ? k : k.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`).replace(/^(webkit|moz|ms)-/, "-$1-"));
|
|
24
|
+
const keyOf = (p) => (p.key.type === "Identifier" ? p.key.name : p.key.type === "Literal" ? String(p.key.value) : null);
|
|
25
|
+
|
|
26
|
+
function literalOf(node, property) {
|
|
27
|
+
if (node.type === "Literal" && typeof node.value === "string") return node.value;
|
|
28
|
+
if (node.type === "Literal" && typeof node.value === "number") return /radius$/.test(property) && node.value !== 0 ? `${node.value}px` : null;
|
|
29
|
+
if (node.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis[0].value.cooked;
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const rule = {
|
|
34
|
+
meta: {
|
|
35
|
+
type: "suggestion",
|
|
36
|
+
docs: { description: "Report hard-coded colours, font families and radii in JSX style props; use --piq-* role tokens" },
|
|
37
|
+
schema: [{ type: "object", properties: { theme: { type: "string" }, allow: { type: "array" } }, additionalProperties: false }],
|
|
38
|
+
},
|
|
39
|
+
create(context) {
|
|
40
|
+
const opts = context.options[0] ?? {};
|
|
41
|
+
const allow = opts.allow ?? [];
|
|
42
|
+
const tokens = loadTokens(opts.theme, "light", context.cwd ?? process.cwd());
|
|
43
|
+
const file = context.filename ?? context.getFilename?.();
|
|
44
|
+
return {
|
|
45
|
+
Program(node) {
|
|
46
|
+
for (const e of checkAllow(allow)) context.report({ node, message: `${e} (${RULE})` });
|
|
47
|
+
},
|
|
48
|
+
JSXAttribute(attr) {
|
|
49
|
+
if (attr.name?.name !== "style" || attr.value?.type !== "JSXExpressionContainer") return;
|
|
50
|
+
const obj = attr.value.expression;
|
|
51
|
+
if (obj.type !== "ObjectExpression") return;
|
|
52
|
+
for (const p of obj.properties) {
|
|
53
|
+
if (p.type !== "Property" || p.computed) continue;
|
|
54
|
+
const key = keyOf(p);
|
|
55
|
+
if (!key) continue;
|
|
56
|
+
const property = kebab(key), value = literalOf(p.value, property);
|
|
57
|
+
if (value == null) continue;
|
|
58
|
+
for (const f of check(property, value, { tokens, allow, file })) context.report({ node: p.value, message: f.message });
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/* ux-builder/no-foreign-ui: another UI kit's components or icon set do not
|
|
66
|
+
follow the theme, so an import of one is reported with the SDK's
|
|
67
|
+
counterpart (US-022). allow: package names the app has reviewed. */
|
|
68
|
+
const FOREIGN = [
|
|
69
|
+
[/^@mui\/(?!utils|base)/, "Material UI", "the SDK's widgets"],
|
|
70
|
+
[/^antd(\/|$)/, "Ant Design", "the SDK's widgets"],
|
|
71
|
+
[/^@chakra-ui\//, "Chakra UI", "the SDK's widgets"],
|
|
72
|
+
[/^@carbon\/(react|icons-react)/, "Carbon React", "the SDK's widgets on a Carbon theme"],
|
|
73
|
+
[/^@fluentui\//, "Fluent UI", "the SDK's widgets on a Fluent theme"],
|
|
74
|
+
[/^@mantine\//, "Mantine", "the SDK's widgets"],
|
|
75
|
+
[/^react-bootstrap(\/|$)|^bootstrap(\/|$)/, "Bootstrap", "the SDK's widgets"],
|
|
76
|
+
[/^@blueprintjs\//, "Blueprint", "the SDK's widgets"],
|
|
77
|
+
[/^primereact(\/|$)/, "PrimeReact", "the SDK's widgets"],
|
|
78
|
+
[/^@radix-ui\/themes/, "Radix Themes", "the SDK's widgets"],
|
|
79
|
+
[/^lucide-react|^react-icons(\/|$)|^@heroicons\/|^@phosphor-icons\/|^@tabler\/icons|^@fortawesome\//, "an icon set", "<Icon> with @ux-builder/ui/icons, which follows the theme's icon set"],
|
|
80
|
+
[/^react-spinners|^react-loader-spinner|^react-loading-skeleton/, "a loader library", "the SDK's loading widgets"],
|
|
81
|
+
];
|
|
82
|
+
const foreign = {
|
|
83
|
+
meta: {
|
|
84
|
+
type: "suggestion",
|
|
85
|
+
docs: { description: "Report imports of other UI kits and icon sets; use @ux-builder/ui" },
|
|
86
|
+
schema: [{ type: "object", properties: { allow: { type: "array", items: { type: "string" } } }, additionalProperties: false }],
|
|
87
|
+
},
|
|
88
|
+
create(context) {
|
|
89
|
+
const allow = new Set(context.options[0]?.allow ?? []);
|
|
90
|
+
const check = (node, source) => {
|
|
91
|
+
if (typeof source !== "string" || allow.has(source)) return;
|
|
92
|
+
const hit = FOREIGN.find(([re]) => re.test(source));
|
|
93
|
+
if (hit) context.report({ node, message: `"${source}" is ${hit[1] === "an icon set" || hit[1] === "a loader library" ? hit[1] : `from ${hit[1]}`}, which does not follow the theme: use ${hit[2]} (ux-builder/no-foreign-ui)` });
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
ImportDeclaration: (n) => check(n, n.source.value),
|
|
97
|
+
ImportExpression: (n) => check(n, n.source.value),
|
|
98
|
+
CallExpression: (n) => { if (n.callee.name === "require" && n.arguments[0]?.type === "Literal") check(n, n.arguments[0].value); },
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/* ux-builder/no-inline-svg: an <svg> written in app JSX is an icon or a loader
|
|
104
|
+
drawn by hand, which keeps its shape whatever the theme's design system.
|
|
105
|
+
Use <Icon> (any of @ux-builder/ui/icons, or registerIcons for the app's own
|
|
106
|
+
drawing) or the loading widgets. Charts and illustrations can opt out with
|
|
107
|
+
allow: ["chart"] matching a className or id on the <svg>. */
|
|
108
|
+
const inlineSvg = {
|
|
109
|
+
meta: {
|
|
110
|
+
type: "suggestion",
|
|
111
|
+
docs: { description: "Report hand-drawn <svg> in JSX; use <Icon> or registerIcons" },
|
|
112
|
+
schema: [{ type: "object", properties: { allow: { type: "array", items: { type: "string" } } }, additionalProperties: false }],
|
|
113
|
+
},
|
|
114
|
+
create(context) {
|
|
115
|
+
const allow = context.options[0]?.allow ?? [];
|
|
116
|
+
const attr = (el, name) => el.attributes.find((a) => a.name?.name === name)?.value?.value;
|
|
117
|
+
return {
|
|
118
|
+
JSXOpeningElement(el) {
|
|
119
|
+
if (el.name.type !== "JSXIdentifier" || el.name.name !== "svg") return;
|
|
120
|
+
const ids = `${attr(el, "className") ?? ""} ${attr(el, "id") ?? ""}`;
|
|
121
|
+
if (allow.some((a) => ids.split(/\s+/).includes(a))) return;
|
|
122
|
+
context.report({ node: el, message: "a hand-drawn <svg> does not follow the theme's icon set: use <Icon> (import it from @ux-builder/ui/icons, or registerIcons({ name, glyphs }) for your own drawing) (ux-builder/no-inline-svg)" });
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export default { meta: { name: "@ux-builder/ui/eslint" }, rules: { "no-hard-coded-values": rule, "no-foreign-ui": foreign, "no-inline-svg": inlineSvg } };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* ux-builder/no-hard-coded-values for stylelint (FEAT-004 SDK-10, SDK-11).
|
|
2
|
+
|
|
3
|
+
// .stylelintrc.json
|
|
4
|
+
{ "plugins": ["@ux-builder/ui/stylelint"],
|
|
5
|
+
"rules": { "ux-builder/no-hard-coded-values": [true, {
|
|
6
|
+
"theme": "databricks", // suggest that theme's role tokens
|
|
7
|
+
"allow": [{ "value": "#4E79A7", "file": "src/charts/**", "reason": "chart series" }]
|
|
8
|
+
}] } }
|
|
9
|
+
|
|
10
|
+
`true` alone reports without suggestions. Every allow entry needs a reason. */
|
|
11
|
+
|
|
12
|
+
import stylelint from "stylelint";
|
|
13
|
+
import { RULE, check, checkAllow, loadTokens } from "./core.mjs";
|
|
14
|
+
|
|
15
|
+
const { createPlugin, utils } = stylelint;
|
|
16
|
+
const isObject = (v) => v && typeof v === "object" && !Array.isArray(v);
|
|
17
|
+
|
|
18
|
+
const rule = (primary, secondary = {}) => (root, result) => {
|
|
19
|
+
const valid = utils.validateOptions(result, RULE, { actual: primary, possible: [true, false] }, { actual: secondary, possible: (v) => isObject(v), optional: true });
|
|
20
|
+
if (!valid || !primary) return;
|
|
21
|
+
for (const e of checkAllow(secondary.allow ?? [])) utils.report({ ruleName: RULE, result, node: root, message: `${e} (${RULE})` });
|
|
22
|
+
const tokens = loadTokens(secondary.theme, "light", result.opts?.cwd ?? process.cwd());
|
|
23
|
+
const file = root.source?.input?.file;
|
|
24
|
+
root.walkDecls((decl) => {
|
|
25
|
+
const before = decl.prop.length + (decl.raws.between ?? ":").length;
|
|
26
|
+
for (const f of check(decl.prop, decl.value, { tokens, allow: secondary.allow ?? [], file })) {
|
|
27
|
+
utils.report({ ruleName: RULE, result, node: decl, message: f.message, index: before + f.index, endIndex: before + f.index + f.literal.length });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
rule.ruleName = RULE;
|
|
32
|
+
rule.messages = utils.ruleMessages(RULE, {});
|
|
33
|
+
rule.meta = { url: "https://github.com/DocumentDrivenDX/ux-builder#lint" };
|
|
34
|
+
|
|
35
|
+
export default createPlugin(RULE, rule);
|
package/package.json
CHANGED
|
@@ -1,6 +1,106 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ux-builder/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"license": "MIT OR Apache-2.0",
|
|
5
|
+
"description": "Widgets for the harness and embedded products: prefixed CSS and React 19 components on @ux-builder/tokens, shaped by whichever theme is on.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"licenses",
|
|
10
|
+
"lint",
|
|
11
|
+
"catalogue.json",
|
|
12
|
+
"AGENTS.md",
|
|
13
|
+
"skill",
|
|
14
|
+
"THIRD-PARTY-NOTICES.md"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./dist/index.js",
|
|
18
|
+
"./icons": "./dist/icons.js",
|
|
19
|
+
"./patterns": "./dist/patterns.js",
|
|
20
|
+
"./ui.css": "./dist/ui.css",
|
|
21
|
+
"./patterns.css": "./dist/patterns.css",
|
|
22
|
+
"./stylelint": "./lint/stylelint.mjs",
|
|
23
|
+
"./eslint": "./lint/eslint.mjs",
|
|
24
|
+
"./catalogue.json": "./catalogue.json",
|
|
25
|
+
"./AGENTS.md": "./AGENTS.md",
|
|
26
|
+
"./skill/*": "./skill/*",
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "node scripts/gen-icons.mjs --library && vite build && cp src/ui.css dist/ui.css && cp src/patterns.css dist/patterns.css",
|
|
31
|
+
"storybook": "storybook dev -p 6006",
|
|
32
|
+
"build-storybook": "storybook build -o storybook-static",
|
|
33
|
+
"test:storybook": "node --test 'test/storybook/*.test.mjs'",
|
|
34
|
+
"prepublishOnly": "npm run build",
|
|
35
|
+
"test": "node --test 'test/*.test.mjs'",
|
|
36
|
+
"icons": "node scripts/gen-icons.mjs"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@ux-builder/tokens": "^1.5.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"eslint": ">=9",
|
|
43
|
+
"react": "^19.0.0",
|
|
44
|
+
"react-dom": "^19.0.0",
|
|
45
|
+
"stylelint": ">=16"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@assistant-ui/react": "^0.15.29",
|
|
49
|
+
"@assistant-ui/react-markdown": "^0.14.23",
|
|
50
|
+
"@carbon/icons": "^11.89.0",
|
|
51
|
+
"@fluentui/svg-icons": "^1.1.343",
|
|
52
|
+
"@material-symbols/svg-400": "^0.47.6",
|
|
53
|
+
"@playwright/test": "~1.63.0",
|
|
54
|
+
"@radix-ui/react-use-controllable-state": "^1.3.0",
|
|
55
|
+
"@storybook/addon-a11y": "^10.6.1",
|
|
56
|
+
"@storybook/addon-docs": "^10.6.1",
|
|
57
|
+
"@storybook/react-vite": "^10.6.1",
|
|
58
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
59
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
60
|
+
"ai": "^7.0.145",
|
|
61
|
+
"class-variance-authority": "^0.7.1",
|
|
62
|
+
"clsx": "^2.1.1",
|
|
63
|
+
"cmdk": "^1.1.1",
|
|
64
|
+
"embla-carousel-react": "^8.6.0",
|
|
65
|
+
"eslint": "^10.12.0",
|
|
66
|
+
"happy-dom": "^20.14.5",
|
|
67
|
+
"lucide-react": "^1.59.0",
|
|
68
|
+
"lucide-static": "^1.52.0",
|
|
69
|
+
"motion": "^14.4.0",
|
|
70
|
+
"nanoid": "^3.3.19",
|
|
71
|
+
"radix-ui": "^1.8.1",
|
|
72
|
+
"react": "^19.3.0",
|
|
73
|
+
"react-dom": "^19.3.0",
|
|
74
|
+
"remark-gfm": "^4.0.1",
|
|
75
|
+
"shiki": "^4.5.0",
|
|
76
|
+
"storybook": "^10.6.1",
|
|
77
|
+
"streamdown": "^2.7.1",
|
|
78
|
+
"stylelint": "^17.16.0",
|
|
79
|
+
"tailwind-merge": "^3.7.0",
|
|
80
|
+
"tailwindcss": "^4.3.3",
|
|
81
|
+
"tw-animate-css": "^1.4.0",
|
|
82
|
+
"tw-shimmer": "^0.4.14",
|
|
83
|
+
"use-stick-to-bottom": "^1.1.6",
|
|
84
|
+
"vite": "^8.3.2",
|
|
85
|
+
"zustand": "^5.0.15"
|
|
86
|
+
},
|
|
87
|
+
"publishConfig": {
|
|
88
|
+
"access": "public"
|
|
89
|
+
},
|
|
90
|
+
"repository": {
|
|
91
|
+
"type": "git",
|
|
92
|
+
"url": "git+https://github.com/DocumentDrivenDX/ux-builder.git",
|
|
93
|
+
"directory": "packages/ui"
|
|
94
|
+
},
|
|
95
|
+
"peerDependenciesMeta": {
|
|
96
|
+
"stylelint": {
|
|
97
|
+
"optional": true
|
|
98
|
+
},
|
|
99
|
+
"eslint": {
|
|
100
|
+
"optional": true
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"sideEffects": [
|
|
104
|
+
"*.css"
|
|
105
|
+
]
|
|
106
|
+
}
|