@rojaostudio/ds-core 1.1.0-next.1 → 1.1.0-next.11

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.
@@ -5,8 +5,7 @@ var rojao = {
5
5
  brand: {
6
6
  primary: "navy-900",
7
7
  secondary: "navy-900",
8
- accent: "flare-700",
9
- heading: "flare-700"
8
+ accent: "flare-700"
10
9
  },
11
10
  surface: "zinc",
12
11
  text: "zinc",
@@ -3,7 +3,14 @@ import { a as BrandDef } from './recipe.schema-Cq7e82nx.js';
3
3
  export { A as Archetype, B as BrandColors, C as ColorRef, D as DarkStrategy, F as FontDef, P as PaletteRef, S as ScaleToken, T as TextStyle, b as TextStyleName, c as TypeScale } from './recipe.schema-Cq7e82nx.js';
4
4
 
5
5
  type RdsMode = "light" | "dark" | "brand";
6
- type RdsTheme = Record<RdsMode, Record<string, string>>;
6
+ type RdsTheme = Record<RdsMode, Record<string, string>> & {
7
+ /** The brand's own variables (Figma `brand` collection, `<brand>/<name>`), one value for every mode. */
8
+ vars?: Record<string, string>;
9
+ };
10
+ type RdsThemeOptions = {
11
+ /** Where the warnings go (an explicit BrandDef.heading that fails AA). Default console.warn. */
12
+ warn?: (message: string) => void;
13
+ };
7
14
  /**
8
15
  * Roles of the [RDS] theme collection, in Figma order: [name, dark source, brand source].
9
16
  * The light mode always reads the plain base token. In dark and brand, Figma points either to the
@@ -12,22 +19,107 @@ type RdsTheme = Record<RdsMode, Record<string, string>>;
12
19
  declare const ROLES: ReadonlyArray<readonly [string, "l" | "d", "l" | "d" | "b"]>;
13
20
  /** CSS custom property of a role: the Figma path with hyphens (the [RDS] codeSyntax rule). */
14
21
  declare const roleVar: (role: string) => string;
15
- declare function generateRdsTheme(def: BrandDef): RdsTheme;
22
+ declare function generateRdsTheme(def: BrandDef, opts?: RdsThemeOptions): RdsTheme;
23
+ /**
24
+ * The selectors on which @rojaostudio/ds redeclares its component tokens (styles/rds/components.css and
25
+ * foundation.css). A component token holds `var(--theme-role)`, and a custom property is resolved on the element
26
+ * where it is declared: a theme scope that none of these match would repaint the roles but not the components
27
+ * inside it, which keep the colours resolved at :root. The attributes are the generic way in: put
28
+ * `data-rds-scope` on a scope element, `data-rds-mode="dark"` on a dark one, `data-rds-plate` on a plate.
29
+ */
30
+ declare const RDS_SCOPE_SELECTORS: readonly [":root", ".ds-scope", "[data-rds-scope]", ".dark", "[data-rds-mode]", ".ds-plate", "[data-rds-plate]"];
31
+ /** RDS_SCOPE_SELECTORS as one selector list, as the component token layer is emitted. */
32
+ declare const RDS_TOKEN_SCOPE: string;
16
33
  type RdsCssOptions = {
17
- /** Scope of the light mode. Default `:root, .ds-scope`. */
34
+ /** Scope of the light mode. Default `:root, .ds-scope, [data-rds-scope]`. */
18
35
  scope?: string;
19
- /** Selector of the dark mode, combined with the scope. Default `.dark`. */
36
+ /**
37
+ * Selector of the dark mode, combined with each scope. Default `.dark, [data-rds-mode="dark"]`. A selector
38
+ * anchored at the root (`:root[data-theme="dark"]`, `html.dark`) is used as is: the theme switches on <html>,
39
+ * as next-themes does.
40
+ */
20
41
  dark?: string;
21
- /** Selector of the brand plate (a section painted with the primary). Default `.ds-plate`. */
42
+ /** Selector of the brand plate (a section painted with the primary). Default `.ds-plate, [data-rds-plate]`. */
22
43
  plate?: string;
44
+ /**
45
+ * Skip the check that every selector is covered by the component tokens of @rojaostudio/ds (RDS_SCOPE_SELECTORS).
46
+ * Only for a theme read by your own CSS, without the components.
47
+ */
48
+ allowUncovered?: boolean;
23
49
  };
24
50
  /**
25
51
  * The theme as CSS. Dark and plate only carry what differs from light: they inherit the rest
26
52
  * through the cascade, like the [RDS] modes that point back to the plain token.
53
+ *
54
+ * Throws when a selector would leave the components of @rojaostudio/ds on the root colours (see
55
+ * RDS_SCOPE_SELECTORS), unless `allowUncovered`.
27
56
  */
28
57
  declare function emitRdsCss(theme: RdsTheme, opts?: RdsCssOptions): string;
29
58
  /** Contrast of a role pair in one mode (alpha colours are not measured: they depend on what is below). */
30
59
  declare function rdsContrast(theme: RdsTheme, mode: RdsMode, fg: string, bg: string): number | null;
60
+ /**
61
+ * The main text pairs of the theme: [text role, the surface it sits on]. Measured in every mode; a pair with an
62
+ * alpha colour is skipped (its contrast depends on what is below).
63
+ */
64
+ declare const RDS_CONTRAST_PAIRS: ReadonlyArray<readonly [string, string]>;
65
+ /**
66
+ * What is not text but has to be seen (WCAG 1.4.11, 3:1): the border of a field and of a field in error, the focus
67
+ * ring, the brand's logo and its chart series, on the surfaces they are placed on.
68
+ */
69
+ declare const RDS_NON_TEXT_PAIRS: ReadonlyArray<readonly [string, string]>;
70
+ /** A pair below its minimum: 4.5:1 for text (RDS_CONTRAST_PAIRS), 3:1 for the rest (RDS_NON_TEXT_PAIRS). */
71
+ type RdsContrastFailure = {
72
+ mode: RdsMode;
73
+ fg: string;
74
+ bg: string;
75
+ ratio: number;
76
+ min: number;
77
+ };
78
+ /**
79
+ * The pairs of RDS_CONTRAST_PAIRS below WCAG AA (4.5:1) and of RDS_NON_TEXT_PAIRS below 3:1, mode by mode. Empty
80
+ * means every pair passes. A pair with an alpha colour is skipped (rdsContrast).
81
+ */
82
+ declare function rdsContrastReport(theme: RdsTheme): RdsContrastFailure[];
83
+ /**
84
+ * A brand as drawn in the [RDS] Base Tokens file: for each theme mode, every role points to a primitive of the
85
+ * [RDS] Primitives library by name ("accyan/400"), and `primitives` holds the colour Figma resolves for each.
86
+ * Exported by `figma/export-brand.js`. The theme comes out one to one with the Figma brand mode.
87
+ */
88
+ type RdsBrandTable = {
89
+ $schema?: "rds-brand-table/1";
90
+ name: string;
91
+ primitives: Record<string, string>;
92
+ modes: Record<RdsMode, Record<string, string>>;
93
+ /** The brand's own variables of the `brand` collection, by Figma path ("marca/ciano") → primitive or value. */
94
+ vars?: Record<string, string>;
95
+ };
96
+ /**
97
+ * The theme of a brand table. Fails on a missing role or an unknown primitive, listing them all. A table exported
98
+ * before a role of ADDED_ROLES existed is the exception: the role is taken from the token Figma aliases it to, and
99
+ * `opts.warn` asks to export the table again. The contrast of the main text pairs (rdsContrastReport) is only
100
+ * reported, through `opts.warn`: the table is the brand as drawn in Figma, kept one to one even where a pair fails.
101
+ */
102
+ declare function rdsThemeFromTable(table: RdsBrandTable, opts?: RdsThemeOptions): RdsTheme;
103
+
104
+ /**
105
+ * validate.ts — what the emitters accept from the outside (a recipe, a brand table, a theme object).
106
+ *
107
+ * ds-core is a public API, and its output lands in a stylesheet (`emitRdsCss`) and in the rules file an AI agent
108
+ * reads (`emitClaudeMd`). A value that is not a colour could close the CSS rule and open another one, pull an
109
+ * `url()`, or carry an instruction into CLAUDE.md. So every value is checked against an allow list before it is
110
+ * written, and the check runs again in each emitter (defence in depth: a theme object can be built by hand).
111
+ *
112
+ * Allow lists, never deny lists: a colour is a hex or a closed `rgb()/rgba()/oklch()`; a variable name is lowercase
113
+ * words joined by `-` or `/`; a font family has no quote, semicolon, brace, backslash or line break.
114
+ */
115
+ declare const isSafeColor: (v: unknown) => v is string;
116
+ declare const isSafeVarName: (v: unknown) => v is string;
117
+ declare const isSafeFont: (v: unknown) => v is string;
118
+ /** Thrown when an input fails the allow list. The message lists every field that failed. */
119
+ declare class RdsValidationError extends Error {
120
+ readonly problems: string[];
121
+ constructor(where: string, problems: string[]);
122
+ }
31
123
 
32
124
  /**
33
125
  * emitCss.ts — formata o output do generateTheme num theme.css legível.
@@ -37,12 +129,38 @@ declare function rdsContrast(theme: RdsTheme, mode: RdsMode, fg: string, bg: str
37
129
 
38
130
  declare function emitCss(def: BrandDef): string;
39
131
 
132
+ /**
133
+ * emitClaudeMd.ts — a brand → the rules file (CLAUDE.md / .cursorrules / AGENTS.md) that teaches
134
+ * the AI (Claude Code, Cursor, any agent) to build with the brand on Rojão DS 2.0.
135
+ *
136
+ * 2.0 (issue #5): the file describes the [RDS] setup — components with their own CSS
137
+ * (`@rojaostudio/ds/styles/rds.css`), the generated theme file imported after it, the theme roles
138
+ * (`--surface-card`, `--text-on-primary`…) and the foundation tokens (`--space-*`, `--radius-*`,
139
+ * `--type-*`). The 1.x text (Tailwind utilities, base.css, `theme-<name>`, shadcn mapping) is gone:
140
+ * no component reads it any more.
141
+ *
142
+ * The input is either a recipe (BrandDef, theme derived by `generateRdsTheme`) or a brand table
143
+ * exported from Figma (`rdsThemeFromTable`). Colours in the table are the theme's own hex values,
144
+ * so the AI "sees" the brand. Pure (no I/O).
145
+ */
146
+
40
147
  type ClaudeMdTarget = "claude" | "cursor" | "agents";
41
148
  interface ClaudeMdOptions {
42
- /** URL de um CSS de tema hospedado, se houver. Sem default: o padrão é arquivo local. */
149
+ /** URL of a hosted theme stylesheet, if any. No default: the theme is a local file. */
43
150
  cssUrl?: string;
151
+ /** Name of the local theme file the consumer imports. Default `rds-theme.css`. */
152
+ cssFile?: string;
153
+ /** The theme, if it was already generated. Default: derived from the input. */
154
+ theme?: RdsTheme;
155
+ /** File the text is written to (only the footer changes). Default `claude`. */
156
+ target?: ClaudeMdTarget;
157
+ /**
158
+ * The install command shown in the setup, as the consumer's package manager runs it
159
+ * (`npm install @rojaostudio/ds@next`). Default `pnpm add @rojaostudio/ds`.
160
+ */
161
+ install?: string;
44
162
  }
45
- declare function emitClaudeMd(def: BrandDef, opts?: ClaudeMdOptions): string;
163
+ declare function emitClaudeMd(source: BrandDef | RdsBrandTable, opts?: ClaudeMdOptions): string;
46
164
 
47
165
  /**
48
166
  * scale.ts — deriva uma escala 50–900 a partir de UMA cor custom (1 hex). #28.
@@ -110,4 +228,4 @@ declare function isNeutralBrand(def: {
110
228
  palettes?: PaletteDefs;
111
229
  }): boolean;
112
230
 
113
- export { BrandDef, type ClaudeMdOptions, type ClaudeMdTarget, ROLES as RDS_ROLES, type RdsCssOptions, type RdsMode, type RdsTheme, SCALE_STEPS, type Scale, type ScaleStep, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, onColor, rdsContrast, refToHex, relativeLuminance, roleVar };
231
+ export { BrandDef, type ClaudeMdOptions, type ClaudeMdTarget, RDS_CONTRAST_PAIRS, RDS_NON_TEXT_PAIRS, ROLES as RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, type RdsBrandTable, type RdsContrastFailure, type RdsCssOptions, type RdsMode, type RdsTheme, type RdsThemeOptions, RdsValidationError, SCALE_STEPS, type Scale, type ScaleStep, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, isSafeColor, isSafeFont, isSafeVarName, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, roleVar };
package/dist/generate.js CHANGED
@@ -1,3 +1,3 @@
1
- export { ROLES as RDS_ROLES, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, onColor, rdsContrast, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-CFOS2RAD.js';
1
+ export { RDS_CONTRAST_PAIRS, RDS_NON_TEXT_PAIRS, ROLES as RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, RdsValidationError, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, isSafeColor, isSafeFont, isSafeVarName, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-D6HJEVJK.js';
2
2
  import './chunk-FRWRLCYM.js';
3
3
  import './chunk-ORMEWXMH.js';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { G as GenResult, R as ResolvedTheme, T as TokenMap, g as generateTheme, r as resolveTheme } from './generateTheme-CUfNpsKs.js';
2
- export { ClaudeMdOptions, ClaudeMdTarget, RDS_ROLES, RdsCssOptions, RdsMode, RdsTheme, SCALE_STEPS, Scale, ScaleStep, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, onColor, rdsContrast, refToHex, relativeLuminance, roleVar } from './generate.js';
2
+ export { ClaudeMdOptions, ClaudeMdTarget, RDS_CONTRAST_PAIRS, RDS_NON_TEXT_PAIRS, RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, RdsBrandTable, RdsContrastFailure, RdsCssOptions, RdsMode, RdsTheme, RdsThemeOptions, RdsValidationError, SCALE_STEPS, Scale, ScaleStep, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, isSafeColor, isSafeFont, isSafeVarName, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, roleVar } from './generate.js';
3
3
  export { A as Archetype, B as BrandColors, a as BrandDef, C as ColorRef, D as DarkStrategy, F as FontDef, P as PaletteRef, S as ScaleToken, T as TextStyle, b as TextStyleName, c as TypeScale } from './recipe.schema-Cq7e82nx.js';
4
4
  export { primitives, tokens } from './tokens.js';
5
5
  export { RecipeName, recipes } from './recipes.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { recipes } from './chunk-3QTDO2HW.js';
2
- export { ROLES as RDS_ROLES, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, onColor, rdsContrast, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-CFOS2RAD.js';
1
+ export { recipes } from './chunk-PV6GFEAM.js';
2
+ export { RDS_CONTRAST_PAIRS, RDS_NON_TEXT_PAIRS, ROLES as RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, RdsValidationError, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, isSafeColor, isSafeFont, isSafeVarName, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-D6HJEVJK.js';
3
3
  export { primitives, tokens } from './chunk-FRWRLCYM.js';
4
4
  import './chunk-ORMEWXMH.js';
package/dist/recipes.d.ts CHANGED
@@ -15,7 +15,11 @@ import { a as BrandDef } from './recipe.schema-Cq7e82nx.js';
15
15
  * ele não descreve marca de ninguém, descreve ponto de partida.
16
16
  */
17
17
 
18
- /** rojao — navy + flare accent on zinc, Inter. The `rojao` brand of the Figma [RDS] (2.0). */
18
+ /**
19
+ * rojao — navy + flare accent on zinc, Inter. The `rojao` brand of the Figma [RDS] (2.0).
20
+ * text/heading is the primary (navy) on light, as in the [RDS] base collection: the logo pairs are navy and
21
+ * orange, white and orange. The orange title is the Heading's tone=accent, not the default.
22
+ */
19
23
  declare const rojao: BrandDef;
20
24
  declare const recipes: {
21
25
  readonly rojao: BrandDef;
package/dist/recipes.js CHANGED
@@ -1,2 +1,2 @@
1
- export { recipes, rojao } from './chunk-3QTDO2HW.js';
1
+ export { recipes, rojao } from './chunk-PV6GFEAM.js';
2
2
  import './chunk-ORMEWXMH.js';
package/dist/themes.js CHANGED
@@ -1,4 +1,4 @@
1
- import { generateTheme } from './chunk-CFOS2RAD.js';
1
+ import { generateTheme } from './chunk-D6HJEVJK.js';
2
2
  import './chunk-FRWRLCYM.js';
3
3
  import { __spreadValues, __objRest } from './chunk-ORMEWXMH.js';
4
4
 
@@ -0,0 +1,58 @@
1
+ /* global figma */
2
+ // Exports one brand of the [RDS] Base Tokens file as a brand table for rdsThemeFromTable().
3
+ // Run inside the Base Tokens file with the Figma Plugin API (a plugin console or an agent with use_figma),
4
+ // after setting BRAND to the mode name of the `base` collection. The return value is the JSON to save.
5
+ //
6
+ // Each theme role is followed through the aliases of the `base` collection (in the brand's mode) down to a
7
+ // primitive of the [RDS] Primitives library, kept by name ("accyan/400") with the colour Figma resolves.
8
+ // The brand's own variables in the `brand` collection (`<brand>/<name>`, as marca/ciano) come along in `vars`.
9
+ const BRAND = "rojao";
10
+
11
+ const cols = await figma.variables.getLocalVariableCollectionsAsync();
12
+ const base = cols.find((c) => c.name === "base");
13
+ const theme = cols.find((c) => c.name === "theme");
14
+ if (!base || !theme) throw new Error("open the [RDS] Base Tokens file");
15
+ const brandMode = base.modes.find((m) => m.name === BRAND);
16
+ if (!brandMode) throw new Error(`no brand "${BRAND}" in base: ${base.modes.map((m) => m.name).join(", ")}`);
17
+ const local = new Set(cols.map((c) => c.id));
18
+
19
+ const hex = (c) =>
20
+ "#" +
21
+ [c.r, c.g, c.b].map((x) => Math.round(x * 255).toString(16).padStart(2, "0")).join("") +
22
+ (c.a !== undefined && c.a < 1 ? Math.round(c.a * 255).toString(16).padStart(2, "0") : "");
23
+ const literal = (v, val) => (v.resolvedType === "COLOR" ? hex(val) : String(val));
24
+ const isAlias = (val) => val && typeof val === "object" && val.type === "VARIABLE_ALIAS";
25
+
26
+ const primitives = {};
27
+ async function resolve(v, themeMode) {
28
+ if (!local.has(v.variableCollectionId)) {
29
+ // A Primitives library variable: keep its name, record what it resolves to.
30
+ const val = Object.values(v.valuesByMode)[0];
31
+ if (isAlias(val)) return resolve(await figma.variables.getVariableByIdAsync(val.id), themeMode);
32
+ primitives[v.name] = literal(v, val);
33
+ return v.name;
34
+ }
35
+ const modeId = v.variableCollectionId === base.id ? brandMode.modeId
36
+ : v.variableCollectionId === theme.id ? themeMode : Object.keys(v.valuesByMode)[0];
37
+ const val = v.valuesByMode[modeId] ?? Object.values(v.valuesByMode)[0];
38
+ if (isAlias(val)) return resolve(await figma.variables.getVariableByIdAsync(val.id), themeMode);
39
+ return literal(v, val);
40
+ }
41
+
42
+ const modes = {};
43
+ for (const m of theme.modes) {
44
+ const roles = {};
45
+ for (const id of theme.variableIds) {
46
+ const v = await figma.variables.getVariableByIdAsync(id);
47
+ roles[v.name] = await resolve(v, m.modeId);
48
+ }
49
+ modes[m.name] = roles;
50
+ }
51
+ const vars = {};
52
+ const brandCol = cols.find((c) => c.name === "brand");
53
+ for (const id of brandCol?.variableIds ?? []) {
54
+ const v = await figma.variables.getVariableByIdAsync(id);
55
+ if (v.name.startsWith(`${BRAND}/`)) vars[v.name] = await resolve(v, theme.modes[0].modeId);
56
+ }
57
+ const sorted = Object.fromEntries(Object.entries(primitives).sort(([a], [b]) => a.localeCompare(b)));
58
+ return { $schema: "rds-brand-table/1", name: BRAND, primitives: sorted, modes, vars };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rojaostudio/ds-core",
3
- "version": "1.1.0-next.1",
3
+ "version": "1.1.0-next.11",
4
4
  "description": "O motor do Rojão DS — tokens, derivação de tema e emissores. Sem React, sem Tailwind, sem CSS.",
5
5
  "license": "MIT",
6
6
  "author": "Rojão Studio (https://rojao.studio)",
@@ -54,7 +54,8 @@
54
54
  "files": [
55
55
  "LICENSE",
56
56
  "README.md",
57
- "dist"
57
+ "dist",
58
+ "figma/export-brand.js"
58
59
  ],
59
60
  "devDependencies": {
60
61
  "eslint": "^9.18.0",