@prongsjs/ui 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,288 @@
1
+ /**
2
+ * The theme the registry's classes read: colour tokens plus surfaces that layer themselves. `prongs add` writes
3
+ * `themeCss()` to theme.css; the values are the project's to change, the names are the
4
+ * contract the registry and `prongs check` rely on.
5
+ */
6
+ export const THEME_TOKENS = [
7
+ { name: '--background', light: 'oklch(1 0 0)', dark: 'oklch(0.145 0 0)', description: 'The page. Surface level 0.', utility: 'background' },
8
+ { name: '--foreground', light: 'oklch(0.145 0 0)', dark: 'oklch(0.985 0 0)', description: 'Main text. Surfaces mix towards it as they rise.', utility: 'foreground' },
9
+ { name: '--card', light: 'oklch(1 0 0)', dark: 'oklch(0.205 0 0)', description: 'Card background, for components outside Prongs. Prongs looks use surfaces instead.', utility: 'card' },
10
+ { name: '--card-foreground', light: 'oklch(0.145 0 0)', dark: 'oklch(0.985 0 0)', description: 'Text on `--card`.', utility: 'card-foreground' },
11
+ { name: '--popover', light: 'oklch(1 0 0)', dark: 'oklch(0.205 0 0)', description: 'Popover background, for components outside Prongs.', utility: 'popover' },
12
+ { name: '--popover-foreground', light: 'oklch(0.145 0 0)', dark: 'oklch(0.985 0 0)', description: 'Text on `--popover`.', utility: 'popover-foreground' },
13
+ { name: '--primary', light: 'oklch(0.205 0 0)', dark: 'oklch(0.922 0 0)', description: 'The main action and the accent colour.', utility: 'primary' },
14
+ { name: '--primary-foreground', light: 'oklch(0.985 0 0)', dark: 'oklch(0.205 0 0)', description: 'Text on `--primary`.', utility: 'primary-foreground' },
15
+ { name: '--secondary', light: 'oklch(0.97 0 0)', dark: 'oklch(0.269 0 0)', description: 'Quieter filled controls.', utility: 'secondary' },
16
+ { name: '--secondary-foreground', light: 'oklch(0.205 0 0)', dark: 'oklch(0.985 0 0)', description: 'Text on `--secondary`.', utility: 'secondary-foreground' },
17
+ { name: '--muted', light: 'oklch(0.97 0 0)', dark: 'oklch(0.269 0 0)', description: 'Tinted backgrounds that do not layer.', utility: 'muted' },
18
+ { name: '--muted-foreground', light: 'oklch(0.556 0 0)', dark: 'oklch(0.708 0 0)', description: 'Quiet text on the page. On surfaces use `--surface-muted`.', utility: 'muted-foreground' },
19
+ { name: '--accent', light: 'oklch(0.97 0 0)', dark: 'oklch(0.269 0 0)', description: 'Hover and selection tint.', utility: 'accent' },
20
+ { name: '--accent-foreground', light: 'oklch(0.205 0 0)', dark: 'oklch(0.985 0 0)', description: 'Text on `--accent`.', utility: 'accent-foreground' },
21
+ { name: '--destructive', light: 'oklch(0.505 0.213 27.518)', dark: 'oklch(0.808 0.114 19.571)', description: 'Danger: deleting, errors.', utility: 'destructive' },
22
+ { name: '--destructive-foreground', light: 'oklch(0.985 0 0)', dark: 'oklch(0.145 0 0)', description: 'Text on `--destructive`.', utility: 'destructive-foreground' },
23
+ { name: '--success', light: 'oklch(0.448 0.119 151.328)', dark: 'oklch(0.792 0.209 151.711)', description: 'Success text.', utility: 'success' },
24
+ { name: '--warning', light: 'oklch(0.473 0.137 46.201)', dark: 'oklch(0.828 0.189 84.429)', description: 'Warning text.', utility: 'warning' },
25
+ { name: '--info', light: 'oklch(0.443 0.11 240.79)', dark: 'oklch(0.746 0.16 232.661)', description: 'Neutral notice text.', utility: 'info' },
26
+ { name: '--border', light: 'oklch(0.922 0 0)', dark: 'oklch(1 0 0 / 10%)', description: 'Hairlines on the page.', utility: 'border' },
27
+ { name: '--input', light: 'oklch(0.922 0 0)', dark: 'oklch(1 0 0 / 15%)', description: 'Field borders.', utility: 'input' },
28
+ { name: '--ring', light: 'oklch(0.708 0 0)', dark: 'oklch(0.556 0 0)', description: 'Focus rings.', utility: 'ring' },
29
+ { name: '--radius', light: '0.5rem', dark: '0.5rem', description: 'Corner radius. `rounded-sm` to `rounded-xl` derive from it; 0.5rem keeps Tailwind\'s own sizes.' },
30
+ ];
31
+ export const SURFACE_SETTINGS = {
32
+ light: { step: 4, overlay: 0, muted: 60, border: 14 },
33
+ dark: { step: 5, overlay: 2, muted: 60, border: 16 },
34
+ };
35
+ /** Levels a nested surface can reach; deeper nesting stays at the last one. */
36
+ export const SURFACE_DEPTH = 4;
37
+ /** The variables a surface sets for whatever is inside it. */
38
+ export const SURFACE_TOKENS = [
39
+ { name: '--surface-base', light: '', dark: '', description: 'The background of the surface you are on.', utility: 'surface' },
40
+ { name: '--surface-hover', light: '', dark: '', description: 'One level up from it: hover fills.', utility: 'surface-hover' },
41
+ { name: '--surface-active', light: '', dark: '', description: 'Two levels up: pressed and selected fills.', utility: 'surface-active' },
42
+ { name: '--surface-inset', light: '', dark: '', description: 'A recess cut into it: wells, code, skeletons.', utility: 'surface-inset' },
43
+ { name: '--surface-fg', light: '', dark: '', description: 'Text on it.', utility: 'surface-fg' },
44
+ { name: '--surface-muted', light: '', dark: '', description: 'Quiet text on it, kept readable at every level.', utility: 'surface-muted' },
45
+ { name: '--surface-border', light: '', dark: '', description: 'A hairline that shows on it.', utility: 'surface-border' },
46
+ ];
47
+ /** The classes that make an element a surface. */
48
+ export const SURFACE_CLASSES = [
49
+ { name: 'surface', description: 'A raised surface: one level above the surface it sits in.' },
50
+ { name: 'surface-inset', description: 'A recess: one level back from the surface it sits in (forward on the page itself).' },
51
+ { name: 'surface-overlay', description: 'Floats over the page (dialogs, menus): starts its own stack, whatever it is nested in.' },
52
+ { name: 'surface-page', description: 'An element that sets its own --background or --foreground (a themed section, a preview): starts again at level 0 with its colours. It does not paint itself.' },
53
+ ];
54
+ /** Every name `theme.css` declares, so `prongs check` can tell a typo from a token. */
55
+ export const THEME_VARIABLES = [
56
+ ...THEME_TOKENS.map((t) => t.name),
57
+ ...SURFACE_TOKENS.map((t) => t.name),
58
+ '--surface-level',
59
+ '--surface-step',
60
+ '--surface-overlay-level',
61
+ '--surface-muted-mix',
62
+ '--surface-border-mix',
63
+ '--surface-inset-level',
64
+ ];
65
+ const mix = (level) => `color-mix(in oklch, var(--foreground) calc(var(--surface-step) * (${level})), var(--background))`;
66
+ /** The CSS `prongs add` writes to theme.css. */
67
+ export function themeCss() {
68
+ const tokens = (mode) => THEME_TOKENS.map((t) => ` ${t.name}: ${t[mode]};`);
69
+ const settings = (mode) => {
70
+ const s = SURFACE_SETTINGS[mode];
71
+ return [
72
+ ` --surface-step: ${s.step}%;`,
73
+ ` --surface-overlay-level: ${s.overlay};`,
74
+ ` --surface-muted-mix: ${s.muted}%;`,
75
+ ` --surface-border-mix: ${s.border}%;`,
76
+ ];
77
+ };
78
+ const depth = Array.from({ length: SURFACE_DEPTH }, (_, i) => {
79
+ const selector = Array(i + 1).fill('.surface').join(' ');
80
+ return `${selector} { --surface-level: ${i + 1}; }`;
81
+ });
82
+ // Repeating a class outranks any nesting depth above it, so a page or an overlay restarts
83
+ // the count wherever it is rendered. Overlays repeat once more, so a dialog opened inside a
84
+ // themed page still floats.
85
+ const page = '.surface-page'.repeat(SURFACE_DEPTH + 1);
86
+ const pageDepth = Array.from({ length: SURFACE_DEPTH }, (_, i) => {
87
+ const selector = [page, ...Array(i + 1).fill('.surface')].join(' ');
88
+ return `${selector} { --surface-level: ${i + 1}; }`;
89
+ });
90
+ const overlay = '.surface-overlay'.repeat(SURFACE_DEPTH + 2);
91
+ const overlayDepth = Array.from({ length: SURFACE_DEPTH - 1 }, (_, i) => {
92
+ const selector = [overlay, ...Array(i + 1).fill('.surface')].join(' ');
93
+ return `${selector} { --surface-level: calc(var(--surface-overlay-level) + ${i + 1}); }`;
94
+ });
95
+ const colors = [...THEME_TOKENS, ...SURFACE_TOKENS]
96
+ .filter((t) => t.utility)
97
+ .map((t) => ` --color-${t.utility}: var(${t.name});`);
98
+ return `/*
99
+ * Prongs theme. Written by \`prongs add\`.
100
+ *
101
+ * The names are the contract: the registry's classes use them, and \`prongs check\` reports
102
+ * a var() that is not declared anywhere. The values are yours to change, here or in your
103
+ * own CSS after this import (later rules win).
104
+ *
105
+ * Surfaces layer themselves. Give a box the class \`surface\` and it sits one level above
106
+ * whatever surface it is inside; \`surface-inset\` is a recess, \`surface-overlay\` floats
107
+ * over the page and starts its own stack. Put \`surface-page\` on an element that sets its own
108
+ * --background or --foreground (a themed section, a preview): it starts again at level 0. Every level is mixed from --background towards
109
+ * --foreground, so a theme only sets those two (and --surface-step) to get all of them,
110
+ * in light and dark. Inside a surface, use the surface colours, not fixed ones:
111
+ * bg-surface-hover hover fill, one level up from wherever it sits
112
+ * text-surface-muted quiet text, readable at every level
113
+ * border-surface-border a hairline that shows on this level
114
+ */
115
+
116
+ :root {
117
+ /* Browser-drawn parts (validation bubbles, scrollbars, date pickers) follow the theme too. */
118
+ color-scheme: light;
119
+ ${tokens('light').join('\n')}
120
+
121
+ ${settings('light').join('\n')}
122
+ --surface-level: 0;
123
+ }
124
+
125
+ /* Dark follows the system. class="dark" on <html> forces it; class="light" keeps it light. */
126
+ .dark {
127
+ color-scheme: dark;
128
+ ${tokens('dark').join('\n')}
129
+
130
+ ${settings('dark').join('\n')}
131
+ }
132
+
133
+ /* :where() keeps this as specific as :root, so your own :root values after the import still win. */
134
+ @media (prefers-color-scheme: dark) {
135
+ :root:where(:not(.light)) {
136
+ color-scheme: dark;
137
+ ${tokens('dark').map((line) => ' ' + line).join('\n')}
138
+
139
+ ${settings('dark').map((line) => ' ' + line).join('\n')}
140
+ }
141
+ }
142
+
143
+ /* ── Surfaces ───────────────────────────────────────────────────────────── */
144
+
145
+ /* Depth: how many surfaces an element sits in. */
146
+ ${depth.join('\n')}
147
+ ${page} { --surface-level: 0; }
148
+ ${pageDepth.join('\n')}
149
+ ${overlay} { --surface-level: var(--surface-overlay-level); }
150
+ ${overlayDepth.join('\n')}
151
+ .surface-inset { --surface-level: var(--surface-inset-level); }
152
+
153
+ /* Colours, recomputed wherever the level or the theme changes. */
154
+ :root, .dark, .surface-page, .surface, .surface-overlay, .surface-inset {
155
+ --surface-base: ${mix('var(--surface-level)')};
156
+ --surface-hover: ${mix('var(--surface-level) + 1')};
157
+ --surface-active: ${mix('var(--surface-level) + 2')};
158
+ --surface-fg: var(--foreground);
159
+ --surface-muted: color-mix(in oklch, var(--foreground) var(--surface-muted-mix), var(--surface-base));
160
+ --surface-border: color-mix(in oklch, var(--foreground) var(--surface-border-mix), var(--surface-base));
161
+ }
162
+
163
+ /* The recess for what is inside: one level back, or forward on the page itself. */
164
+ :root, .dark, .surface-page, .surface, .surface-overlay {
165
+ --surface-inset-level: max(var(--surface-level) - 1, 1 - var(--surface-level));
166
+ --surface-inset: ${mix('var(--surface-inset-level)')};
167
+ }
168
+
169
+ /* In the base layer, so a utility on the same element (hover:bg-surface-hover) still wins. */
170
+ @layer base {
171
+ .surface, .surface-overlay, .surface-inset {
172
+ background-color: var(--surface-base);
173
+ color: var(--surface-fg);
174
+ }
175
+ }
176
+
177
+ /* ── Tailwind ───────────────────────────────────────────────────────────── */
178
+
179
+ @theme inline {
180
+ ${colors.join('\n')}
181
+ --radius-sm: calc(var(--radius) - 4px);
182
+ --radius-md: calc(var(--radius) - 2px);
183
+ --radius-lg: var(--radius);
184
+ --radius-xl: calc(var(--radius) + 4px);
185
+ }
186
+ `;
187
+ }
188
+ /** Parses `oklch(L C H)` (L as 0–1 or a percentage) or `#rrggbb`. Alpha is not supported. */
189
+ export function parseColor(value) {
190
+ const v = value.trim();
191
+ const ok = /^oklch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)\s*\)$/.exec(v);
192
+ if (ok)
193
+ return { l: Number(ok[1]) / (ok[2] ? 100 : 1), c: Number(ok[3]), h: Number(ok[4]) };
194
+ const hex = /^#([0-9a-f]{6})$/i.exec(v);
195
+ if (hex) {
196
+ const n = parseInt(hex[1], 16);
197
+ return rgbToOklch([(n >> 16) & 255, (n >> 8) & 255, n & 255].map((x) => x / 255));
198
+ }
199
+ return undefined;
200
+ }
201
+ const toLinear = (x) => (x <= 0.04045 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4);
202
+ function rgbToOklch([r, g, b]) {
203
+ const [lr, lg, lb] = [r, g, b].map(toLinear);
204
+ const l = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
205
+ const m = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
206
+ const s = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);
207
+ const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
208
+ const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
209
+ const bb = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
210
+ const c = Math.hypot(a, bb);
211
+ return { l: L, c: c < 1e-4 ? 0 : c, h: c < 1e-4 ? 0 : ((Math.atan2(bb, a) * 180) / Math.PI + 360) % 360 };
212
+ }
213
+ /** Relative luminance (WCAG), clamped to sRGB. */
214
+ function luminance({ l, c, h }) {
215
+ const a = c * Math.cos((h * Math.PI) / 180);
216
+ const b = c * Math.sin((h * Math.PI) / 180);
217
+ const l_ = (l + 0.3963377774 * a + 0.2158037573 * b) ** 3;
218
+ const m_ = (l - 0.1055613458 * a - 0.0638541728 * b) ** 3;
219
+ const s_ = (l - 0.0894841775 * a - 1.291485548 * b) ** 3;
220
+ const clamp = (x) => Math.min(1, Math.max(0, x));
221
+ const r = clamp(4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_);
222
+ const g = clamp(-1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_);
223
+ const bl = clamp(-0.0041960863 * l_ - 0.7034186147 * m_ + 1.707614701 * s_);
224
+ return 0.2126 * r + 0.7152 * g + 0.0722 * bl;
225
+ }
226
+ /** color-mix(in oklch, a p, b): `p` is the share of `a`, 0–1. A grey takes the other colour's hue. */
227
+ export function mixOklch(a, b, p) {
228
+ let ha = a.h;
229
+ let hb = b.h;
230
+ if (a.c === 0)
231
+ ha = hb;
232
+ if (b.c === 0)
233
+ hb = ha;
234
+ let dh = hb - ha;
235
+ if (dh > 180)
236
+ dh -= 360;
237
+ if (dh < -180)
238
+ dh += 360;
239
+ return { l: a.l * p + b.l * (1 - p), c: a.c * p + b.c * (1 - p), h: (((hb - dh * p) % 360) + 360) % 360 };
240
+ }
241
+ export function contrast(a, b) {
242
+ const [x, y] = [luminance(a), luminance(b)].sort((m, n) => n - m);
243
+ return (x + 0.05) / (y + 0.05);
244
+ }
245
+ /** Every level the CSS can produce for a background and foreground, with its contrasts. */
246
+ export function surfaceLevels(colors, settings) {
247
+ const bg = parseColor(colors.background);
248
+ const fg = parseColor(colors.foreground);
249
+ if (!bg || !fg)
250
+ throw new Error(`surfaceLevels needs opaque oklch() or #rrggbb colours, got ${colors.background} and ${colors.foreground}`);
251
+ const at = (level) => mixOklch(fg, bg, Math.min(1, (settings.step * level) / 100));
252
+ const row = (name, level) => {
253
+ const base = at(level);
254
+ return {
255
+ name,
256
+ level,
257
+ background: base,
258
+ text: contrast(fg, base),
259
+ muted: contrast(mixOklch(fg, base, settings.muted / 100), base),
260
+ border: contrast(mixOklch(fg, base, settings.border / 100), base),
261
+ hover: contrast(at(level + 1), base),
262
+ };
263
+ };
264
+ const page = Array.from({ length: SURFACE_DEPTH + 1 }, (_, i) => row(i === 0 ? 'page' : String(i), i));
265
+ const overlay = Array.from({ length: SURFACE_DEPTH }, (_, i) => row(i === 0 ? 'overlay' : `overlay ${i}`, settings.overlay + i));
266
+ return [...page, ...overlay];
267
+ }
268
+ /** `surfaceLevels` for the default theme. */
269
+ export function defaultSurfaceLevels(mode) {
270
+ const value = (name) => THEME_TOKENS.find((t) => t.name === name)[mode];
271
+ return surfaceLevels({ background: value('--background'), foreground: value('--foreground') }, SURFACE_SETTINGS[mode]);
272
+ }
273
+ /** Contrast of a default token pair, e.g. `--primary-foreground` on `--primary`. */
274
+ export function tokenContrast(mode, fg, bg) {
275
+ const value = (name) => parseColor(THEME_TOKENS.find((t) => t.name === name)[mode]);
276
+ const a = value(fg);
277
+ const b = value(bg);
278
+ if (!a || !b)
279
+ throw new Error(`tokenContrast needs opaque colours: ${fg}, ${bg}`);
280
+ return contrast(a, b);
281
+ }
282
+ /** Contrast of a default token's text on every level of the default surfaces. */
283
+ export function tokenOnSurfaces(mode, token) {
284
+ const color = parseColor(THEME_TOKENS.find((t) => t.name === token)[mode]);
285
+ if (!color)
286
+ throw new Error(`tokenOnSurfaces needs an opaque colour: ${token}`);
287
+ return defaultSurfaceLevels(mode).map((level) => contrast(color, level.background));
288
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Two layers. Call sites choose a MEANING intent by what the element does
3
+ * (`action.delete`); meaning intents refer to LOOK intents (`control.destructive`)
4
+ * for their styling. Look intents never appear at a call site.
5
+ */
6
+ export declare const MEANING_DOMAINS: readonly ["heading", "text", "feedback", "action", "nav", "choice", "field", "layout", "media", "dialog", "popover"];
7
+ export declare const LOOK_DOMAINS: readonly ["typography", "tone", "surface", "control", "input", "arrange", "constraint", "frame"];
8
+ export type MeaningDomain = (typeof MEANING_DOMAINS)[number];
9
+ export type LookDomain = (typeof LOOK_DOMAINS)[number];
10
+ /** Which component renders each meaning domain. */
11
+ export declare const COMPONENT_DOMAINS: {
12
+ readonly Text: readonly ["heading", "text", "feedback"];
13
+ readonly Action: readonly ["action", "nav", "choice"];
14
+ readonly Field: readonly ["field"];
15
+ readonly Layout: readonly ["layout"];
16
+ readonly Media: readonly ["media"];
17
+ readonly Dialog: readonly ["dialog"];
18
+ readonly Popover: readonly ["popover"];
19
+ };
20
+ export type ComponentName = keyof typeof COMPONENT_DOMAINS;
21
+ export declare const COMPONENT_NAMES: ComponentName[];
22
+ /** Elements an intent may choose through `binding.element`, per component. */
23
+ export declare const ALLOWED_ELEMENTS: Record<ComponentName, readonly string[]>;
24
+ /**
25
+ * Words that describe how something looks or what widget it is, not what it is for.
26
+ * Meaning intents may not use them: an agent should pick an intent from facts in the
27
+ * code ("this handler shares the article"), never from a visual judgement ("this should
28
+ * be a secondary pill"). Look intents (typography.*, control.*, ...) may use any words.
29
+ */
30
+ export declare const LOOK_WORDS: readonly string[];
31
+ export declare function componentFor(id: string): ComponentName | undefined;
32
+ export declare function isLookDomain(domain: string): domain is LookDomain;
@@ -0,0 +1,57 @@
1
+ import { domainOf } from '@prongsjs/core';
2
+ /**
3
+ * Two layers. Call sites choose a MEANING intent by what the element does
4
+ * (`action.delete`); meaning intents refer to LOOK intents (`control.destructive`)
5
+ * for their styling. Look intents never appear at a call site.
6
+ */
7
+ export const MEANING_DOMAINS = ['heading', 'text', 'feedback', 'action', 'nav', 'choice', 'field', 'layout', 'media', 'dialog', 'popover'];
8
+ export const LOOK_DOMAINS = ['typography', 'tone', 'surface', 'control', 'input', 'arrange', 'constraint', 'frame'];
9
+ /** Which component renders each meaning domain. */
10
+ export const COMPONENT_DOMAINS = {
11
+ Text: ['heading', 'text', 'feedback'],
12
+ Action: ['action', 'nav', 'choice'],
13
+ Field: ['field'],
14
+ Layout: ['layout'],
15
+ Media: ['media'],
16
+ Dialog: ['dialog'],
17
+ Popover: ['popover'],
18
+ };
19
+ export const COMPONENT_NAMES = Object.keys(COMPONENT_DOMAINS);
20
+ /** Elements an intent may choose through `binding.element`, per component. */
21
+ export const ALLOWED_ELEMENTS = {
22
+ Text: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'div', 'label', 'strong', 'em', 'small', 'blockquote', 'code', 'pre', 'summary'],
23
+ Action: [],
24
+ Field: ['input', 'textarea', 'select'],
25
+ Layout: ['div', 'main', 'section', 'article', 'aside', 'header', 'footer', 'nav', 'form', 'fieldset', 'ul', 'ol', 'li', 'hr', 'details', 'table', 'figure'],
26
+ /** Images by default; an intent can make it a frame that shows another page. */
27
+ Media: ['iframe'],
28
+ Dialog: [],
29
+ Popover: [],
30
+ };
31
+ /**
32
+ * Words that describe how something looks or what widget it is, not what it is for.
33
+ * Meaning intents may not use them: an agent should pick an intent from facts in the
34
+ * code ("this handler shares the article"), never from a visual judgement ("this should
35
+ * be a secondary pill"). Look intents (typography.*, control.*, ...) may use any words.
36
+ */
37
+ export const LOOK_WORDS = [
38
+ // emphasis and hierarchy
39
+ 'primary', 'secondary', 'tertiary', 'ghost', 'outline', 'outlined', 'solid', 'filled', 'subtle', 'muted',
40
+ 'loud', 'quiet', 'bold', 'light', 'dark', 'plain', 'fancy', 'accent', 'highlight', 'emphasis',
41
+ // size and density
42
+ 'big', 'large', 'medium', 'small', 'tiny', 'mini', 'compact', 'dense', 'prominent', 'wide', 'narrow', 'tall', 'short',
43
+ // widgets and shapes
44
+ 'hero', 'banner', 'badge', 'chip', 'pill', 'card', 'tile', 'swatch', 'segment', 'segmented', 'tab', 'tabs',
45
+ 'button', 'link', 'icon', 'box', 'bubble', 'container', 'wrapper',
46
+ // arrangement
47
+ 'centered', 'center', 'left', 'right', 'top', 'bottom', 'row', 'column', 'stack', 'grid', 'flex', 'inline',
48
+ // colours
49
+ 'red', 'blue', 'green', 'yellow', 'orange', 'purple', 'pink', 'gray', 'grey', 'black', 'white',
50
+ ];
51
+ export function componentFor(id) {
52
+ const domain = domainOf(id);
53
+ return COMPONENT_NAMES.find((name) => COMPONENT_DOMAINS[name].includes(domain));
54
+ }
55
+ export function isLookDomain(domain) {
56
+ return LOOK_DOMAINS.includes(domain);
57
+ }
package/package.json CHANGED
@@ -1,6 +1,74 @@
1
1
  {
2
2
  "name": "@prongsjs/ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "The Prongs UI profile: intents that resolve to Tailwind classes, elements and ARIA roles, plus the tooling that checks how they are used.",
5
+ "keywords": [
6
+ "intents",
7
+ "design-system",
8
+ "tailwind",
9
+ "react",
10
+ "ai-agents",
11
+ "accessibility"
12
+ ],
13
+ "homepage": "https://github.com/drewpoling2/prongs#readme",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/drewpoling2/prongs.git",
17
+ "directory": "packages/ui"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/drewpoling2/prongs/issues"
21
+ },
22
+ "author": "Drew Poling",
23
+ "license": "MIT",
24
+ "type": "module",
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ },
32
+ "./tooling": {
33
+ "types": "./dist/tooling/index.d.ts",
34
+ "import": "./dist/tooling/index.js"
35
+ },
36
+ "./spec": {
37
+ "types": "./dist/tooling/spec.d.ts",
38
+ "import": "./dist/tooling/spec.js"
39
+ },
40
+ "./theme": {
41
+ "types": "./dist/tooling/theme.d.ts",
42
+ "import": "./dist/tooling/theme.js"
43
+ }
44
+ },
45
+ "sideEffects": false,
46
+ "files": [
47
+ "dist"
48
+ ],
49
+ "scripts": {
50
+ "build": "rm -rf dist && tsc -p tsconfig.json",
51
+ "test": "vitest run",
52
+ "prepublishOnly": "npm run build"
53
+ },
54
+ "dependencies": {
55
+ "@prongsjs/core": "0.2.0"
56
+ },
57
+ "peerDependencies": {
58
+ "tailwind-merge": "^2.5.0 || ^3.0.0",
59
+ "typescript": ">=5.0.0"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "typescript": {
63
+ "optional": true
64
+ }
65
+ },
66
+ "devDependencies": {
67
+ "tailwind-merge": "^2.6.0",
68
+ "typescript": "^5.6.0",
69
+ "vitest": "^2.1.0"
70
+ },
71
+ "publishConfig": {
72
+ "access": "public"
73
+ }
74
+ }