@rojaostudio/ds-core 1.1.0-next.2 → 1.1.0-next.4
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/README.md +7 -4
- package/dist/{chunk-DZAJKHNW.js → chunk-UD53AUQQ.js} +148 -131
- package/dist/generate.d.ts +29 -3
- package/dist/generate.js +1 -1
- package/dist/index.js +1 -1
- package/dist/themes.js +1 -1
- package/figma/export-brand.js +8 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ npm i @rojaostudio/ds-core
|
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
|
-
import {
|
|
12
|
+
import { generateRdsTheme, emitRdsCss, emitClaudeMd } from '@rojaostudio/ds-core/generate';
|
|
13
13
|
|
|
14
14
|
const brand = {
|
|
15
15
|
name: 'acme',
|
|
@@ -19,10 +19,13 @@ const brand = {
|
|
|
19
19
|
fonts: { body: 'inter' },
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
emitClaudeMd(brand);
|
|
22
|
+
emitRdsCss(generateRdsTheme(brand)); // rds-theme.css — import it AFTER @rojaostudio/ds/styles/rds.css
|
|
23
|
+
emitClaudeMd(brand); // the same brand as rules for Claude Code, Cursor or AGENTS.md
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
Or, without writing code: `npx rojao-ds init` (the [`rojao-ds`](https://www.npmjs.com/package/rojao-ds) CLI)
|
|
27
|
+
writes both files into your project.
|
|
28
|
+
|
|
26
29
|
One color goes in. What comes out is an OKLCH scale with an adaptive contrast floor, semantic
|
|
27
30
|
roles for surface, text, border and brand, and the full light/dark pair — verified against
|
|
28
31
|
WCAG AA on all three surfaces, by test, on every build.
|
|
@@ -41,7 +44,7 @@ Components, styles and the React Native target live in
|
|
|
41
44
|
|
|
42
45
|
| Entry | What's in it |
|
|
43
46
|
|---|---|
|
|
44
|
-
| `@rojaostudio/ds-core/generate` | `
|
|
47
|
+
| `@rojaostudio/ds-core/generate` | `generateRdsTheme`, `rdsThemeFromTable`, `emitRdsCss`, `emitClaudeMd`, `generateTheme`/`emitCss` (1.x), `buildScale`, `contrastRatio` |
|
|
45
48
|
| `@rojaostudio/ds-core/tokens` | `primitives` — the raw palettes and scales |
|
|
46
49
|
| `@rojaostudio/ds-core/recipes` | the reference brand definition |
|
|
47
50
|
| `@rojaostudio/ds-core/themes` | the preset catalogue: a starting palette per niche |
|
|
@@ -1092,7 +1092,7 @@ ${lines.join("\n")}
|
|
|
1092
1092
|
const scopes = scope.split(",").map((s) => s.trim());
|
|
1093
1093
|
const darkSel = scopes.map((s) => s === ":root" ? `:root${dark}, ${dark}` : `${s}${dark}, ${dark} ${s}`).join(", ");
|
|
1094
1094
|
return [
|
|
1095
|
-
block2(scope, theme.light),
|
|
1095
|
+
block2(scope, __spreadValues(__spreadValues({}, theme.light), theme.vars)),
|
|
1096
1096
|
block2(darkSel, theme.dark, theme.light),
|
|
1097
1097
|
block2(plate, theme.brand, theme.light)
|
|
1098
1098
|
].join("\n\n") + "\n";
|
|
@@ -1123,6 +1123,19 @@ function rdsThemeFromTable(table) {
|
|
|
1123
1123
|
} else out[mode][roleVar(role)] = ref2;
|
|
1124
1124
|
}
|
|
1125
1125
|
}
|
|
1126
|
+
const valueOf = (where, ref2) => {
|
|
1127
|
+
if (!isColourRef(ref2)) return ref2;
|
|
1128
|
+
const value = table.primitives[ref2];
|
|
1129
|
+
if (value === void 0) problems.push(`${where} points to unknown primitive "${ref2}"`);
|
|
1130
|
+
return value == null ? void 0 : value.toLowerCase();
|
|
1131
|
+
};
|
|
1132
|
+
if (table.vars) {
|
|
1133
|
+
out.vars = {};
|
|
1134
|
+
for (const [name, ref2] of Object.entries(table.vars)) {
|
|
1135
|
+
const value = valueOf(`var "${name}"`, ref2);
|
|
1136
|
+
if (value !== void 0) out.vars[roleVar(name)] = value;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1126
1139
|
if (problems.length) throw new Error(`rdsThemeFromTable(${table.name}):
|
|
1127
1140
|
${problems.join("\n ")}`);
|
|
1128
1141
|
return out;
|
|
@@ -1199,152 +1212,156 @@ ${lightBlock}${darkBlock}
|
|
|
1199
1212
|
}
|
|
1200
1213
|
|
|
1201
1214
|
// generate/emitClaudeMd.ts
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
{
|
|
1220
|
-
{
|
|
1221
|
-
{
|
|
1222
|
-
{
|
|
1223
|
-
{
|
|
1224
|
-
{
|
|
1225
|
-
{ key: "--brand-accent-light", use: "fundo tingido do accent" },
|
|
1226
|
-
{ key: "--brand-on-accent", use: "texto sobre o accent" },
|
|
1227
|
-
{ key: "--surface-page", use: "fundo da p\xE1gina" },
|
|
1228
|
-
{ key: "--surface-default", use: "cards, pain\xE9is" },
|
|
1229
|
-
{ key: "--surface-raised", use: "superf\xEDcie elevada/hover" },
|
|
1230
|
-
{ key: "--border-subtle", use: "divis\xF3rias fracas" },
|
|
1231
|
-
{ key: "--border-default", use: "bordas de inputs e cards" },
|
|
1232
|
-
{ key: "--border-strong", use: "bordas enfatizadas" },
|
|
1233
|
-
{ key: "--border-focus", use: "anel de foco" },
|
|
1234
|
-
{ key: "--text-primary", use: "t\xEDtulos, corpo" },
|
|
1235
|
-
{ key: "--text-secondary", use: "texto de apoio" },
|
|
1236
|
-
{ key: "--text-muted", use: "legendas, metadados" },
|
|
1237
|
-
{ key: "--text-inverse", use: "texto sobre superf\xEDcies invertidas" },
|
|
1238
|
-
{ key: "--icon-default", use: "cor padr\xE3o de \xEDcone" }
|
|
1215
|
+
var ROLE_DOCS = [
|
|
1216
|
+
{ role: "colors/primary/default", use: "primary action, brand fills" },
|
|
1217
|
+
{ role: "text/on/primary", use: "text/icon on the primary fill" },
|
|
1218
|
+
{ role: "colors/primary/active", use: "pressed primary" },
|
|
1219
|
+
{ role: "colors/secondary/default", use: "secondary brand fill (with `--text-on-secondary`)" },
|
|
1220
|
+
{ role: "colors/accent/default", use: "highlight \u2014 sparingly (with `--text-on-accent`)" },
|
|
1221
|
+
{ role: "surface/page", use: "page background" },
|
|
1222
|
+
{ role: "surface/card", use: "cards" },
|
|
1223
|
+
{ role: "surface/panel", use: "panels, side areas" },
|
|
1224
|
+
{ role: "surface/muted", use: "muted areas, wells" },
|
|
1225
|
+
{ role: "surface/tint/default", use: "selected / tinted fill (with `--text-on-tint`)" },
|
|
1226
|
+
{ role: "border/default", use: "dividers, card borders" },
|
|
1227
|
+
{ role: "border/strong", use: "control borders, emphasis" },
|
|
1228
|
+
{ role: "focus/ring", use: "focus ring" },
|
|
1229
|
+
{ role: "text/heading", use: "headings" },
|
|
1230
|
+
{ role: "text/body", use: "body text" },
|
|
1231
|
+
{ role: "text/muted", use: "supporting text" },
|
|
1232
|
+
{ role: "text/subtle", use: "captions, metadata" },
|
|
1233
|
+
{ role: "text/link", use: "links" },
|
|
1234
|
+
{ role: "colors/state/error", use: "error fill (with `--text-on-error`)" },
|
|
1235
|
+
{ role: "text/error", use: "error message text" },
|
|
1236
|
+
{ role: "colors/state/success", use: "success fill (with `--text-on-success`)" },
|
|
1237
|
+
{ role: "colors/state/warning", use: "warning fill (with `--text-on-warning`)" }
|
|
1239
1238
|
];
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1239
|
+
var CLAUDE_MD_COMPONENTS = [
|
|
1240
|
+
"button",
|
|
1241
|
+
"icon-button",
|
|
1242
|
+
"tooltip",
|
|
1243
|
+
"input",
|
|
1244
|
+
"textarea",
|
|
1245
|
+
"select",
|
|
1246
|
+
"checkbox",
|
|
1247
|
+
"radio-group",
|
|
1248
|
+
"switch",
|
|
1249
|
+
"card",
|
|
1250
|
+
"dialog",
|
|
1251
|
+
"sheet",
|
|
1252
|
+
"tabs",
|
|
1253
|
+
"table",
|
|
1254
|
+
"toast",
|
|
1255
|
+
"badge",
|
|
1256
|
+
"alert",
|
|
1257
|
+
"avatar",
|
|
1258
|
+
"dropdown-menu",
|
|
1259
|
+
"page-header",
|
|
1260
|
+
"sidebar",
|
|
1261
|
+
"empty",
|
|
1262
|
+
"skeleton",
|
|
1263
|
+
"pagination"
|
|
1264
|
+
];
|
|
1265
|
+
var CLAUDE_MD_FOUNDATION = {
|
|
1266
|
+
space: ["--space-4", "--space-8", "--space-12", "--space-16", "--space-24", "--space-32", "--space-40", "--space-48", "--space-56", "--space-64"],
|
|
1267
|
+
radius: ["--radius-xs", "--radius-control", "--radius-field", "--radius-card", "--radius-container", "--radius-full"],
|
|
1268
|
+
type: ["heading", "lead", "body", "label", "button", "small", "caption"],
|
|
1269
|
+
font: "--type-font-stack"
|
|
1270
|
+
};
|
|
1271
|
+
function isTable(x) {
|
|
1272
|
+
return typeof x === "object" && x !== null && "modes" in x && "primitives" in x;
|
|
1254
1273
|
}
|
|
1255
1274
|
function fileLabel(target) {
|
|
1256
1275
|
return target === "cursor" ? ".cursorrules" : target === "agents" ? "AGENTS.md" : "CLAUDE.md";
|
|
1257
1276
|
}
|
|
1258
|
-
function
|
|
1277
|
+
function roleTable(theme) {
|
|
1278
|
+
const rows = ROLE_DOCS.filter((r) => roleVar(r.role) in theme.light).map((r) => {
|
|
1279
|
+
var _a;
|
|
1280
|
+
const v = roleVar(r.role);
|
|
1281
|
+
return `| \`${v}\` | ${r.use} | \`${theme.light[v]}\` | \`${(_a = theme.dark[v]) != null ? _a : theme.light[v]}\` |`;
|
|
1282
|
+
});
|
|
1283
|
+
return ["| Token | Use for | Light | Dark |", "|---|---|---|---|", ...rows].join("\n");
|
|
1284
|
+
}
|
|
1285
|
+
function emitClaudeMd(source, opts = {}) {
|
|
1259
1286
|
var _a, _b, _c;
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1262
|
-
const Name =
|
|
1263
|
-
const
|
|
1264
|
-
const
|
|
1265
|
-
const
|
|
1266
|
-
const
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
return `# Design System \u2014 ${Name}
|
|
1272
|
-
|
|
1273
|
-
> This project uses the **${Name}** design system. When you build any UI \u2014
|
|
1274
|
-
> components, pages, screens \u2014 follow the rules below so every screen looks like the
|
|
1275
|
-
> same product. Do not invent colors, fonts, spacing, or radii.
|
|
1287
|
+
const theme = (_a = opts.theme) != null ? _a : isTable(source) ? rdsThemeFromTable(source) : generateRdsTheme(source);
|
|
1288
|
+
const name = source.name;
|
|
1289
|
+
const Name = name ? name[0].toUpperCase() + name.slice(1) : "Brand";
|
|
1290
|
+
const description = !isTable(source) && source.description ? ` \u2014 ${source.description}` : "";
|
|
1291
|
+
const cssFile = (_b = opts.cssFile) != null ? _b : "rds-theme.css";
|
|
1292
|
+
const target = (_c = opts.target) != null ? _c : "claude";
|
|
1293
|
+
const f = CLAUDE_MD_FOUNDATION;
|
|
1294
|
+
const own = theme.vars ? Object.keys(theme.vars) : [];
|
|
1295
|
+
const themeImport = opts.cssUrl ? `@import "@rojaostudio/ds/styles/rds.css";
|
|
1296
|
+
@import url("${opts.cssUrl}"); /* the brand theme, AFTER rds.css */` : `@import "@rojaostudio/ds/styles/rds.css";
|
|
1297
|
+
@import "./${cssFile}"; /* the brand theme, AFTER rds.css */`;
|
|
1298
|
+
return `# Design System \u2014 ${Name}
|
|
1276
1299
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
- **Body font:** ${fontTitle(bodyFont)} \xB7 **Display font:** ${fontTitle(displayFont)} (load from Google Fonts; never substitute)
|
|
1281
|
-
- **Default radius:** \`${primitives.radius.md}px\` (\`rounded-lg\`). Pills/avatars fully round.
|
|
1282
|
-
- **Light AND dark are first-class** \u2014 every screen must work in both.
|
|
1300
|
+
> This project uses **Roj\xE3o DS 2.0** with the **${Name}** brand${description}. When you build any
|
|
1301
|
+
> UI \u2014 components, pages, screens \u2014 follow the rules below so every screen looks like the same
|
|
1302
|
+
> product. Do not invent colors, fonts, spacing or radii.
|
|
1283
1303
|
|
|
1284
|
-
##
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
> One sheet can hold **several themes** \u2014 each as its own \`theme-<name>\` class.
|
|
1300
|
-
> Switch with the class (one product per app, or many on one page).${cssUrl ? `
|
|
1301
|
-
> Edit the theme at the source and this same URL updates **all your apps at once**.` : ""}
|
|
1302
|
-
3. Style everything with the semantic tokens below, as CSS variables \u2014 they already
|
|
1303
|
-
flip between light and dark. You never write a hex value in a component.
|
|
1304
|
-
\`\`\`css
|
|
1305
|
-
.card { background: var(--surface-default); color: var(--text-primary);
|
|
1306
|
-
border: 1px solid var(--border-default); border-radius: ${primitives.radius.md}px; }
|
|
1307
|
-
\`\`\`
|
|
1304
|
+
## Setup (once per app)
|
|
1305
|
+
\`\`\`bash
|
|
1306
|
+
pnpm add @rojaostudio/ds
|
|
1307
|
+
\`\`\`
|
|
1308
|
+
\`\`\`css
|
|
1309
|
+
/* root stylesheet */
|
|
1310
|
+
${themeImport}
|
|
1311
|
+
\`\`\`
|
|
1312
|
+
- \`rds.css\` carries the tokens and every component's styles, in cascade layers
|
|
1313
|
+
(\`rds.theme\`, \`rds.tokens\`, \`rds.components\`). No Tailwind, no other stylesheet.
|
|
1314
|
+
- ${opts.cssUrl ? `The theme is served from \`${opts.cssUrl}\`.` : `\`${cssFile}\` is the brand theme, generated \u2014 a file **you own**; commit it. Regenerate it
|
|
1315
|
+
with \`npx rojao-ds init\` instead of editing it by hand.`}
|
|
1316
|
+
- Dark mode: \`class="dark"\` on \`<html>\` (or any element). Light AND dark are first-class.
|
|
1317
|
+
- Brand plate: \`class="ds-plate"\` on a section paints it with the primary color; the roles inside
|
|
1318
|
+
flip so text and components stay legible.
|
|
1308
1319
|
|
|
1309
|
-
##
|
|
1310
|
-
|
|
1320
|
+
## Components first
|
|
1321
|
+
Use the design system components before writing your own markup. Import each from its own path:
|
|
1322
|
+
\`\`\`tsx
|
|
1323
|
+
import { Button } from "@rojaostudio/ds/components/button";
|
|
1324
|
+
import { IconButton } from "@rojaostudio/ds/components/icon-button";
|
|
1325
|
+
import { Tooltip } from "@rojaostudio/ds/components/tooltip";
|
|
1326
|
+
\`\`\`
|
|
1327
|
+
Available, among others: ${CLAUDE_MD_COMPONENTS.map((c) => `\`${c}\``).join(" \xB7 ")}.
|
|
1328
|
+
- Components bring their own CSS (inside \`rds.css\`). Don't restyle their internals, don't wrap
|
|
1329
|
+
them in Tailwind classes, don't rebuild them with shadcn/ui.
|
|
1330
|
+
- Every \`IconButton\` goes inside a \`Tooltip\` with the same text as its \`label\`.
|
|
1311
1331
|
|
|
1312
|
-
##
|
|
1313
|
-
|
|
1332
|
+
## Theme roles \u2014 ALWAYS use these in your own CSS, NEVER hardcode a color
|
|
1333
|
+
They already flip between light, dark and the brand plate.
|
|
1334
|
+
${roleTable(theme)}
|
|
1314
1335
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1336
|
+
Every role pairs a fill with its \`--text-on-*\` text. Example:
|
|
1337
|
+
\`\`\`css
|
|
1338
|
+
.panel { background: var(--surface-card); color: var(--text-body);
|
|
1339
|
+
border: var(--border-width) solid var(--border-default);
|
|
1340
|
+
border-radius: var(--radius-card); padding: var(--space-16); }
|
|
1341
|
+
\`\`\`${own.length ? `
|
|
1317
1342
|
|
|
1318
|
-
|
|
1319
|
-
**Line heights:** ${Object.entries(primitives.lineHeight).map(([k, v]) => `\`${k}\`=${v}`).join(" \xB7 ")}
|
|
1343
|
+
Brand's own variables (same value in every mode): ${own.map((v) => `\`${v}\``).join(" \xB7 ")}.` : ""}
|
|
1320
1344
|
|
|
1321
|
-
##
|
|
1322
|
-
${
|
|
1345
|
+
## Foundation tokens
|
|
1346
|
+
- **Spacing** (padding, margin, gap) \u2014 ONLY: ${f.space.map((v) => `\`${v}\``).join(" \xB7 ")}
|
|
1347
|
+
- **Radius:** ${f.radius.map((v) => `\`${v}\``).join(" \xB7 ")}
|
|
1348
|
+
- **Type** \u2014 \`--type-<style>-size\` / \`--type-<style>-line\`, styles: ${f.type.map((v) => `\`${v}\``).join(" \xB7 ")}
|
|
1349
|
+
- **Font:** \`font-family: var(${f.font})\` \u2014 never substitute.
|
|
1323
1350
|
|
|
1324
1351
|
## Rules (do / don't)
|
|
1325
|
-
- \u2705 Primary
|
|
1326
|
-
- \u2705
|
|
1327
|
-
- \u2705
|
|
1328
|
-
- \u2705
|
|
1329
|
-
- \u2705
|
|
1330
|
-
- \
|
|
1331
|
-
- \u274C Never
|
|
1332
|
-
|
|
1333
|
-
- \u274C Never
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
You are NOT replacing shadcn. Use shadcn/ui components and apply THIS brand as the theme:
|
|
1337
|
-
map the tokens above onto shadcn's CSS variables (\`--background\` \u2192 \`--surface-page\`,
|
|
1338
|
-
\`--foreground\` \u2192 \`--text-primary\`, \`--primary\` \u2192 \`--brand-primary\`, \`--border\` \u2192
|
|
1339
|
-
\`--border-default\`, \`--ring\` \u2192 \`--border-focus\`, \`--radius\` \u2192 \`${primitives.radius.md}px\`).
|
|
1340
|
-
Build with shadcn's component shapes; paint them with these tokens. Don't restyle components from scratch.
|
|
1341
|
-
|
|
1342
|
-
## Primitive palette (reference \u2014 prefer the semantic tokens above)
|
|
1343
|
-
\`\`\`
|
|
1344
|
-
neutral 50 ${neutral[50]} \xB7 500 ${neutral[500]} \xB7 900 ${neutral[900]}
|
|
1345
|
-
\`\`\`
|
|
1352
|
+
- \u2705 Primary action = \`Button\` (it already paints \`--colors-primary-default\` + \`--text-on-primary\`).
|
|
1353
|
+
- \u2705 Page uses \`--surface-page\`; cards \`--surface-card\`; body text \`--text-body\`.
|
|
1354
|
+
- \u2705 \`--colors-accent-default\` is the highlight \u2014 use it sparingly.
|
|
1355
|
+
- \u2705 Spacing, radius and type come ONLY from the foundation tokens above.
|
|
1356
|
+
- \u2705 Your own CSS is unlayered, so it already wins over the design system \u2014 no \`!important\`.
|
|
1357
|
+
- \u274C Never write a raw hex (\`#000\`) or a Tailwind palette class (\`bg-emerald-500\`) in a component.
|
|
1358
|
+
- \u274C Never use a brand fill as a border or as text on a surface: contrast is guaranteed only for a
|
|
1359
|
+
fill + its \`--text-on-*\`.
|
|
1360
|
+
- \u274C Never hardcode dark-mode colors \u2014 the roles already flip. Don't add a second accent color.
|
|
1361
|
+
- \u274C Don't use the 1.x stylesheet (\`styles/base.css\`), its utilities (\`bg-surface-*\`, \`text-fg-*\`) or
|
|
1362
|
+
\`theme-<name>\` classes.
|
|
1346
1363
|
|
|
1347
|
-
<!-- Generated by Roj\xE3o DS \u2014 file: ${fileLabel(
|
|
1364
|
+
<!-- Generated by Roj\xE3o DS (npx rojao-ds init) \u2014 file: ${fileLabel(target)}. Claude Code, Cursor and other agents read it from the project root. -->
|
|
1348
1365
|
`;
|
|
1349
1366
|
}
|
|
1350
1367
|
|
package/dist/generate.d.ts
CHANGED
|
@@ -3,7 +3,10 @@ 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
|
+
};
|
|
7
10
|
/**
|
|
8
11
|
* Roles of the [RDS] theme collection, in Figma order: [name, dark source, brand source].
|
|
9
12
|
* The light mode always reads the plain base token. In dark and brand, Figma points either to the
|
|
@@ -38,6 +41,8 @@ type RdsBrandTable = {
|
|
|
38
41
|
name: string;
|
|
39
42
|
primitives: Record<string, string>;
|
|
40
43
|
modes: Record<RdsMode, Record<string, string>>;
|
|
44
|
+
/** The brand's own variables of the `brand` collection, by Figma path ("acassius/cyan") → primitive or value. */
|
|
45
|
+
vars?: Record<string, string>;
|
|
41
46
|
};
|
|
42
47
|
/** The theme of a brand table. Fails on a missing role or an unknown primitive, listing them all. */
|
|
43
48
|
declare function rdsThemeFromTable(table: RdsBrandTable): RdsTheme;
|
|
@@ -50,12 +55,33 @@ declare function rdsThemeFromTable(table: RdsBrandTable): RdsTheme;
|
|
|
50
55
|
|
|
51
56
|
declare function emitCss(def: BrandDef): string;
|
|
52
57
|
|
|
58
|
+
/**
|
|
59
|
+
* emitClaudeMd.ts — a brand → the rules file (CLAUDE.md / .cursorrules / AGENTS.md) that teaches
|
|
60
|
+
* the AI (Claude Code, Cursor, any agent) to build with the brand on Rojão DS 2.0.
|
|
61
|
+
*
|
|
62
|
+
* 2.0 (issue #5): the file describes the [RDS] setup — components with their own CSS
|
|
63
|
+
* (`@rojaostudio/ds/styles/rds.css`), the generated theme file imported after it, the theme roles
|
|
64
|
+
* (`--surface-card`, `--text-on-primary`…) and the foundation tokens (`--space-*`, `--radius-*`,
|
|
65
|
+
* `--type-*`). The 1.x text (Tailwind utilities, base.css, `theme-<name>`, shadcn mapping) is gone:
|
|
66
|
+
* no component reads it any more.
|
|
67
|
+
*
|
|
68
|
+
* The input is either a recipe (BrandDef, theme derived by `generateRdsTheme`) or a brand table
|
|
69
|
+
* exported from Figma (`rdsThemeFromTable`). Colours in the table are the theme's own hex values,
|
|
70
|
+
* so the AI "sees" the brand. Pure (no I/O).
|
|
71
|
+
*/
|
|
72
|
+
|
|
53
73
|
type ClaudeMdTarget = "claude" | "cursor" | "agents";
|
|
54
74
|
interface ClaudeMdOptions {
|
|
55
|
-
/** URL
|
|
75
|
+
/** URL of a hosted theme stylesheet, if any. No default: the theme is a local file. */
|
|
56
76
|
cssUrl?: string;
|
|
77
|
+
/** Name of the local theme file the consumer imports. Default `rds-theme.css`. */
|
|
78
|
+
cssFile?: string;
|
|
79
|
+
/** The theme, if it was already generated. Default: derived from the input. */
|
|
80
|
+
theme?: RdsTheme;
|
|
81
|
+
/** File the text is written to (only the footer changes). Default `claude`. */
|
|
82
|
+
target?: ClaudeMdTarget;
|
|
57
83
|
}
|
|
58
|
-
declare function emitClaudeMd(
|
|
84
|
+
declare function emitClaudeMd(source: BrandDef | RdsBrandTable, opts?: ClaudeMdOptions): string;
|
|
59
85
|
|
|
60
86
|
/**
|
|
61
87
|
* scale.ts — deriva uma escala 50–900 a partir de UMA cor custom (1 hex). #28.
|
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, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-
|
|
1
|
+
export { ROLES as RDS_ROLES, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, onColor, rdsContrast, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-UD53AUQQ.js';
|
|
2
2
|
import './chunk-FRWRLCYM.js';
|
|
3
3
|
import './chunk-ORMEWXMH.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
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, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-
|
|
2
|
+
export { ROLES as RDS_ROLES, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, onColor, rdsContrast, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-UD53AUQQ.js';
|
|
3
3
|
export { primitives, tokens } from './chunk-FRWRLCYM.js';
|
|
4
4
|
import './chunk-ORMEWXMH.js';
|
package/dist/themes.js
CHANGED
package/figma/export-brand.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
//
|
|
6
6
|
// Each theme role is followed through the aliases of the `base` collection (in the brand's mode) down to a
|
|
7
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 acassius/cyan) come along in `vars`.
|
|
8
9
|
const BRAND = "rojao";
|
|
9
10
|
|
|
10
11
|
const cols = await figma.variables.getLocalVariableCollectionsAsync();
|
|
@@ -47,5 +48,11 @@ for (const m of theme.modes) {
|
|
|
47
48
|
}
|
|
48
49
|
modes[m.name] = roles;
|
|
49
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
|
+
}
|
|
50
57
|
const sorted = Object.fromEntries(Object.entries(primitives).sort(([a], [b]) => a.localeCompare(b)));
|
|
51
|
-
return { $schema: "rds-brand-table/1", name: BRAND, primitives: sorted, modes };
|
|
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.
|
|
3
|
+
"version": "1.1.0-next.4",
|
|
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)",
|