@geonosis/themekit 1.0.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/LICENSE +202 -0
- package/README.md +226 -0
- package/dist/index.d.ts +456 -0
- package/dist/index.js +541 -0
- package/package.json +39 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tokens contract — the semantic surface a brand fills in.
|
|
3
|
+
*
|
|
4
|
+
* Generalised from dielime's `BrandTheme` (measured in `docs/themekit-inventory-2026-08-30.md`),
|
|
5
|
+
* with two deliberate widenings and one deliberate narrowing:
|
|
6
|
+
*
|
|
7
|
+
* - `fonts`, `radii` and `shadows` are OPEN maps, not fixed keys. dielime names its font roles
|
|
8
|
+
* `body` and `display`; another repo names them `sans`/`serif`/`mono`; during.day has no radii at
|
|
9
|
+
* all. Font roles are data (law 6), so the contract may not decide them — `font-roles-only` takes
|
|
10
|
+
* the list as an option for the same reason.
|
|
11
|
+
* - `colors` stays a NAMED set, because the role names are the contract's whole value and they are
|
|
12
|
+
* design-system vocabulary rather than any repo's: "primary" is not a dielime word. A brand with
|
|
13
|
+
* a colour the set does not model puts it in `extra`, which is what `extra` is for.
|
|
14
|
+
* - `logo` and the brand's identity are NOT here. They live in `brand.json` beside the tokens: a
|
|
15
|
+
* logo is an asset descriptor with no DTCG type and no CSS output, and mixing it into the token
|
|
16
|
+
* file is what makes a token file un-exportable.
|
|
17
|
+
*
|
|
18
|
+
* Every value is a CSS value as authored — `#ffe849`, `oklch(0.91 0.17 102)`, `var(--mc-gray-c)`,
|
|
19
|
+
* `color-mix(in srgb, … )`, a whole font stack, a whole shadow. The kit never re-spells one.
|
|
20
|
+
*/
|
|
21
|
+
/** A CSS value, exactly as authored. Never parsed, never normalised, never rounded. */
|
|
22
|
+
type TokenValue = string;
|
|
23
|
+
/**
|
|
24
|
+
* Colour roles. Names express ROLE, not hue — a brand's `primary` can be any colour.
|
|
25
|
+
*
|
|
26
|
+
* Required roles are the ones a component library cannot render without. The optional ones are
|
|
27
|
+
* optional because dielime ships themes without them, and a contract that demanded them would
|
|
28
|
+
* refuse the themes it was extracted from.
|
|
29
|
+
*/
|
|
30
|
+
interface ThemeColors {
|
|
31
|
+
/** Decorative accent — highlights, ribbons, badges. */
|
|
32
|
+
accent?: TokenValue;
|
|
33
|
+
/** Default page surface. */
|
|
34
|
+
background: TokenValue;
|
|
35
|
+
/** Hairlines, dividers, input borders. */
|
|
36
|
+
border: TokenValue;
|
|
37
|
+
/** Destructive and error states. */
|
|
38
|
+
danger: TokenValue;
|
|
39
|
+
/** Focus rings and selection highlights. */
|
|
40
|
+
focusRing: TokenValue;
|
|
41
|
+
/** Default text and icon colour on `background`. */
|
|
42
|
+
foreground: TokenValue;
|
|
43
|
+
/** Inline link text. Falls back to `primary` when a brand omits it. */
|
|
44
|
+
link?: TokenValue;
|
|
45
|
+
/** Subdued text — captions, secondary copy. */
|
|
46
|
+
muted: TokenValue;
|
|
47
|
+
/** Text and icons on `accent`. */
|
|
48
|
+
onAccent?: TokenValue;
|
|
49
|
+
/** Text and icons on `primary`. Should clear WCAG AA against it. */
|
|
50
|
+
onPrimary: TokenValue;
|
|
51
|
+
/** Text and icons on `secondary`. */
|
|
52
|
+
onSecondary: TokenValue;
|
|
53
|
+
/** Text and icons on `surface`. */
|
|
54
|
+
onSurface: TokenValue;
|
|
55
|
+
/** Text and icons on `surfaceInverse`. */
|
|
56
|
+
onSurfaceInverse?: TokenValue;
|
|
57
|
+
/** Brand identity — primary CTAs, active states, brand chrome. */
|
|
58
|
+
primary: TokenValue;
|
|
59
|
+
/** Supportive identity — secondary CTAs, accents. */
|
|
60
|
+
secondary: TokenValue;
|
|
61
|
+
/** Positive and success states. */
|
|
62
|
+
success: TokenValue;
|
|
63
|
+
/** Elevated surface, one step above `background` — cards, popovers. */
|
|
64
|
+
surface: TokenValue;
|
|
65
|
+
/** Inverse surface — footers and dark sections under light content. */
|
|
66
|
+
surfaceInverse?: TokenValue;
|
|
67
|
+
/** Warning and caution states. */
|
|
68
|
+
warning: TokenValue;
|
|
69
|
+
}
|
|
70
|
+
/** One status's surface and the colour that reads on it. */
|
|
71
|
+
interface StatusPair {
|
|
72
|
+
onSurface: TokenValue;
|
|
73
|
+
surface: TokenValue;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Per-status banner and alert pairs. Optional: a component library falls back to the core
|
|
77
|
+
* `success`/`warning`/`danger` roles when a brand omits them.
|
|
78
|
+
*/
|
|
79
|
+
interface ThemeStatusColors {
|
|
80
|
+
danger?: StatusPair;
|
|
81
|
+
info?: StatusPair;
|
|
82
|
+
success?: StatusPair;
|
|
83
|
+
warning?: StatusPair;
|
|
84
|
+
}
|
|
85
|
+
/** Font roles → family stacks. The role names are the repo's, never the kit's. */
|
|
86
|
+
type ThemeFonts = Record<string, TokenValue>;
|
|
87
|
+
/** Corner radii by step name. */
|
|
88
|
+
type ThemeRadii = Record<string, TokenValue>;
|
|
89
|
+
/** Elevation steps. Each value is a whole CSS `box-shadow`, kept as one string. */
|
|
90
|
+
type ThemeShadows = Record<string, TokenValue>;
|
|
91
|
+
/**
|
|
92
|
+
* A theme: the values, and nothing about where they are rendered.
|
|
93
|
+
*
|
|
94
|
+
* `id` and `name` identify it; everything else is tokens. What the tokens compile TO is the
|
|
95
|
+
* `Emission` map, which is a separate input — the same theme compiles to `--mc-*` for one repo and
|
|
96
|
+
* to `--color-*` for another without being touched.
|
|
97
|
+
*/
|
|
98
|
+
interface Theme {
|
|
99
|
+
colors: ThemeColors;
|
|
100
|
+
/**
|
|
101
|
+
* Brand-only tokens the semantic contract deliberately does not model — a seasonal pink, a
|
|
102
|
+
* gradient, a partner's blue. Keys are emitted VERBATIM, so `limeWilt` stays `limeWilt`; nothing
|
|
103
|
+
* kebab-cases them, because `--mc-limeWilt` is a valid custom property and the one a stylesheet
|
|
104
|
+
* that already exists is reading.
|
|
105
|
+
*/
|
|
106
|
+
extra?: Record<string, TokenValue>;
|
|
107
|
+
fonts: ThemeFonts;
|
|
108
|
+
/** Stable identifier — what a `[data-brand="…"]` selector and a baseline name are keyed by. */
|
|
109
|
+
id: string;
|
|
110
|
+
/** Human-readable name, for a switcher's label. */
|
|
111
|
+
name: string;
|
|
112
|
+
radii: ThemeRadii;
|
|
113
|
+
shadows: ThemeShadows;
|
|
114
|
+
status?: ThemeStatusColors;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Typed authoring entry. Returns the theme unchanged — its only job is to pin the type so an editor
|
|
118
|
+
* completes the roles and rejects an invented one at the call site.
|
|
119
|
+
*/
|
|
120
|
+
declare const defineTheme: (theme: Theme) => Theme;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The variable map: which CSS custom properties a theme compiles to, in what order, as DATA.
|
|
124
|
+
*
|
|
125
|
+
* dielime emits `--mc-primary`, `--theme-primary`, `--primary` and `--ring` from four different
|
|
126
|
+
* vocabularies at once — its own brand prefix, a legacy layer, shadcn's names, Tailwind's radius
|
|
127
|
+
* scale. Every one of those is a fact about dielime's stylesheet, not about theming, so none of
|
|
128
|
+
* them may be a default in this package (law 6). What ships instead is the SHAPE of a map plus a
|
|
129
|
+
* generic default that holds for a repo which has declared nothing.
|
|
130
|
+
*
|
|
131
|
+
* The order of the list is the emission order, and it is load-bearing twice over: a `:root { … }`
|
|
132
|
+
* block reads top to bottom, and a later entry that names an earlier variable overwrites its VALUE
|
|
133
|
+
* while keeping its POSITION — which is how a brand's `extra` overrides a derived colour.
|
|
134
|
+
*/
|
|
135
|
+
/**
|
|
136
|
+
* A dotted path into the theme: `colors.primary`, `radii.md`, `fonts.body`,
|
|
137
|
+
* `status.danger.surface`, `extra.limeWilt`. A path that resolves to nothing means the emission is
|
|
138
|
+
* skipped, which is how an optional role stays optional with no special case anywhere.
|
|
139
|
+
*/
|
|
140
|
+
type RolePath = string;
|
|
141
|
+
/** `color-mix(in <space>, <role> <amount>, <with>)`, spelled exactly like that. */
|
|
142
|
+
interface Mix {
|
|
143
|
+
/** The first colour's share — `'85%'`. */
|
|
144
|
+
amount: string;
|
|
145
|
+
/** The role mixed FROM. */
|
|
146
|
+
role: RolePath;
|
|
147
|
+
/** The interpolation space. Defaults to `srgb`. */
|
|
148
|
+
space?: string;
|
|
149
|
+
/** The colour mixed INTO — `'#000000'`. */
|
|
150
|
+
with: string;
|
|
151
|
+
}
|
|
152
|
+
/** Copy a role's value into a variable. */
|
|
153
|
+
interface CopyEmission {
|
|
154
|
+
role: RolePath;
|
|
155
|
+
variable: string;
|
|
156
|
+
}
|
|
157
|
+
/** Compute a variable by mixing a role with another colour. */
|
|
158
|
+
interface MixEmission {
|
|
159
|
+
mix: Mix;
|
|
160
|
+
variable: string;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* One variable per key of an open map — `extra`, `radii`, `fonts` — named `<prefix><key>`.
|
|
164
|
+
*
|
|
165
|
+
* The key is used VERBATIM. Case-folding it is the obvious wrong normalisation: `--mc-limeWilt` is
|
|
166
|
+
* a valid custom property and, in a repo that already ships one, the name a stylesheet is reading.
|
|
167
|
+
*/
|
|
168
|
+
interface EachEmission {
|
|
169
|
+
each: 'extra' | 'fonts' | 'radii' | 'shadows';
|
|
170
|
+
prefix: string;
|
|
171
|
+
}
|
|
172
|
+
type Emission = CopyEmission | EachEmission | MixEmission;
|
|
173
|
+
/**
|
|
174
|
+
* Read a dotted path off a theme, or nothing.
|
|
175
|
+
*
|
|
176
|
+
* Deliberately total and deliberately quiet: an emission naming a role the theme does not carry is
|
|
177
|
+
* how `--accent` stays conditional, so a miss cannot be an error here. What a miss must never do is
|
|
178
|
+
* emit an empty variable — `--accent: ;` is a parse error in the stylesheet and a silent one in the
|
|
179
|
+
* compiler, which is the class of failure this whole package exists downstream of.
|
|
180
|
+
*/
|
|
181
|
+
declare const valueAt: (theme: Theme, path: RolePath) => TokenValue | undefined;
|
|
182
|
+
/**
|
|
183
|
+
* The variables an emission map writes for a theme, in order.
|
|
184
|
+
*
|
|
185
|
+
* Assignment rather than accumulation, so a repeated variable keeps its first position and takes
|
|
186
|
+
* its last value — JavaScript's own record semantics, which is what the stylesheet a consumer
|
|
187
|
+
* already ships was written against.
|
|
188
|
+
*/
|
|
189
|
+
declare const emitVariables: (theme: Theme, emit: readonly Emission[]) => Record<string, string>;
|
|
190
|
+
/**
|
|
191
|
+
* The map a repo gets when it has declared none: one variable per role, `<prefix><kebab-role>`,
|
|
192
|
+
* nothing derived and nothing aliased.
|
|
193
|
+
*
|
|
194
|
+
* It is not dielime's map and is not meant to be. A repo with an existing stylesheet declares its
|
|
195
|
+
* own; a repo starting from zero gets a complete, predictable set and no opinions about shadcn.
|
|
196
|
+
*/
|
|
197
|
+
declare const defaultEmission: (prefix?: string) => Emission[];
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Compile a theme to the four shapes a consumer actually needs, from one pass over one map.
|
|
201
|
+
*
|
|
202
|
+
* Nothing here launches, renders or resolves anything: a theme in, strings out. That is what makes
|
|
203
|
+
* the extraction possible at all — the compiler it replaces was pure too, and a compiler that
|
|
204
|
+
* needed React would have taken a component library with it (D-010).
|
|
205
|
+
*/
|
|
206
|
+
interface CompileOptions {
|
|
207
|
+
/**
|
|
208
|
+
* A CSS-value expression per emitted variable, for the Tailwind v4 `@theme inline` block —
|
|
209
|
+
* `(variable) => `--color-brand${variable.slice(4)}``. Omitted, no block is generated.
|
|
210
|
+
*/
|
|
211
|
+
readonly themeBlock?: (variable: string) => string | undefined;
|
|
212
|
+
/** The variable map. Defaults to {@link defaultEmission} — generic, not any repo's. */
|
|
213
|
+
readonly emit?: readonly Emission[];
|
|
214
|
+
/** The selector the `css` block targets. Defaults to `:root`. */
|
|
215
|
+
readonly selector?: string;
|
|
216
|
+
}
|
|
217
|
+
interface Compiled {
|
|
218
|
+
/** The custom properties, in emission order. Spreadable into a `style` prop as-is. */
|
|
219
|
+
readonly cssVariables: Record<string, string>;
|
|
220
|
+
/** `<selector> { … }`, one declaration per line, newline-terminated. */
|
|
221
|
+
readonly css: string;
|
|
222
|
+
/** The theme it was compiled from, unchanged. */
|
|
223
|
+
readonly object: Theme;
|
|
224
|
+
/**
|
|
225
|
+
* Role path → resolved value, flat: `plain['colors.primary']`, `plain['radii.md']`.
|
|
226
|
+
*
|
|
227
|
+
* For the consumers that cannot use custom properties at all — react-email inline styles, an
|
|
228
|
+
* `<svg fill>`, a canvas. It is deliberately NOT the variable record: a mail client that drops
|
|
229
|
+
* `var()` needs the value, and which variable a repo happens to call it by is not its business.
|
|
230
|
+
*/
|
|
231
|
+
readonly plain: Record<string, TokenValue>;
|
|
232
|
+
/** The Tailwind v4 `@theme inline { … }` block, or `''` when no mapper was given. */
|
|
233
|
+
readonly themeBlock: string;
|
|
234
|
+
}
|
|
235
|
+
declare const compile: (theme: Theme, options?: CompileOptions) => Compiled;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* DTCG in and out — the interchange, and the one place the kit deviates from the draft on purpose.
|
|
239
|
+
*
|
|
240
|
+
* The Design Tokens Community Group draft types a colour as an OBJECT (`colorSpace`, `components`),
|
|
241
|
+
* a dimension as `{ value, unit }` and a shadow as five named fields. Every value in a real theme
|
|
242
|
+
* measured for this package is a CSS string instead, and three kinds of them cannot become those
|
|
243
|
+
* objects at all: `var(--mc-gray-c)` has no colour space, `color-mix(in srgb, … )` is a computation,
|
|
244
|
+
* and `0 1px 5px rgb(0 0 0 / 0.12)` is a spelling — parse it into five fields and print it back and
|
|
245
|
+
* you get the same colour and different bytes.
|
|
246
|
+
*
|
|
247
|
+
* Different bytes is the whole problem. The gate on this extraction is that a repo's stylesheet is
|
|
248
|
+
* unchanged afterwards, so a token file that re-spells a value on the way through is not an
|
|
249
|
+
* interchange format, it is a rewrite.
|
|
250
|
+
*
|
|
251
|
+
* **So this profile stores `$value` as the CSS string, exactly as authored.** Everything else is
|
|
252
|
+
* the draft: groups, `$value`, `$type`, `{alias}`-shaped names, `$extensions`. The deviation is
|
|
253
|
+
* declared in the document itself and measured by `validateDtcg(doc, { strict: true })`, which
|
|
254
|
+
* names the tokens the strict draft could not hold — the extension the draft would need, per token,
|
|
255
|
+
* rather than as a paragraph in a README nobody diffs.
|
|
256
|
+
*/
|
|
257
|
+
/** Reverse-domain key for the kit's own `$extensions` block, as the draft requires. */
|
|
258
|
+
declare const GEONOSIS_EXTENSION = "com.microcompanies.geonosis";
|
|
259
|
+
/** The profile name written into every document the kit produces. */
|
|
260
|
+
declare const PROFILE = "css-values";
|
|
261
|
+
type DtcgType = 'color' | 'dimension' | 'fontFamily' | 'gradient' | 'shadow';
|
|
262
|
+
interface DtcgToken {
|
|
263
|
+
$description?: string;
|
|
264
|
+
$type: DtcgType;
|
|
265
|
+
$value: TokenValue;
|
|
266
|
+
}
|
|
267
|
+
interface BrandIdentity {
|
|
268
|
+
id: string;
|
|
269
|
+
name: string;
|
|
270
|
+
profile: string;
|
|
271
|
+
}
|
|
272
|
+
interface DtcgDocument {
|
|
273
|
+
[group: string]: unknown;
|
|
274
|
+
$extensions: Record<string, BrandIdentity | undefined>;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* A theme as a DTCG document.
|
|
278
|
+
*
|
|
279
|
+
* A group the theme does not carry is left out entirely rather than written empty: an empty group
|
|
280
|
+
* is indistinguishable from a group whose tokens were lost, and `fromDtcg` would hand back
|
|
281
|
+
* `extra: {}` where the theme said nothing at all.
|
|
282
|
+
*/
|
|
283
|
+
declare const toDtcg: (theme: Theme) => DtcgDocument;
|
|
284
|
+
/**
|
|
285
|
+
* A DTCG document as a theme.
|
|
286
|
+
*
|
|
287
|
+
* Deliberately lossy in exactly one direction and no other: `$description` has nowhere to go in the
|
|
288
|
+
* contract, so it is dropped. `validateDtcg` names every path where that will happen, because a
|
|
289
|
+
* conversion that quietly deletes half a file is the failure mode the whole package is downstream
|
|
290
|
+
* of.
|
|
291
|
+
*/
|
|
292
|
+
declare const fromDtcg: (document: Record<string, unknown>) => Theme;
|
|
293
|
+
interface ValidateOptions {
|
|
294
|
+
/** Also report every value the DTCG draft's own `$value` shapes could not hold. */
|
|
295
|
+
readonly strict?: boolean;
|
|
296
|
+
}
|
|
297
|
+
interface DtcgReport {
|
|
298
|
+
/** Structural breaks. Non-empty means the document is not this profile. */
|
|
299
|
+
readonly errors: string[];
|
|
300
|
+
/** Paths whose data a round trip through the contract would drop. */
|
|
301
|
+
readonly lossy: string[];
|
|
302
|
+
readonly valid: boolean;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Judge a document against this profile, and — on request — against the strict draft.
|
|
306
|
+
*
|
|
307
|
+
* Structure first: a token without `$value`, a `$value` that is not a string, a `$type` the group
|
|
308
|
+
* cannot have, an identity block that is not there. Then `lossy`, which is not an error but is
|
|
309
|
+
* never silent. Then, under `strict`, the tokens whose value the draft's own shapes cannot express
|
|
310
|
+
* — the list of extensions the draft would need to hold this file, per token.
|
|
311
|
+
*/
|
|
312
|
+
declare const validateDtcg: (document: Record<string, unknown>, options?: ValidateOptions) => DtcgReport;
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Figma variables → DTCG, over the shape the REST API documents for
|
|
316
|
+
* `GET /v1/files/:key/variables/local`.
|
|
317
|
+
*
|
|
318
|
+
* Two things decide whether an importer is worth having, and both of them are about what it does
|
|
319
|
+
* with what it cannot handle.
|
|
320
|
+
*
|
|
321
|
+
* **Aliases.** A real Figma file is a `Primitives` collection nobody themes and a `Semantic`
|
|
322
|
+
* collection whose every variable is an ALIAS into it. An importer that did not resolve those would
|
|
323
|
+
* write `VariableID:1` where a colour goes, and — worse — every mode would come out identical,
|
|
324
|
+
* because the id does not change per mode. Resolution follows the chain in the mode being imported,
|
|
325
|
+
* falling back to a collection's own default mode when it does not have that mode (`Primitives` has
|
|
326
|
+
* never heard of `Dark`), and stops on a cycle with a report rather than a stack overflow.
|
|
327
|
+
*
|
|
328
|
+
* **Everything it will not guess.** A `BOOLEAN` is not a design token. A `FLOAT` is a bare number:
|
|
329
|
+
* Figma carries `8`, with nothing anywhere saying `px` or `rem`, and guessing `px` is right most of
|
|
330
|
+
* the time and produces, the rest of the time, a layout nobody can trace back to an import. A
|
|
331
|
+
* variable nobody mapped is a token a designer made and the contract does not model. None of the
|
|
332
|
+
* three is dropped: the first two are reported in `unsupported` with a reason, and the third is
|
|
333
|
+
* imported into `extra` — the escape hatch — and listed in `unmapped`.
|
|
334
|
+
*/
|
|
335
|
+
type FigmaColor = {
|
|
336
|
+
a?: number;
|
|
337
|
+
b: number;
|
|
338
|
+
g: number;
|
|
339
|
+
r: number;
|
|
340
|
+
};
|
|
341
|
+
type FigmaAlias = {
|
|
342
|
+
id: string;
|
|
343
|
+
type: 'VARIABLE_ALIAS';
|
|
344
|
+
};
|
|
345
|
+
type FigmaValue = boolean | FigmaAlias | FigmaColor | number | string;
|
|
346
|
+
interface FigmaVariable {
|
|
347
|
+
name: string;
|
|
348
|
+
resolvedType: string;
|
|
349
|
+
valuesByMode: Record<string, FigmaValue>;
|
|
350
|
+
variableCollectionId: string;
|
|
351
|
+
}
|
|
352
|
+
interface FigmaCollection {
|
|
353
|
+
defaultModeId: string;
|
|
354
|
+
modes: {
|
|
355
|
+
modeId: string;
|
|
356
|
+
name: string;
|
|
357
|
+
}[];
|
|
358
|
+
name: string;
|
|
359
|
+
}
|
|
360
|
+
interface ImportedTheme {
|
|
361
|
+
id: string;
|
|
362
|
+
/** The mode's own name, as the designer typed it. */
|
|
363
|
+
name: string;
|
|
364
|
+
tokens: DtcgDocument;
|
|
365
|
+
}
|
|
366
|
+
interface FigmaImport {
|
|
367
|
+
themes: ImportedTheme[];
|
|
368
|
+
/** Variables the `map` did not name. Imported into `extra`, never dropped. */
|
|
369
|
+
unmapped: string[];
|
|
370
|
+
/** `name: reason` per variable that could not become a token at all. */
|
|
371
|
+
unsupported: string[];
|
|
372
|
+
}
|
|
373
|
+
interface FigmaOptions {
|
|
374
|
+
/**
|
|
375
|
+
* The name of the collection whose modes become themes. Defaults to the collection with the most
|
|
376
|
+
* modes — which is the semantic one in every file built the usual way.
|
|
377
|
+
*/
|
|
378
|
+
readonly collection?: string;
|
|
379
|
+
/**
|
|
380
|
+
* The unit a `FLOAT` is in. Unset, every FLOAT is reported as unsupported: the payload does not
|
|
381
|
+
* carry a unit and this package does not invent one.
|
|
382
|
+
*/
|
|
383
|
+
readonly floatUnit?: 'px' | 'rem';
|
|
384
|
+
/** Figma variable name → contract role path. The designer's vocabulary meeting the repo's. */
|
|
385
|
+
readonly map?: Record<string, string>;
|
|
386
|
+
}
|
|
387
|
+
declare const fromFigmaVariables: (payload: Record<string, unknown>, options?: FigmaOptions) => FigmaImport;
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* A themekit read off disk.
|
|
391
|
+
*
|
|
392
|
+
* The registry is FILES — `themekits/<brand>/` in this repo, a directory vendored into a consumer,
|
|
393
|
+
* a directory inside `node_modules`. `loadThemekit` takes a path and does not care which, and that
|
|
394
|
+
* is deliberate: whose brand gets published, and where, is the brand owner's decision and not the
|
|
395
|
+
* kit's. Nothing here ships inside `@geonosis/themekit`'s tarball (`files: ["dist"]`), so installing
|
|
396
|
+
* the compiler never installs somebody else's colours.
|
|
397
|
+
*/
|
|
398
|
+
/** `themekit.json` — the manifest, which is what a version lives in. */
|
|
399
|
+
interface ThemekitManifest {
|
|
400
|
+
/** Does the theme fill every required colour role? Checked, never taken on trust. */
|
|
401
|
+
complete: boolean;
|
|
402
|
+
description?: string;
|
|
403
|
+
/**
|
|
404
|
+
* Path to the emission map, relative to this directory. Two brands over one UI kit share one map,
|
|
405
|
+
* and one fact copied into two files is two facts that will disagree.
|
|
406
|
+
*/
|
|
407
|
+
emit?: string;
|
|
408
|
+
name: string;
|
|
409
|
+
version: string;
|
|
410
|
+
}
|
|
411
|
+
/** `brand.json` — identity, which is not tokens: a name, a logo, whatever else a brand carries. */
|
|
412
|
+
interface Brand {
|
|
413
|
+
[key: string]: unknown;
|
|
414
|
+
id: string;
|
|
415
|
+
name: string;
|
|
416
|
+
}
|
|
417
|
+
interface Themekit {
|
|
418
|
+
brand: Brand;
|
|
419
|
+
/** From `emit.json`, or the generic default when the directory checks none in. */
|
|
420
|
+
emit: Emission[];
|
|
421
|
+
manifest: ThemekitManifest;
|
|
422
|
+
/** Required colour roles the theme does not fill. Empty for a complete themekit. */
|
|
423
|
+
missingRoles: string[];
|
|
424
|
+
theme: Theme;
|
|
425
|
+
/** The DTCG document as read, before it became a theme. */
|
|
426
|
+
tokens: Record<string, unknown>;
|
|
427
|
+
}
|
|
428
|
+
interface LoadOptions {
|
|
429
|
+
/**
|
|
430
|
+
* The version the consumer expects. A directory whose version has moved is REFUSED rather than
|
|
431
|
+
* loaded, which is what makes "a bump is invisible until you pin it" true instead of hopeful —
|
|
432
|
+
* a pin nobody checks is a comment.
|
|
433
|
+
*/
|
|
434
|
+
readonly pinned?: string;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* The colour roles a theme must fill.
|
|
438
|
+
*
|
|
439
|
+
* The optional ones are optional because the real themes this was extracted from omit them, and a
|
|
440
|
+
* contract that demanded them would refuse the themes it came from.
|
|
441
|
+
*/
|
|
442
|
+
declare const REQUIRED_COLOR_ROLES: readonly ["background", "border", "danger", "focusRing", "foreground", "muted", "onPrimary", "onSecondary", "onSurface", "primary", "secondary", "success", "surface", "warning"];
|
|
443
|
+
/** Which required roles a theme leaves empty, by role path. */
|
|
444
|
+
declare const MISSING_ROLES: (theme: Theme) => string[];
|
|
445
|
+
/**
|
|
446
|
+
* Read, validate and refuse. Never repair.
|
|
447
|
+
*
|
|
448
|
+
* Four things stop the load: the directory is not a themekit, the manifest carries no version, the
|
|
449
|
+
* tokens are not this profile (naming the token), or the manifest CLAIMS to be complete while a
|
|
450
|
+
* required role is empty. The last one is the one worth having: an incomplete themekit is honest
|
|
451
|
+
* and a consumer can plan around it, while a manifest that lies about being complete is a page that
|
|
452
|
+
* renders wrong on someone else's machine.
|
|
453
|
+
*/
|
|
454
|
+
declare const loadThemekit: (dir: string, options?: LoadOptions) => Themekit;
|
|
455
|
+
|
|
456
|
+
export { type Brand, type BrandIdentity, type CompileOptions, type Compiled, type CopyEmission, type DtcgDocument, type DtcgReport, type DtcgToken, type DtcgType, type EachEmission, type Emission, type FigmaAlias, type FigmaCollection, type FigmaColor, type FigmaImport, type FigmaOptions, type FigmaValue, type FigmaVariable, GEONOSIS_EXTENSION, type ImportedTheme, type LoadOptions, MISSING_ROLES, type Mix, type MixEmission, PROFILE, REQUIRED_COLOR_ROLES, type RolePath, type StatusPair, type Theme, type ThemeColors, type ThemeFonts, type ThemeRadii, type ThemeShadows, type ThemeStatusColors, type Themekit, type ThemekitManifest, type TokenValue, type ValidateOptions, compile, defaultEmission, defineTheme, emitVariables, fromDtcg, fromFigmaVariables, loadThemekit, toDtcg, validateDtcg, valueAt };
|