@assure-one/design-system 1.37.0 → 1.39.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.
Files changed (51) hide show
  1. package/README.md +60 -16
  2. package/codemods/README.md +90 -16
  3. package/codemods/lib/entry-map.mjs +315 -0
  4. package/codemods/lib/registry.mjs +2 -0
  5. package/codemods/transforms/cm-01-entry-imports.mjs +202 -0
  6. package/codemods/transforms/cm-18-data-table-parts.mjs +234 -0
  7. package/dist/brand-theme-B6rem2II.d.ts +59 -0
  8. package/dist/css/components.css +1 -1
  9. package/dist/css/legacy-aliases.css +8 -0
  10. package/dist/css/tokens.css +14 -6
  11. package/dist/design-system-provider-BM_F1C13.d.ts +143 -0
  12. package/dist/icons/index.d.ts +441 -3
  13. package/dist/icons/index.js.map +1 -1
  14. package/dist/index-BAAuIQqP.d.ts +2579 -0
  15. package/dist/index-DxBjmBBp.d.ts +1038 -0
  16. package/dist/index.d.ts +1017 -3293
  17. package/dist/index.js +7598 -5906
  18. package/dist/index.js.map +1 -1
  19. package/dist/legacy/index.d.ts +9 -0
  20. package/dist/legacy/index.js +3 -0
  21. package/dist/legacy/index.js.map +1 -0
  22. package/dist/next/index.d.ts +2 -1
  23. package/dist/reference-BkDbrPIB.d.ts +310 -0
  24. package/dist/styles.css +1 -1
  25. package/dist/suite/index.d.ts +4 -0
  26. package/dist/suite/index.js +3 -0
  27. package/dist/suite/index.js.map +1 -0
  28. package/dist/{system-CyAVbHhM.d.ts → system-DfJIVBlJ.d.ts} +1 -310
  29. package/dist/tokens/index.d.ts +3 -61
  30. package/dist/tokens/index.js +3 -3
  31. package/dist/tokens/index.js.map +1 -1
  32. package/dist/{design-system-provider-C8UlFe52.d.ts → types-k_kjwipx.d.ts} +69 -142
  33. package/dist/vocab-DornLJbW.d.ts +76 -0
  34. package/docs/components.md +62 -32
  35. package/docs/components.registry.json +2415 -465
  36. package/docs/for-ai-agents.md +2 -0
  37. package/eslint-config/index.mjs +210 -0
  38. package/eslint-config/stylelint-rules.mjs +54 -0
  39. package/eslint-config/stylelint.mjs +73 -0
  40. package/package.json +21 -3
  41. package/public/brand/assure-audit-mark.svg +6 -0
  42. package/public/brand/assure-audit.svg +8 -0
  43. package/public/brand/assure-books-mark.svg +6 -0
  44. package/public/brand/assure-books.svg +8 -0
  45. package/public/brand/assure-pro-mark.svg +6 -0
  46. package/public/brand/assure-pro.svg +8 -0
  47. package/public/brand/assure-tax-mark.svg +6 -0
  48. package/public/brand/assure-tax.svg +8 -0
  49. package/public/brand/mycpe-teams-mark.svg +36 -0
  50. package/public/brand/mycpe-teams.svg +66 -0
  51. package/dist/index-vztxMSfl.d.ts +0 -512
@@ -0,0 +1,202 @@
1
+ /**
2
+ * CM-01 — root imports → `/suite` and `/legacy` (class R; plan §29 seq 1,
3
+ * registry `C-ROOT-SUITE`, lands with W8-02).
4
+ *
5
+ * W8-02 gave the shared Assure patterns their own entry and put the frozen and
6
+ * product-owned modules behind `/legacy`. Both are re-export barrels over the
7
+ * very same module instances the root exports, so moving an import is a pure
8
+ * path change: no duplicated context, no behaviour difference, nothing to test
9
+ * afterwards. The root keeps the names through 1.x and drops them in 2.0.
10
+ *
11
+ * ## What it rewrites
12
+ *
13
+ * ```tsx
14
+ * import { Button, AgreementViewer, TimeLogger } from "@assure-one/design-system";
15
+ * ```
16
+ *
17
+ * becomes
18
+ *
19
+ * ```tsx
20
+ * import { Button } from "@assure-one/design-system";
21
+ * import { AgreementViewer } from "@assure-one/design-system/suite";
22
+ * import { TimeLogger } from "@assure-one/design-system/legacy";
23
+ * ```
24
+ *
25
+ * A declaration whose names all move keeps its shape and only its module
26
+ * specifier changes. `import type` and per-specifier `type` prefixes, aliases
27
+ * (`X as Y`), quote style and a default import are preserved; the default
28
+ * import always stays on the root.
29
+ *
30
+ * Which entry owns which name comes from `codemods/lib/entry-map.mjs`, which
31
+ * `pnpm entry-map` generates from `architecture/entry-map.json` — so the
32
+ * installed copy of the codemod always knows the map of its own version.
33
+ *
34
+ * ## What it reports
35
+ *
36
+ * | rule | action | severity | why |
37
+ * | ------------------ | ------- | -------- | --------------------------------------------------------------------------------------- |
38
+ * | `moved-to-suite` | applied | low | shared Assure patterns now import from `/suite` |
39
+ * | `moved-to-legacy` | applied | low | frozen modules with no known consumer now import from `/legacy` |
40
+ * | `product-owned` | applied | medium | the import moved to `/legacy`, but the module is leaving for a product repository |
41
+ * | `namespace-import` | review | high | `import * as DS` cannot be split; the named members have to be imported one by one first |
42
+ *
43
+ * Imports already on `/suite` or `/legacy` are not touched (idempotency), and
44
+ * neither is a name the root still owns. Test files are skipped.
45
+ */
46
+ import { ENTRY_BY_SYMBOL, ENTRY_SPECIFIER, PRODUCT_TARGET } from "../lib/entry-map.mjs";
47
+ import { applyEdits } from "../lib/jsx-edit.mjs";
48
+ import { analyseForms } from "../lib/forms.mjs";
49
+
50
+ export const meta = {
51
+ id: "CM-01",
52
+ title: "Root imports of suite, legacy and product-owned names → /suite and /legacy",
53
+ class: "R",
54
+ oneShot: false,
55
+ requires: { codemods: [], dsVersion: null },
56
+ parses: ["code"],
57
+ includeTests: false,
58
+ usesTypeScript: true,
59
+ usesPostcss: false,
60
+ registryIds: ["C-ROOT-SUITE"],
61
+ };
62
+
63
+ export const RULES = {
64
+ "moved-to-suite": { action: "applied", severity: "low" },
65
+ "moved-to-legacy": { action: "applied", severity: "low" },
66
+ "product-owned": { action: "applied", severity: "medium" },
67
+ "namespace-import": { action: "review", severity: "high" },
68
+ };
69
+
70
+ const ROOT = "@assure-one/design-system";
71
+
72
+ /** The entry that owns an imported name, or `null` when the root keeps it. */
73
+ export const entryOf = (name) => ENTRY_BY_SYMBOL[name] ?? null;
74
+
75
+ const ruleFor = (name, entry) =>
76
+ PRODUCT_TARGET[name] ? "product-owned" : entry === "suite" ? "moved-to-suite" : "moved-to-legacy";
77
+
78
+ /** One `import …` statement built from the specifiers of the original one. */
79
+ function declaration({ typeOnly, defaultName, specifiers, quote, semi }, specifier) {
80
+ const clause = [defaultName, specifiers.length ? `{ ${specifiers.join(", ")} }` : null].filter(
81
+ Boolean,
82
+ );
83
+ return `import ${typeOnly ? "type " : ""}${clause.join(", ")} from ${quote}${specifier}${quote}${semi}`;
84
+ }
85
+
86
+ export function transform(file, { ts }) {
87
+ const facts = analyseForms(ts, file.source, file.rel);
88
+ const sf = facts.sf;
89
+ const findings = [];
90
+ const edits = [];
91
+
92
+ for (const stmt of sf.statements) {
93
+ if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier)) continue;
94
+ if (stmt.moduleSpecifier.text !== ROOT) continue;
95
+ const clause = stmt.importClause;
96
+ if (!clause) continue;
97
+
98
+ const line = sf.getLineAndCharacterOfPosition(stmt.getStart(sf)).line + 1;
99
+ const base = { line, registryId: "C-ROOT-SUITE", component: null, gate: null };
100
+
101
+ // `import * as DS` — the members are reached through the namespace, so the
102
+ // import cannot be split by name. Only worth reporting when the file
103
+ // actually reads a name that moved.
104
+ if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) {
105
+ const ns = clause.namedBindings.name.text;
106
+ const used = new Set();
107
+ const visit = (node) => {
108
+ if (
109
+ ts.isPropertyAccessExpression(node) &&
110
+ ts.isIdentifier(node.expression) &&
111
+ node.expression.text === ns &&
112
+ entryOf(node.name.text)
113
+ ) {
114
+ used.add(node.name.text);
115
+ }
116
+ if (ts.isQualifiedName(node) && ts.isIdentifier(node.left) && node.left.text === ns) {
117
+ if (entryOf(node.right.text)) used.add(node.right.text);
118
+ }
119
+ ts.forEachChild(node, visit);
120
+ };
121
+ ts.forEachChild(sf, visit);
122
+ if (used.size) {
123
+ findings.push({
124
+ ...base,
125
+ rule: "namespace-import",
126
+ ...RULES["namespace-import"],
127
+ match: `import * as ${ns}`,
128
+ detail: { namespace: ns, symbols: [...used].sort() },
129
+ });
130
+ }
131
+ continue;
132
+ }
133
+
134
+ const named = clause.namedBindings;
135
+ if (!named || !ts.isNamedImports(named)) continue;
136
+
137
+ const buckets = { root: [], suite: [], legacy: [] };
138
+ const movedNames = { suite: [], legacy: [] };
139
+ for (const element of named.elements) {
140
+ const imported = (element.propertyName ?? element.name).text;
141
+ const entry = entryOf(imported);
142
+ const bucket = entry ?? "root";
143
+ buckets[bucket].push(element.getText(sf));
144
+ if (entry) movedNames[entry].push(imported);
145
+ }
146
+ if (!movedNames.suite.length && !movedNames.legacy.length) continue;
147
+
148
+ const text = stmt.getText(sf);
149
+ const shape = {
150
+ typeOnly: clause.isTypeOnly,
151
+ defaultName: clause.name ? clause.name.text : null,
152
+ quote: stmt.moduleSpecifier.getText(sf)[0],
153
+ semi: text.endsWith(";") ? ";" : "",
154
+ };
155
+ const rows = [];
156
+ if (buckets.root.length || shape.defaultName) {
157
+ rows.push(declaration({ ...shape, specifiers: buckets.root }, ROOT));
158
+ }
159
+ for (const entry of ["suite", "legacy"]) {
160
+ if (!buckets[entry].length) continue;
161
+ rows.push(
162
+ declaration(
163
+ { ...shape, defaultName: null, specifiers: buckets[entry] },
164
+ ENTRY_SPECIFIER[entry],
165
+ ),
166
+ );
167
+ }
168
+ edits.push({ pos: stmt.getStart(sf), end: stmt.getEnd(), text: rows.join("\n") });
169
+
170
+ for (const entry of ["suite", "legacy"]) {
171
+ const byRule = new Map();
172
+ for (const name of movedNames[entry]) {
173
+ const rule = ruleFor(name, entry);
174
+ if (!byRule.has(rule)) byRule.set(rule, []);
175
+ byRule.get(rule).push(name);
176
+ }
177
+ for (const [rule, names] of byRule) {
178
+ findings.push({
179
+ ...base,
180
+ rule,
181
+ ...RULES[rule],
182
+ match: `{ ${names.sort().join(", ")} }`,
183
+ detail: {
184
+ from: ROOT,
185
+ to: ENTRY_SPECIFIER[entry],
186
+ symbols: names,
187
+ ...(rule === "product-owned"
188
+ ? { owners: [...new Set(names.map((n) => PRODUCT_TARGET[n]))].sort() }
189
+ : {}),
190
+ },
191
+ });
192
+ }
193
+ }
194
+ }
195
+
196
+ return {
197
+ output: edits.length ? applyEdits(file.source, edits) : file.source,
198
+ findings,
199
+ notTransformed: [],
200
+ parseErrors: facts.parseErrors,
201
+ };
202
+ }
@@ -0,0 +1,234 @@
1
+ /**
2
+ * CM-18 — `DataTable*` presentational parts → `Table*` (class A; plan §29
3
+ * seq 12, registry `C-DT-PARTS`, lands with W6-03).
4
+ *
5
+ * W6-03 made the `DataTable*` table parts thin aliases of the `Table*` parts:
6
+ * one styling source, the same props. Where a part is a 1:1 copy the rename
7
+ * is mechanical and this codemod applies it — tag, closing tag and the named
8
+ * import:
9
+ *
10
+ * | from | to | note |
11
+ * | ----------------- | ------------- | ----------------------------------------------------------- |
12
+ * | `DataTableHead` | `TableHeader` | `<thead>` |
13
+ * | `DataTableBody` | `TableBody` | `<tbody>` |
14
+ * | `DataTableRow` | `TableRow` | `selected` is the same prop |
15
+ * | `DataTableCell` | `TableCell` | same props (`align`, `numeric`, `muted`, `truncate`, `width`)|
16
+ * | `DataTableHeader` | `TableHead` | plain, or controlled `sortable sort onSortChange` — `sortable` is dropped because `TableHead` is sortable whenever `sort` is passed |
17
+ *
18
+ * What changes for the rendered DOM: the `data-slot` attributes read
19
+ * `table-*` instead of `data-table-*`, and `TableHead` does not stamp
20
+ * `scope="col"` (a `<th>` inside `<thead>` is a column header by default).
21
+ * Classes, geometry and colours are the same — the `DataTable*` parts render
22
+ * through the `Table*` parts since W6-03.
23
+ *
24
+ * ## What it leaves alone, and lists under "could not be transformed"
25
+ *
26
+ * - `DataTableHeader sortable` **without** a controlled `sort` (uncontrolled
27
+ * sorting, `defaultSort`): `TableHead` has no uncontrolled sort state — the
28
+ * consumer wires `sort` from `useDataTable` or its own state.
29
+ * - `DataTableHeader sortable={expr}`: whether the header is sortable is not
30
+ * visible here.
31
+ * - `DataTableHeader {...props}`: the spread may carry `sortable`.
32
+ *
33
+ * Not touched at all (not parallel copies of a `Table*` part, they stay):
34
+ * `DataTable` (the card frame), `DataTableToolbar`, `DataTableSearch`,
35
+ * `DataTableSpacer`, `DataTableResultsCount`, `DataTableCellName` /
36
+ * `DataTableCellMono` / `DataTableCellId` / `DataTableCellDue`,
37
+ * `DataTableCheckbox`, `DataTablePagination`, and the `DataTable*Props` types.
38
+ * An aliased import (`DataTableCell as Cell`) and a namespace import are left
39
+ * alone too. The import specifier is rewritten only when no reference to the
40
+ * old name remains in the file (a `typeof DataTableCell` keeps it, and the
41
+ * new name is added next to it). Local components with the same names are
42
+ * not touched; test files are skipped, as by the scanner that measures
43
+ * C-DT-PARTS.
44
+ */
45
+ import { analyseForms } from "../lib/forms.mjs";
46
+ import { applyEdits, openingOf, removeAttribute } from "../lib/jsx-edit.mjs";
47
+
48
+ export const meta = {
49
+ id: "CM-18",
50
+ title: "DataTable* presentational parts → Table* (pure rename where the part is a 1:1 copy)",
51
+ class: "A",
52
+ oneShot: false,
53
+ requires: { codemods: [], dsVersion: null },
54
+ parses: ["code"],
55
+ includeTests: false,
56
+ usesTypeScript: true,
57
+ usesPostcss: false,
58
+ registryIds: ["C-DT-PARTS"],
59
+ };
60
+
61
+ /** `DataTable*` part → the `Table*` part it aliases (src/composites/data-table.tsx, W6-03). */
62
+ export const PART_MAP = {
63
+ DataTableHead: "TableHeader",
64
+ DataTableBody: "TableBody",
65
+ DataTableRow: "TableRow",
66
+ DataTableCell: "TableCell",
67
+ DataTableHeader: "TableHead",
68
+ };
69
+
70
+ export const RULES = {
71
+ "part-renamed": { action: "applied", severity: "low" },
72
+ };
73
+
74
+ const DS_PACKAGE = "@assure-one/design-system";
75
+
76
+ /**
77
+ * Identifier references to `name` outside its import specifier and outside
78
+ * JSX tag names — `typeof DataTableCell`, `as={DataTableCell}`, a plain
79
+ * value use. While one exists the old import must stay.
80
+ */
81
+ function hasOtherReferences(ts, sf, name) {
82
+ let found = false;
83
+ const visit = (node) => {
84
+ if (found) return;
85
+ if (ts.isIdentifier(node) && node.text === name) {
86
+ const parent = node.parent;
87
+ const isImport = parent && ts.isImportSpecifier(parent);
88
+ const isTag =
89
+ parent &&
90
+ (ts.isJsxOpeningElement(parent) ||
91
+ ts.isJsxSelfClosingElement(parent) ||
92
+ ts.isJsxClosingElement(parent)) &&
93
+ parent.tagName === node;
94
+ if (!isImport && !isTag) found = true;
95
+ return;
96
+ }
97
+ ts.forEachChild(node, visit);
98
+ };
99
+ visit(sf);
100
+ return found;
101
+ }
102
+
103
+ export function transform(file, { ts }) {
104
+ const facts = analyseForms(ts, file.source, file.rel);
105
+ const sf = facts.sf;
106
+ const findings = [];
107
+ const notTransformed = [];
108
+ const edits = [];
109
+ /** old local name → new name, for the elements that were renamed */
110
+ const renamed = new Map();
111
+ /** old local names that still have an element on them */
112
+ const remaining = new Set();
113
+
114
+ for (const el of facts.elements) {
115
+ if (!el.isDs || !PART_MAP[el.base] || el.component !== el.base) continue;
116
+ // An aliased import (`DataTableCell as Cell`): the local name is not the part's.
117
+ if (el.tag !== el.base) {
118
+ remaining.add(el.tag);
119
+ continue;
120
+ }
121
+ const to = PART_MAP[el.base];
122
+ const opening = openingOf(ts, el.node);
123
+ const inner = [];
124
+ let sortableDropped = false;
125
+
126
+ if (el.base === "DataTableHeader") {
127
+ const sortable = el.props.get("sortable");
128
+ if (el.spread) {
129
+ notTransformed.push({
130
+ line: el.line,
131
+ reason: "spread-props",
132
+ detail: `<${el.tag} {…}> — the spread may carry \`sortable\`; rename to \`TableHead\` by hand once you know it does not`,
133
+ });
134
+ remaining.add(el.tag);
135
+ continue;
136
+ }
137
+ if (sortable) {
138
+ if (sortable.text !== "true") {
139
+ notTransformed.push({
140
+ line: el.line,
141
+ reason: "dynamic-sortable",
142
+ detail: `<${el.tag} sortable={…}> — whether the header sorts is not visible here; \`TableHead\` sorts whenever \`sort\` is passed`,
143
+ });
144
+ remaining.add(el.tag);
145
+ continue;
146
+ }
147
+ if (!el.props.has("sort") || el.props.has("defaultSort")) {
148
+ notTransformed.push({
149
+ line: el.line,
150
+ reason: "uncontrolled-sort",
151
+ detail: `<${el.tag} sortable> without a controlled \`sort\` — \`TableHead\` has no uncontrolled sort state; wire \`sort\` + \`onSortChange\` (from \`useDataTable\`) first`,
152
+ });
153
+ remaining.add(el.tag);
154
+ continue;
155
+ }
156
+ const drop = removeAttribute(ts, sf, opening, "sortable");
157
+ if (drop) {
158
+ inner.push(drop);
159
+ sortableDropped = true;
160
+ }
161
+ } else if (el.props.has("defaultSort")) {
162
+ notTransformed.push({
163
+ line: el.line,
164
+ reason: "uncontrolled-sort",
165
+ detail: `<${el.tag} defaultSort> — \`TableHead\` has no uncontrolled sort state`,
166
+ });
167
+ remaining.add(el.tag);
168
+ continue;
169
+ }
170
+ }
171
+
172
+ inner.push({ pos: opening.tagName.getStart(sf), end: opening.tagName.getEnd(), text: to });
173
+ if (ts.isJsxElement(el.node)) {
174
+ const closing = el.node.closingElement.tagName;
175
+ inner.push({ pos: closing.getStart(sf), end: closing.getEnd(), text: to });
176
+ }
177
+ edits.push(...inner);
178
+ renamed.set(el.base, to);
179
+ findings.push({
180
+ line: el.line,
181
+ registryId: "C-DT-PARTS",
182
+ rule: "part-renamed",
183
+ match: `<${el.tag}>`,
184
+ component: el.component,
185
+ action: "applied",
186
+ gate: null,
187
+ severity: RULES["part-renamed"].severity,
188
+ detail: { from: el.base, to, sortableDropped },
189
+ });
190
+ }
191
+
192
+ // The import: each renamed name becomes its `Table*` part when nothing else
193
+ // in the file still refers to it; otherwise the new name is added next to it.
194
+ if (renamed.size) {
195
+ for (const stmt of sf.statements) {
196
+ if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier)) continue;
197
+ const spec = stmt.moduleSpecifier.text;
198
+ if (spec !== DS_PACKAGE && !spec.startsWith(`${DS_PACKAGE}/`)) continue;
199
+ const bindings = stmt.importClause?.namedBindings;
200
+ if (!bindings || !ts.isNamedImports(bindings)) continue;
201
+ const present = new Set(bindings.elements.map((e) => e.name.text));
202
+ const list = bindings.elements;
203
+ for (const [from, to] of renamed) {
204
+ const index = list.findIndex((e) => !e.propertyName && e.name.text === from);
205
+ if (index < 0) continue;
206
+ const element = list[index];
207
+ const keep = remaining.has(from) || hasOtherReferences(ts, sf, from);
208
+ if (keep) {
209
+ if (!present.has(to)) {
210
+ edits.push({ pos: element.getEnd(), text: `, ${to}` });
211
+ present.add(to);
212
+ }
213
+ continue;
214
+ }
215
+ if (present.has(to)) {
216
+ // `TableCell` is already imported: the old specifier goes.
217
+ const fromPos = index === 0 ? element.getStart(sf) : list[index - 1].getEnd();
218
+ const toPos = index === 0 && list.length > 1 ? list[1].getStart(sf) : element.getEnd();
219
+ edits.push({ pos: fromPos, end: toPos, text: "" });
220
+ } else {
221
+ edits.push({ pos: element.getStart(sf), end: element.getEnd(), text: to });
222
+ present.add(to);
223
+ }
224
+ }
225
+ }
226
+ }
227
+
228
+ return {
229
+ output: edits.length ? applyEdits(file.source, edits) : file.source,
230
+ findings,
231
+ notTransformed,
232
+ parseErrors: facts.parseErrors,
233
+ };
234
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The seed a host supplies per tenant.
3
+ *
4
+ * @experimental Gated by decision D7.
5
+ */
6
+ interface BrandThemeSeed {
7
+ /**
8
+ * The brand fill, as `#rgb` or `#rrggbb`. Drives the `action.brand` family,
9
+ * the focus ring and the brand gradient.
10
+ */
11
+ brand: string;
12
+ /**
13
+ * An optional second hue for the `action.accent` family, as `#rgb` or
14
+ * `#rrggbb`. `undefined` or `null` means "the same as `brand`", which is what
15
+ * the built-in `[data-ds-brand]` scopes do.
16
+ */
17
+ accent?: string | null;
18
+ }
19
+ /** Every custom property `createBrandTheme()` sets, in output order. */
20
+ declare const BRAND_THEME_PROPERTIES: readonly ["--ds-color-action-brand-bg", "--ds-color-action-brand-bg-hover", "--ds-color-action-brand-bg-active", "--ds-color-action-brand-bg-disabled", "--ds-color-action-brand-subtle-bg", "--ds-color-action-brand-subtle-bg-hover", "--ds-color-action-brand-subtle-bg-active", "--ds-color-action-brand-border", "--ds-color-action-brand-ring", "--ds-color-action-brand-fg", "--ds-color-action-accent-bg", "--ds-color-action-accent-bg-strong", "--ds-color-action-accent-bg-hover", "--ds-color-action-accent-bg-active", "--ds-color-action-accent-subtle-bg", "--ds-color-action-accent-ring", "--ds-color-action-accent-fg", "--ds-color-border-focus", "--ds-focus-ring-color", "--ds-focus-ring-border", "--ds-gradient-brand", "--ds-compat-color-brand-soft", "--ds-compat-color-brand-soft-foreground"];
21
+ /** One of the custom properties `createBrandTheme()` sets. */
22
+ type BrandThemeProperty = (typeof BRAND_THEME_PROPERTIES)[number];
23
+ /**
24
+ * The style object `createBrandTheme()` returns: every property of
25
+ * {@link BRAND_THEME_PROPERTIES} with a CSS value.
26
+ *
27
+ * React types `style` as `CSSProperties`, which has no index signature for
28
+ * custom properties, so spread it or cast it at the call site:
29
+ * `style={{ ...createBrandTheme(seed) }}`.
30
+ *
31
+ * @experimental Gated by decision D7.
32
+ */
33
+ type BrandTheme = {
34
+ readonly [P in BrandThemeProperty]: string;
35
+ };
36
+ /**
37
+ * Turn a tenant's colours into the design system's brand and accent tokens.
38
+ *
39
+ * ```tsx
40
+ * const theme = createBrandTheme({ brand: firm.primaryColor, accent: firm.accentColor });
41
+ * return <div style={{ ...theme }}>{children}</div>;
42
+ * ```
43
+ *
44
+ * Apply it to the element that scopes the brand — `<body>` for a whole-tenant
45
+ * theme, a wrapper for a preview. The properties are the same ones a built-in
46
+ * `[data-ds-brand]` scope declares, so anything reading them (every design
47
+ * system component, and any application CSS reading `--ds-*`) follows.
48
+ *
49
+ * @param seed - `brand`, and optionally `accent` (decision D7).
50
+ * @returns A frozen object of custom property names to CSS values.
51
+ * @throws TypeError when the seed is not an object, carries an unknown key, or
52
+ * a colour is missing or not `#rgb`/`#rrggbb`.
53
+ *
54
+ * @experimental Gated by decision D7. The property set may grow, and the seed
55
+ * shape may change, until D7 is resolved.
56
+ */
57
+ declare function createBrandTheme(seed: BrandThemeSeed): BrandTheme;
58
+
59
+ export { BRAND_THEME_PROPERTIES as B, type BrandTheme as a, type BrandThemeProperty as b, type BrandThemeSeed as c, createBrandTheme as d };