@forumone/throughline-design-contract 0.0.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +162 -28
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/lint.d.ts +38 -0
- package/dist/lint.d.ts.map +1 -0
- package/dist/lint.js +110 -0
- package/dist/lint.js.map +1 -0
- package/dist/loader.d.ts +49 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +95 -0
- package/dist/loader.js.map +1 -0
- package/dist/manifest.d.ts +454 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +29 -0
- package/dist/manifest.js.map +1 -0
- package/dist/schema.d.ts +330 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +170 -0
- package/dist/schema.js.map +1 -0
- package/package.json +58 -7
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* The current version of the contract schema. Bump this when making a
|
|
4
|
+
* backwards-incompatible change to the shape. Manifests declare the
|
|
5
|
+
* version they satisfy so the loader can reject mismatches loudly.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CONTRACT_VERSION = "1.0.0";
|
|
8
|
+
declare const FieldTypeSchema: z.ZodEnum<["text", "richtext", "link", "image", "video", "select", "group", "array", "boolean", "number"]>;
|
|
9
|
+
declare const PlacementSchema: z.ZodEnum<["page", "section", "inline"]>;
|
|
10
|
+
declare const CategorySchema: z.ZodEnum<["hero", "section", "card", "media", "cta", "navigation", "data", "form", "utility"]>;
|
|
11
|
+
/**
|
|
12
|
+
* Where an editor looks for a component, as distinct from what the component
|
|
13
|
+
* *is*.
|
|
14
|
+
*
|
|
15
|
+
* `category` answers the second question and several consumers reason about it
|
|
16
|
+
* as a kind. It is a poor answer to the first: a real design system files
|
|
17
|
+
* roughly half its library under `section`, so a picker grouped on `category`
|
|
18
|
+
* hands back the flat list the grouping was meant to avoid, while `card` and
|
|
19
|
+
* `navigation` hold one entry each. Redistributing within `category` to even
|
|
20
|
+
* the shelves out would file components under the wrong kind for every other
|
|
21
|
+
* consumer.
|
|
22
|
+
*
|
|
23
|
+
* So this is a second, optional field. The values are shelf labels, chosen to
|
|
24
|
+
* split the sections a `category` cannot:
|
|
25
|
+
*
|
|
26
|
+
* - `hero` — page openers
|
|
27
|
+
* - `narrative` — sections that explain, walk through, or tell
|
|
28
|
+
* - `proof` — sections that evidence a claim: testimony, clients, results
|
|
29
|
+
* - `listing` — collections of records, usually repeating
|
|
30
|
+
* - `media` — image, video, audio, and their captions
|
|
31
|
+
* - `form` — anything an editor thinks of as a form
|
|
32
|
+
* - `cta` — asks
|
|
33
|
+
* - `navigation` — wayfinding within or across pages
|
|
34
|
+
* - `utility` — structural or incidental
|
|
35
|
+
*
|
|
36
|
+
* There is deliberately no `section`: a shelf that holds half the library is
|
|
37
|
+
* the problem this field exists to solve. There is no `card` or `data` either
|
|
38
|
+
* — both name a kind rather than a place to look, and their contents belong on
|
|
39
|
+
* `listing` and `proof` respectively.
|
|
40
|
+
*/
|
|
41
|
+
declare const GroupSchema: z.ZodEnum<["hero", "narrative", "proof", "listing", "media", "form", "cta", "navigation", "utility"]>;
|
|
42
|
+
export type ContentField = {
|
|
43
|
+
name: string;
|
|
44
|
+
type: z.infer<typeof FieldTypeSchema>;
|
|
45
|
+
required: boolean;
|
|
46
|
+
maxLength?: number | undefined;
|
|
47
|
+
constraints?: string | undefined;
|
|
48
|
+
of?: ContentField[] | undefined;
|
|
49
|
+
};
|
|
50
|
+
/** Input shape for {@link ContentFieldSchema}: defaulted fields are optional. */
|
|
51
|
+
export type ContentFieldInput = {
|
|
52
|
+
name: string;
|
|
53
|
+
type: z.infer<typeof FieldTypeSchema>;
|
|
54
|
+
required?: boolean | undefined;
|
|
55
|
+
maxLength?: number | undefined;
|
|
56
|
+
constraints?: string | undefined;
|
|
57
|
+
of?: ContentFieldInput[] | undefined;
|
|
58
|
+
};
|
|
59
|
+
export declare const ComponentContractSchema: z.ZodObject<{
|
|
60
|
+
name: z.ZodString;
|
|
61
|
+
category: z.ZodEnum<["hero", "section", "card", "media", "cta", "navigation", "data", "form", "utility"]>;
|
|
62
|
+
/**
|
|
63
|
+
* The shelf an authoring UI files this component under. Optional: when it is
|
|
64
|
+
* absent, {@link groupOf} falls back to {@link category}, so a design system
|
|
65
|
+
* that sets none groups exactly as it did before this field existed.
|
|
66
|
+
*/
|
|
67
|
+
group: z.ZodOptional<z.ZodEnum<["hero", "narrative", "proof", "listing", "media", "form", "cta", "navigation", "utility"]>>;
|
|
68
|
+
description: z.ZodString;
|
|
69
|
+
intent: z.ZodString;
|
|
70
|
+
composition: z.ZodObject<{
|
|
71
|
+
placement: z.ZodArray<z.ZodEnum<["page", "section", "inline"]>, "atleastone">;
|
|
72
|
+
maxPerPage: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
73
|
+
requiredSiblings: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
74
|
+
forbiddenAdjacent: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
75
|
+
allowedSlots: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
|
|
76
|
+
}, "strip", z.ZodTypeAny, {
|
|
77
|
+
placement: ["page" | "section" | "inline", ...("page" | "section" | "inline")[]];
|
|
78
|
+
maxPerPage: number | null;
|
|
79
|
+
requiredSiblings: string[];
|
|
80
|
+
forbiddenAdjacent: string[];
|
|
81
|
+
allowedSlots?: Record<string, string[]> | undefined;
|
|
82
|
+
}, {
|
|
83
|
+
placement: ["page" | "section" | "inline", ...("page" | "section" | "inline")[]];
|
|
84
|
+
maxPerPage?: number | null | undefined;
|
|
85
|
+
requiredSiblings?: string[] | undefined;
|
|
86
|
+
forbiddenAdjacent?: string[] | undefined;
|
|
87
|
+
allowedSlots?: Record<string, string[]> | undefined;
|
|
88
|
+
}>;
|
|
89
|
+
content: z.ZodObject<{
|
|
90
|
+
fields: z.ZodArray<z.ZodType<ContentField, z.ZodTypeDef, ContentFieldInput>, "many">;
|
|
91
|
+
variants: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
92
|
+
name: z.ZodString;
|
|
93
|
+
description: z.ZodString;
|
|
94
|
+
whenToUse: z.ZodString;
|
|
95
|
+
}, "strip", z.ZodTypeAny, {
|
|
96
|
+
name: string;
|
|
97
|
+
description: string;
|
|
98
|
+
whenToUse: string;
|
|
99
|
+
}, {
|
|
100
|
+
name: string;
|
|
101
|
+
description: string;
|
|
102
|
+
whenToUse: string;
|
|
103
|
+
}>, "many">>;
|
|
104
|
+
}, "strip", z.ZodTypeAny, {
|
|
105
|
+
fields: ContentField[];
|
|
106
|
+
variants?: {
|
|
107
|
+
name: string;
|
|
108
|
+
description: string;
|
|
109
|
+
whenToUse: string;
|
|
110
|
+
}[] | undefined;
|
|
111
|
+
}, {
|
|
112
|
+
fields: ContentFieldInput[];
|
|
113
|
+
variants?: {
|
|
114
|
+
name: string;
|
|
115
|
+
description: string;
|
|
116
|
+
whenToUse: string;
|
|
117
|
+
}[] | undefined;
|
|
118
|
+
}>;
|
|
119
|
+
tokens: z.ZodObject<{
|
|
120
|
+
consumes: z.ZodArray<z.ZodString, "many">;
|
|
121
|
+
configurable: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
122
|
+
prop: z.ZodString;
|
|
123
|
+
tokenGroup: z.ZodString;
|
|
124
|
+
allowedValues: z.ZodArray<z.ZodString, "many">;
|
|
125
|
+
}, "strip", z.ZodTypeAny, {
|
|
126
|
+
prop: string;
|
|
127
|
+
tokenGroup: string;
|
|
128
|
+
allowedValues: string[];
|
|
129
|
+
}, {
|
|
130
|
+
prop: string;
|
|
131
|
+
tokenGroup: string;
|
|
132
|
+
allowedValues: string[];
|
|
133
|
+
}>, "many">>;
|
|
134
|
+
}, "strip", z.ZodTypeAny, {
|
|
135
|
+
consumes: string[];
|
|
136
|
+
configurable?: {
|
|
137
|
+
prop: string;
|
|
138
|
+
tokenGroup: string;
|
|
139
|
+
allowedValues: string[];
|
|
140
|
+
}[] | undefined;
|
|
141
|
+
}, {
|
|
142
|
+
consumes: string[];
|
|
143
|
+
configurable?: {
|
|
144
|
+
prop: string;
|
|
145
|
+
tokenGroup: string;
|
|
146
|
+
allowedValues: string[];
|
|
147
|
+
}[] | undefined;
|
|
148
|
+
}>;
|
|
149
|
+
accessibility: z.ZodObject<{
|
|
150
|
+
role: z.ZodOptional<z.ZodString>;
|
|
151
|
+
keyboardSupport: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
152
|
+
screenReaderBehavior: z.ZodString;
|
|
153
|
+
contentWarnings: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
154
|
+
}, "strip", z.ZodTypeAny, {
|
|
155
|
+
keyboardSupport: string[];
|
|
156
|
+
screenReaderBehavior: string;
|
|
157
|
+
contentWarnings: string[];
|
|
158
|
+
role?: string | undefined;
|
|
159
|
+
}, {
|
|
160
|
+
screenReaderBehavior: string;
|
|
161
|
+
role?: string | undefined;
|
|
162
|
+
keyboardSupport?: string[] | undefined;
|
|
163
|
+
contentWarnings?: string[] | undefined;
|
|
164
|
+
}>;
|
|
165
|
+
examples: z.ZodArray<z.ZodObject<{
|
|
166
|
+
label: z.ZodString;
|
|
167
|
+
intent: z.ZodString;
|
|
168
|
+
storyId: z.ZodString;
|
|
169
|
+
}, "strip", z.ZodTypeAny, {
|
|
170
|
+
intent: string;
|
|
171
|
+
label: string;
|
|
172
|
+
storyId: string;
|
|
173
|
+
}, {
|
|
174
|
+
intent: string;
|
|
175
|
+
label: string;
|
|
176
|
+
storyId: string;
|
|
177
|
+
}>, "many">;
|
|
178
|
+
antiExamples: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
179
|
+
label: z.ZodString;
|
|
180
|
+
why: z.ZodString;
|
|
181
|
+
useInstead: z.ZodOptional<z.ZodString>;
|
|
182
|
+
}, "strip", z.ZodTypeAny, {
|
|
183
|
+
label: string;
|
|
184
|
+
why: string;
|
|
185
|
+
useInstead?: string | undefined;
|
|
186
|
+
}, {
|
|
187
|
+
label: string;
|
|
188
|
+
why: string;
|
|
189
|
+
useInstead?: string | undefined;
|
|
190
|
+
}>, "many">>;
|
|
191
|
+
behavior: z.ZodDefault<z.ZodObject<{
|
|
192
|
+
fetchesData: z.ZodDefault<z.ZodBoolean>;
|
|
193
|
+
hasClientState: z.ZodDefault<z.ZodBoolean>;
|
|
194
|
+
animates: z.ZodDefault<z.ZodBoolean>;
|
|
195
|
+
requiresAnalytics: z.ZodDefault<z.ZodBoolean>;
|
|
196
|
+
}, "strip", z.ZodTypeAny, {
|
|
197
|
+
fetchesData: boolean;
|
|
198
|
+
hasClientState: boolean;
|
|
199
|
+
animates: boolean;
|
|
200
|
+
requiresAnalytics: boolean;
|
|
201
|
+
}, {
|
|
202
|
+
fetchesData?: boolean | undefined;
|
|
203
|
+
hasClientState?: boolean | undefined;
|
|
204
|
+
animates?: boolean | undefined;
|
|
205
|
+
requiresAnalytics?: boolean | undefined;
|
|
206
|
+
}>>;
|
|
207
|
+
}, "strip", z.ZodTypeAny, {
|
|
208
|
+
name: string;
|
|
209
|
+
category: "section" | "hero" | "card" | "media" | "cta" | "navigation" | "data" | "form" | "utility";
|
|
210
|
+
description: string;
|
|
211
|
+
intent: string;
|
|
212
|
+
composition: {
|
|
213
|
+
placement: ["page" | "section" | "inline", ...("page" | "section" | "inline")[]];
|
|
214
|
+
maxPerPage: number | null;
|
|
215
|
+
requiredSiblings: string[];
|
|
216
|
+
forbiddenAdjacent: string[];
|
|
217
|
+
allowedSlots?: Record<string, string[]> | undefined;
|
|
218
|
+
};
|
|
219
|
+
content: {
|
|
220
|
+
fields: ContentField[];
|
|
221
|
+
variants?: {
|
|
222
|
+
name: string;
|
|
223
|
+
description: string;
|
|
224
|
+
whenToUse: string;
|
|
225
|
+
}[] | undefined;
|
|
226
|
+
};
|
|
227
|
+
tokens: {
|
|
228
|
+
consumes: string[];
|
|
229
|
+
configurable?: {
|
|
230
|
+
prop: string;
|
|
231
|
+
tokenGroup: string;
|
|
232
|
+
allowedValues: string[];
|
|
233
|
+
}[] | undefined;
|
|
234
|
+
};
|
|
235
|
+
accessibility: {
|
|
236
|
+
keyboardSupport: string[];
|
|
237
|
+
screenReaderBehavior: string;
|
|
238
|
+
contentWarnings: string[];
|
|
239
|
+
role?: string | undefined;
|
|
240
|
+
};
|
|
241
|
+
examples: {
|
|
242
|
+
intent: string;
|
|
243
|
+
label: string;
|
|
244
|
+
storyId: string;
|
|
245
|
+
}[];
|
|
246
|
+
antiExamples: {
|
|
247
|
+
label: string;
|
|
248
|
+
why: string;
|
|
249
|
+
useInstead?: string | undefined;
|
|
250
|
+
}[];
|
|
251
|
+
behavior: {
|
|
252
|
+
fetchesData: boolean;
|
|
253
|
+
hasClientState: boolean;
|
|
254
|
+
animates: boolean;
|
|
255
|
+
requiresAnalytics: boolean;
|
|
256
|
+
};
|
|
257
|
+
group?: "hero" | "media" | "cta" | "navigation" | "form" | "utility" | "narrative" | "proof" | "listing" | undefined;
|
|
258
|
+
}, {
|
|
259
|
+
name: string;
|
|
260
|
+
category: "section" | "hero" | "card" | "media" | "cta" | "navigation" | "data" | "form" | "utility";
|
|
261
|
+
description: string;
|
|
262
|
+
intent: string;
|
|
263
|
+
composition: {
|
|
264
|
+
placement: ["page" | "section" | "inline", ...("page" | "section" | "inline")[]];
|
|
265
|
+
maxPerPage?: number | null | undefined;
|
|
266
|
+
requiredSiblings?: string[] | undefined;
|
|
267
|
+
forbiddenAdjacent?: string[] | undefined;
|
|
268
|
+
allowedSlots?: Record<string, string[]> | undefined;
|
|
269
|
+
};
|
|
270
|
+
content: {
|
|
271
|
+
fields: ContentFieldInput[];
|
|
272
|
+
variants?: {
|
|
273
|
+
name: string;
|
|
274
|
+
description: string;
|
|
275
|
+
whenToUse: string;
|
|
276
|
+
}[] | undefined;
|
|
277
|
+
};
|
|
278
|
+
tokens: {
|
|
279
|
+
consumes: string[];
|
|
280
|
+
configurable?: {
|
|
281
|
+
prop: string;
|
|
282
|
+
tokenGroup: string;
|
|
283
|
+
allowedValues: string[];
|
|
284
|
+
}[] | undefined;
|
|
285
|
+
};
|
|
286
|
+
accessibility: {
|
|
287
|
+
screenReaderBehavior: string;
|
|
288
|
+
role?: string | undefined;
|
|
289
|
+
keyboardSupport?: string[] | undefined;
|
|
290
|
+
contentWarnings?: string[] | undefined;
|
|
291
|
+
};
|
|
292
|
+
examples: {
|
|
293
|
+
intent: string;
|
|
294
|
+
label: string;
|
|
295
|
+
storyId: string;
|
|
296
|
+
}[];
|
|
297
|
+
group?: "hero" | "media" | "cta" | "navigation" | "form" | "utility" | "narrative" | "proof" | "listing" | undefined;
|
|
298
|
+
antiExamples?: {
|
|
299
|
+
label: string;
|
|
300
|
+
why: string;
|
|
301
|
+
useInstead?: string | undefined;
|
|
302
|
+
}[] | undefined;
|
|
303
|
+
behavior?: {
|
|
304
|
+
fetchesData?: boolean | undefined;
|
|
305
|
+
hasClientState?: boolean | undefined;
|
|
306
|
+
animates?: boolean | undefined;
|
|
307
|
+
requiresAnalytics?: boolean | undefined;
|
|
308
|
+
} | undefined;
|
|
309
|
+
}>;
|
|
310
|
+
export type ComponentContract = z.infer<typeof ComponentContractSchema>;
|
|
311
|
+
export type ComponentCategory = z.infer<typeof CategorySchema>;
|
|
312
|
+
export type ComponentGroup = z.infer<typeof GroupSchema>;
|
|
313
|
+
/**
|
|
314
|
+
* The shelf a component is filed under: its `group` when set, otherwise its
|
|
315
|
+
* `category`.
|
|
316
|
+
*
|
|
317
|
+
* Anything that groups components should call this rather than reading either
|
|
318
|
+
* field directly, so a design system part-way through adopting `group` groups
|
|
319
|
+
* consistently instead of half one way and half the other. It takes a
|
|
320
|
+
* structural type so it also accepts the looser component shapes consumers
|
|
321
|
+
* carry around, not only a fully parsed {@link ComponentContract}.
|
|
322
|
+
*/
|
|
323
|
+
export declare function groupOf(component: {
|
|
324
|
+
category: string;
|
|
325
|
+
group?: string | undefined;
|
|
326
|
+
}): string;
|
|
327
|
+
export type ComponentPlacement = z.infer<typeof PlacementSchema>;
|
|
328
|
+
export type ContentFieldType = z.infer<typeof FieldTypeSchema>;
|
|
329
|
+
export {};
|
|
330
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,UAAU,CAAA;AAEvC,QAAA,MAAM,eAAe,4GAWnB,CAAA;AAEF,QAAA,MAAM,eAAe,0CAAwC,CAAA;AAE7D,QAAA,MAAM,cAAc,iGAUlB,CAAA;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,QAAA,MAAM,WAAW,uGAUf,CAAA;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;IACrC,QAAQ,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,EAAE,CAAC,EAAE,YAAY,EAAE,GAAG,SAAS,CAAA;CAChC,CAAA;AAED,iFAAiF;AACjF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;IACrC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,EAAE,CAAC,EAAE,iBAAiB,EAAE,GAAG,SAAS,CAAA;CACrC,CAAA;AAeD,eAAO,MAAM,uBAAuB;;;IAIlC;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgFH,CAAA;AAEF,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAA;AACvE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAC9D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAA;AAExD;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,SAAS,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAAG,MAAM,CAE3F;AACD,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAChE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* The current version of the contract schema. Bump this when making a
|
|
4
|
+
* backwards-incompatible change to the shape. Manifests declare the
|
|
5
|
+
* version they satisfy so the loader can reject mismatches loudly.
|
|
6
|
+
*/
|
|
7
|
+
export const CONTRACT_VERSION = '1.0.0';
|
|
8
|
+
const FieldTypeSchema = z.enum([
|
|
9
|
+
'text',
|
|
10
|
+
'richtext',
|
|
11
|
+
'link',
|
|
12
|
+
'image',
|
|
13
|
+
'video',
|
|
14
|
+
'select',
|
|
15
|
+
'group',
|
|
16
|
+
'array',
|
|
17
|
+
'boolean',
|
|
18
|
+
'number',
|
|
19
|
+
]);
|
|
20
|
+
const PlacementSchema = z.enum(['page', 'section', 'inline']);
|
|
21
|
+
const CategorySchema = z.enum([
|
|
22
|
+
'hero',
|
|
23
|
+
'section',
|
|
24
|
+
'card',
|
|
25
|
+
'media',
|
|
26
|
+
'cta',
|
|
27
|
+
'navigation',
|
|
28
|
+
'data',
|
|
29
|
+
'form',
|
|
30
|
+
'utility',
|
|
31
|
+
]);
|
|
32
|
+
/**
|
|
33
|
+
* Where an editor looks for a component, as distinct from what the component
|
|
34
|
+
* *is*.
|
|
35
|
+
*
|
|
36
|
+
* `category` answers the second question and several consumers reason about it
|
|
37
|
+
* as a kind. It is a poor answer to the first: a real design system files
|
|
38
|
+
* roughly half its library under `section`, so a picker grouped on `category`
|
|
39
|
+
* hands back the flat list the grouping was meant to avoid, while `card` and
|
|
40
|
+
* `navigation` hold one entry each. Redistributing within `category` to even
|
|
41
|
+
* the shelves out would file components under the wrong kind for every other
|
|
42
|
+
* consumer.
|
|
43
|
+
*
|
|
44
|
+
* So this is a second, optional field. The values are shelf labels, chosen to
|
|
45
|
+
* split the sections a `category` cannot:
|
|
46
|
+
*
|
|
47
|
+
* - `hero` — page openers
|
|
48
|
+
* - `narrative` — sections that explain, walk through, or tell
|
|
49
|
+
* - `proof` — sections that evidence a claim: testimony, clients, results
|
|
50
|
+
* - `listing` — collections of records, usually repeating
|
|
51
|
+
* - `media` — image, video, audio, and their captions
|
|
52
|
+
* - `form` — anything an editor thinks of as a form
|
|
53
|
+
* - `cta` — asks
|
|
54
|
+
* - `navigation` — wayfinding within or across pages
|
|
55
|
+
* - `utility` — structural or incidental
|
|
56
|
+
*
|
|
57
|
+
* There is deliberately no `section`: a shelf that holds half the library is
|
|
58
|
+
* the problem this field exists to solve. There is no `card` or `data` either
|
|
59
|
+
* — both name a kind rather than a place to look, and their contents belong on
|
|
60
|
+
* `listing` and `proof` respectively.
|
|
61
|
+
*/
|
|
62
|
+
const GroupSchema = z.enum([
|
|
63
|
+
'hero',
|
|
64
|
+
'narrative',
|
|
65
|
+
'proof',
|
|
66
|
+
'listing',
|
|
67
|
+
'media',
|
|
68
|
+
'form',
|
|
69
|
+
'cta',
|
|
70
|
+
'navigation',
|
|
71
|
+
'utility',
|
|
72
|
+
]);
|
|
73
|
+
const ContentFieldSchema = z.lazy(() => z.object({
|
|
74
|
+
name: z.string().min(1),
|
|
75
|
+
type: FieldTypeSchema,
|
|
76
|
+
required: z.boolean().default(false),
|
|
77
|
+
maxLength: z.number().int().positive().optional(),
|
|
78
|
+
/** Human-readable constraint description the AI reasons about. */
|
|
79
|
+
constraints: z.string().optional(),
|
|
80
|
+
/** For array or group fields, the nested field shape. */
|
|
81
|
+
of: z.array(ContentFieldSchema).optional(),
|
|
82
|
+
}));
|
|
83
|
+
export const ComponentContractSchema = z.object({
|
|
84
|
+
// Identity
|
|
85
|
+
name: z.string().min(1).regex(/^[A-Z][A-Za-z0-9]+$/, 'Component names must be PascalCase'),
|
|
86
|
+
category: CategorySchema,
|
|
87
|
+
/**
|
|
88
|
+
* The shelf an authoring UI files this component under. Optional: when it is
|
|
89
|
+
* absent, {@link groupOf} falls back to {@link category}, so a design system
|
|
90
|
+
* that sets none groups exactly as it did before this field existed.
|
|
91
|
+
*/
|
|
92
|
+
group: GroupSchema.optional(),
|
|
93
|
+
description: z.string().min(20).max(280),
|
|
94
|
+
intent: z.string().min(20).max(500),
|
|
95
|
+
// Composition
|
|
96
|
+
composition: z.object({
|
|
97
|
+
placement: z.array(PlacementSchema).nonempty(),
|
|
98
|
+
maxPerPage: z.number().int().positive().nullable().default(null),
|
|
99
|
+
requiredSiblings: z.array(z.string()).default([]),
|
|
100
|
+
forbiddenAdjacent: z.array(z.string()).default([]),
|
|
101
|
+
allowedSlots: z.record(z.string(), z.array(z.string())).optional(),
|
|
102
|
+
}),
|
|
103
|
+
// Content
|
|
104
|
+
content: z.object({
|
|
105
|
+
fields: z.array(ContentFieldSchema),
|
|
106
|
+
variants: z
|
|
107
|
+
.array(z.object({
|
|
108
|
+
name: z.string(),
|
|
109
|
+
description: z.string(),
|
|
110
|
+
whenToUse: z.string(),
|
|
111
|
+
}))
|
|
112
|
+
.optional(),
|
|
113
|
+
}),
|
|
114
|
+
// Tokens
|
|
115
|
+
tokens: z.object({
|
|
116
|
+
consumes: z.array(z.string()),
|
|
117
|
+
configurable: z
|
|
118
|
+
.array(z.object({
|
|
119
|
+
prop: z.string(),
|
|
120
|
+
tokenGroup: z.string(),
|
|
121
|
+
allowedValues: z.array(z.string()),
|
|
122
|
+
}))
|
|
123
|
+
.optional(),
|
|
124
|
+
}),
|
|
125
|
+
// Accessibility
|
|
126
|
+
accessibility: z.object({
|
|
127
|
+
role: z.string().optional(),
|
|
128
|
+
keyboardSupport: z.array(z.string()).default([]),
|
|
129
|
+
screenReaderBehavior: z.string().min(10),
|
|
130
|
+
contentWarnings: z.array(z.string()).default([]),
|
|
131
|
+
}),
|
|
132
|
+
// Examples
|
|
133
|
+
examples: z
|
|
134
|
+
.array(z.object({
|
|
135
|
+
label: z.string(),
|
|
136
|
+
intent: z.string(),
|
|
137
|
+
storyId: z.string(),
|
|
138
|
+
}))
|
|
139
|
+
.min(1, 'Each component must have at least one example'),
|
|
140
|
+
antiExamples: z
|
|
141
|
+
.array(z.object({
|
|
142
|
+
label: z.string(),
|
|
143
|
+
why: z.string(),
|
|
144
|
+
useInstead: z.string().optional(),
|
|
145
|
+
}))
|
|
146
|
+
.default([]),
|
|
147
|
+
// Behavioral
|
|
148
|
+
behavior: z
|
|
149
|
+
.object({
|
|
150
|
+
fetchesData: z.boolean().default(false),
|
|
151
|
+
hasClientState: z.boolean().default(false),
|
|
152
|
+
animates: z.boolean().default(false),
|
|
153
|
+
requiresAnalytics: z.boolean().default(false),
|
|
154
|
+
})
|
|
155
|
+
.default({}),
|
|
156
|
+
});
|
|
157
|
+
/**
|
|
158
|
+
* The shelf a component is filed under: its `group` when set, otherwise its
|
|
159
|
+
* `category`.
|
|
160
|
+
*
|
|
161
|
+
* Anything that groups components should call this rather than reading either
|
|
162
|
+
* field directly, so a design system part-way through adopting `group` groups
|
|
163
|
+
* consistently instead of half one way and half the other. It takes a
|
|
164
|
+
* structural type so it also accepts the looser component shapes consumers
|
|
165
|
+
* carry around, not only a fully parsed {@link ComponentContract}.
|
|
166
|
+
*/
|
|
167
|
+
export function groupOf(component) {
|
|
168
|
+
return component.group ?? component.category;
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forumone/throughline-design-contract",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "The contract every AI-ready design system satisfies to be consumable by Throughline.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./lint": {
|
|
14
|
+
"types": "./dist/lint.d.ts",
|
|
15
|
+
"default": "./dist/lint.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"CHANGELOG.md"
|
|
22
|
+
],
|
|
5
23
|
"keywords": [
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
24
|
+
"throughline",
|
|
25
|
+
"design-system",
|
|
26
|
+
"ai-ready",
|
|
27
|
+
"payload"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/forumone/throughline.git",
|
|
33
|
+
"directory": "packages/design-contract"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/forumone/throughline/tree/main/packages/design-contract#readme",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/forumone/throughline/issues"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"zod": "^3.23.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.17.0",
|
|
47
|
+
"eslint": "^9.15.0",
|
|
48
|
+
"typescript": "^5.6.0",
|
|
49
|
+
"vitest": "^2.1.0",
|
|
50
|
+
"@forumone/throughline-eslint-config": "0.0.0",
|
|
51
|
+
"@forumone/throughline-tsconfig": "0.0.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsc -b",
|
|
55
|
+
"dev": "tsc -b -w",
|
|
56
|
+
"clean": "rm -rf dist .turbo *.tsbuildinfo",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"lint": "eslint src",
|
|
59
|
+
"test": "vitest run"
|
|
60
|
+
}
|
|
61
|
+
}
|