@scattered-light/loomiere 0.2.0 → 0.4.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/README.md CHANGED
@@ -29,6 +29,8 @@ Every value is a `--sl-` variable, so your own CSS, and component packages such
29
29
  | `loomiere check` | Checks contrast of the theme already built. |
30
30
  | `loomiere studio` | Opens the brand studio at `http://localhost:4322`. `--export page.html` writes a standalone copy (needs esbuild). |
31
31
  | `loomiere lab` | Opens the pair lab at `http://localhost:4321` to pick brand pairs (a background, its text and an accent). |
32
+ | `loomiere figma push` | Builds, then pushes variables and text styles to a Figma file. `--dry-run` reports only. See [figma/README.md](figma/README.md). |
33
+ | `loomiere figma serve` | Serves the build for the Loomiere Figma plugin's Pull from project. |
32
34
 
33
35
  `--brand <file>`, `--out <dir>` and `--prefix <name>` override the config for one run. `--port <n>` moves studio or lab.
34
36
 
@@ -46,16 +48,19 @@ Every value is a `--sl-` variable, so your own CSS, and component packages such
46
48
  - `out`: where the theme goes. Default `theme`.
47
49
  - `prefix`: the CSS variable prefix. Default `sl`, which is what loomiere-base-ui reads.
48
50
  - `exports`: which framework files to write. Default all of them.
51
+ - `figma`: `{ "file": "<Figma URL>" }`, the file `loomiere figma push` writes to.
49
52
 
50
53
  ## What a build writes
51
54
 
52
55
  | File | For |
53
56
  | --- | --- |
54
- | `tokens.css` | Every token as a `--sl-` variable, inside `@layer sl.tokens` so your own styles win. Dark mode follows `prefers-color-scheme` or `data-theme="dark"`. Also holds the `.sl-pair-<name>` classes and `data-color` scopes. |
57
+ | `tokens.css` | Every token as a `--sl-` variable, inside `@layer sl.tokens` so your own styles win. Dark mode and high contrast follow the system, or `data-theme` on `<html>`: `light`, `dark`, `light-high-contrast`, `dark-high-contrast`, or a color vision theme (`deuteranopia`, `protanopia`, `tritanopia`, `achromatopsia`). Also holds the `.sl-pair-<name>` classes, `data-color` scopes, `data-status` (info, success, warning, error) and `data-emphasis` levels. |
55
58
  | `utilities.css` | `.text-style-*`, `.icon-*`, `.focus-ring`, `.hide-on-*`. |
56
59
  | `tokens.js`, `tokens.d.ts` | Resolved values for JavaScript. |
57
60
  | `tokens.json`, `pairs.json` | The token catalog and every palette color against every other, for docs and agents. |
58
61
  | `exports/` | DTCG JSON, Figma variables, Tailwind v4, shadcn/ui, MUI, Chakra v3, AG Grid 31–32, 33–35 and 36+, a Claude Design System and `AGENTS.md` (the brand book for agents). See [exporters/README.md](exporters/README.md). |
59
62
  | `generated/` | The brand's primitive tokens. Rebuilt every time; no need to commit. |
60
63
 
64
+ Figma: three ways to send the theme into a Figma file (a Claude chat, `loomiere figma push`, or the Loomiere plugin for designers without the repo) in [figma/README.md](figma/README.md).
65
+
61
66
  How the layers work, the brand file's fields, roles, concepts and pairs: [tokens/README.md](tokens/README.md).
package/bin/loomiere.mjs CHANGED
@@ -7,6 +7,8 @@
7
7
  * loomiere check check contrast of the theme already built
8
8
  * loomiere studio edit the brand with live previews (--export page.html for a static copy)
9
9
  * loomiere lab pick brand pairs
10
+ * loomiere figma push build, then push the theme to Figma (lib/figma.mjs)
11
+ * loomiere figma serve serve the build for the Loomiere Figma plugin
10
12
  *
11
13
  * Flags: --brand <file> --out <dir> --prefix <name> --port <n>
12
14
  */
@@ -26,6 +28,8 @@ Usage
26
28
  loomiere check Check contrast of the built theme
27
29
  loomiere studio Edit the brand with live previews
28
30
  loomiere lab Pick brand pairs (background + text)
31
+ loomiere figma push Build, then push variables and text styles to Figma
32
+ loomiere figma serve Serve the build for the Loomiere Figma plugin
29
33
 
30
34
  Options
31
35
  --brand <file> Brand file (default: the only one in brand/)
@@ -33,6 +37,11 @@ Options
33
37
  --prefix <name> CSS variable prefix (default: sl)
34
38
  --port <n> Port for studio and lab
35
39
  --export <file> studio only: write one self-contained page instead of serving
40
+ --file <url> figma: the Figma file (or "figma": { "file" } in the config)
41
+ --via <way> figma push: claude, api, plugin or scripts (default: the first that works)
42
+ --since <file> figma push --via scripts: the snapshot, to send only what changed
43
+ --remove figma push: delete what the build no longer has
44
+ --dry-run figma push: report what would change, change nothing
36
45
  --help Show this help
37
46
  `;
38
47
 
@@ -44,6 +53,11 @@ const { values: flags, positionals } = parseArgs({
44
53
  prefix: { type: 'string' },
45
54
  port: { type: 'string' },
46
55
  export: { type: 'string' },
56
+ file: { type: 'string' },
57
+ via: { type: 'string' },
58
+ since: { type: 'string' },
59
+ remove: { type: 'boolean' },
60
+ 'dry-run': { type: 'boolean' },
47
61
  help: { type: 'boolean', short: 'h' },
48
62
  },
49
63
  });
@@ -101,6 +115,16 @@ async function main() {
101
115
  const { lab } = await import('../lib/lab.mjs');
102
116
  return lab(resolveConfig(configFlags), { port });
103
117
  }
118
+ case 'figma': {
119
+ const { figmaCommand } = await import('../lib/figma.mjs');
120
+ const config = resolveConfig(configFlags);
121
+ const sub = rest[0] ?? 'push';
122
+ // Push what the brand says now, the way a designer would expect.
123
+ if (sub === 'push' && flags.via !== 'scripts' && !(await build(config))) return void (process.exitCode = 1);
124
+ const ok = await figmaCommand(config, sub, { via: flags.via, file: flags.file, since: flags.since, remove: flags.remove, dryRun: flags['dry-run'], port });
125
+ if (ok === false) process.exitCode = 1;
126
+ return;
127
+ }
104
128
  default:
105
129
  throw new Error(`Unknown command "${command}". Run loomiere --help.`);
106
130
  }
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Tokens can carry mode values in $extensions["sl.modes"] (dark, contrast-more,
11
11
  * dark-contrast-more, reduced-motion); the CSS output emits each mode as its own block.
12
- * Color vision themes (vision.* primitives) become [data-vision='<theme>'] blocks.
12
+ * Color vision themes (vision.* primitives) become [data-theme='<theme>'] blocks.
13
13
  *
14
14
  * Run it through the command line (loomiere build), which reads loomiere.config.json.
15
15
  */
@@ -22,28 +22,45 @@ import { fileHeader, formattedVariables } from 'style-dictionary/utils';
22
22
  import { exportAll } from '../exporters/index.mjs';
23
23
  import { generate } from '../generator/generate.mjs';
24
24
  import { LEVELS, PAIR_ROLES, pairTable, paletteColors } from '../generator/pairs.mjs';
25
- import { resolveRoles } from '../generator/roles.mjs';
25
+ import { resolveRoles, STYLES } from '../generator/roles.mjs';
26
26
 
27
27
  const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
28
- // Set by buildTokens() from the config; the Style Dictionary formats below read them.
28
+ // Set by buildTokens() from the config and brand; the Style Dictionary formats below read them.
29
29
  let prefix = 'sl';
30
30
  let brandName = '';
31
+ let roles;
31
32
 
32
33
  const LAYERS = ['foundation', 'primitive', 'semantic', 'component'];
33
34
 
35
+ /**
36
+ * Themes, picked with data-theme on <html>: light, dark, light-high-contrast,
37
+ * dark-high-contrast, and one per color vision theme (deuteranopia…). With no data-theme the
38
+ * system decides light or dark and high contrast. Color vision themes stand on their own:
39
+ * they are light, and neither the system's dark mode nor its contrast setting changes them.
40
+ */
41
+ const theme = (names) => names.map((n) => `[data-theme='${n}']`).join(', ');
42
+
34
43
  /** Selectors for each mode, in cascade order: later blocks win. */
35
- const MODES = [
36
- ['dark', [['@media (prefers-color-scheme: dark)', ":root:not([data-theme='light'])"], [null, "[data-theme='dark']"]]],
37
- ['contrast-more', [['@media (prefers-contrast: more)', ':root']]],
38
- [
39
- 'dark-contrast-more',
44
+ function modes(visions) {
45
+ const notLight = `:root:not(${theme(['light', 'light-high-contrast', ...visions])})`;
46
+ const notVision = visions.length ? `:root:not(${theme(visions)})` : ':root';
47
+ return [
48
+ ['dark', [['@media (prefers-color-scheme: dark)', notLight], [null, theme(['dark', 'dark-high-contrast'])]]],
49
+ ['contrast-more', [['@media (prefers-contrast: more)', notVision], [null, theme(['light-high-contrast', 'dark-high-contrast'])]]],
40
50
  [
41
- ['@media (prefers-contrast: more) and (prefers-color-scheme: dark)', ":root:not([data-theme='light'])"],
42
- ['@media (prefers-contrast: more)', "[data-theme='dark']"],
51
+ 'dark-contrast-more',
52
+ [
53
+ ['@media (prefers-contrast: more) and (prefers-color-scheme: dark)', notLight],
54
+ ['@media (prefers-contrast: more)', theme(['dark'])],
55
+ [null, theme(['dark-high-contrast'])],
56
+ ],
43
57
  ],
44
- ],
45
- ['reduced-motion', [['@media (prefers-reduced-motion: reduce)', ':root']]],
46
- ];
58
+ ['reduced-motion', [['@media (prefers-reduced-motion: reduce)', ':root']]],
59
+ ];
60
+ }
61
+
62
+ /** The color vision themes the build has (vision.<theme>.* tokens). */
63
+ const visionsIn = (dictionary) => [...new Set(dictionary.allTokens.filter((t) => t.path[0] === 'vision').map((t) => t.path[1]))];
47
64
 
48
65
  const varName = (tokenPath) => `--${[prefix, ...tokenPath].join('-')}`;
49
66
  const refsToVars = (value) => String(value).replace(/\{([^}]+)\}/g, (_, ref) => `var(${varName(ref.split('.'))})`);
@@ -61,7 +78,7 @@ const modeValue = (token, mode) => token.original.$extensions?.['sl.modes']?.[mo
61
78
 
62
79
  function modeBlocks(dictionary) {
63
80
  const blocks = [];
64
- for (const [mode, targets] of MODES) {
81
+ for (const [mode, targets] of modes(visionsIn(dictionary))) {
65
82
  const declarations = dictionary.allTokens
66
83
  .filter((token) => modeValue(token, mode) !== undefined)
67
84
  .map((token) => `${varName(token.path)}: ${refsToVars(modeValue(token, mode))};`)
@@ -76,7 +93,7 @@ function modeBlocks(dictionary) {
76
93
  }
77
94
 
78
95
  /**
79
- * [data-vision="<theme>"] re-declares the brand's palette and data variables with the
96
+ * [data-theme="<vision theme>"] re-declares the brand's palette and data variables with the
80
97
  * theme's (vision.<theme>.* tokens, generated in generator/vision.mjs). Set it on the root
81
98
  * element: variables declared at :root resolve there, so everything that reads them follows.
82
99
  */
@@ -84,66 +101,92 @@ function visionBlocks(dictionary) {
84
101
  const byTheme = new Map();
85
102
  for (const token of dictionary.allTokens) {
86
103
  if (token.path[0] !== 'vision') continue;
87
- const [, theme, ...rest] = token.path;
88
- if (!byTheme.has(theme)) byTheme.set(theme, []);
89
- byTheme.get(theme).push(`${varName(rest)}: var(${varName(token.path)});`);
104
+ const [, name, ...rest] = token.path;
105
+ if (!byTheme.has(name)) byTheme.set(name, []);
106
+ byTheme.get(name).push(`${varName(rest)}: var(${varName(token.path)});`);
90
107
  }
91
- return [...byTheme].map(([theme, lines]) => `[data-vision='${theme}'] {\n${indent(lines.join('\n'), 2)}\n}`).join('\n\n');
108
+ return [...byTheme].map(([name, lines]) => `${theme([name])} {\n${indent(lines.join('\n'), 2)}\n}`).join('\n\n');
92
109
  }
93
110
 
94
111
  /**
95
- * Color scopes and emphasis (generator/roles.mjs). Every palette's slots are tokens
112
+ * Color scopes, statuses and emphasis (generator/roles.mjs). Every palette's slots are tokens
96
113
  * (scope.<palette>.<slot>); these blocks point the generic --sl-scope-<slot> at one of them:
97
- * the accent by default, any palette under [data-color], and the level's own color under
98
- * [data-emphasis] when that color is chrome or a named palette, unless the element names a color itself. --sl-e-<part> is what a
99
- * component paints with: the level's style read from whatever scope is in effect.
114
+ * the accent by default, any palette under [data-color] or [data-status], and the level's own
115
+ * color under [data-emphasis] when that color is chrome or a named palette, unless the element
116
+ * names a color or sits in a status. --sl-e-<part> is what a component paints with: the level's
117
+ * style read from whatever scope is in effect, or the brand pair the brand file picked for that
118
+ * level in that status (roles.status).
100
119
  */
101
- // Button's data-variant names are aliases for emphasis levels, so the brand's roles drive every
102
- // button. data-emphasis wins when both are set; a button with neither is bold.
103
- const VARIANT_LEVELS = {
104
- bold: [".Button[data-variant='solid']:not([data-emphasis])", '.Button:not([data-variant]):not([data-emphasis])'],
105
- standard: [".Button[data-variant='outline']:not([data-emphasis])"],
120
+ // Each component that paints with emphasis starts at a level of its own; data-emphasis on it, or
121
+ // on a group around a toggle, picks another. Button's data-variant names are aliases for levels,
122
+ // so the brand's roles drive every button; a button with neither is bold.
123
+ const DEFAULT_LEVELS = {
124
+ bold: [".Button[data-variant='solid']:not([data-emphasis])", '.Button:not([data-variant]):not([data-emphasis])', '.AvatarRoot:not([data-emphasis])'],
125
+ standard: [".Button[data-variant='outline']:not([data-emphasis])", '.Toggle:not([data-emphasis], [data-emphasis] *)'],
106
126
  quiet: [".Button[data-variant='ghost']:not([data-emphasis])"],
107
127
  };
108
128
 
109
- // A toast's type (toastManager.add({ type: 'success' })) picks the status palette of the same
110
- // name, so success, warning, error and info toasts tint themselves. data-color wins when both are set.
129
+ // A toast's type (toastManager.add({ type: 'success' })) is its status, so success, warning,
130
+ // error and info toasts tint themselves. data-color or data-status wins when one is set.
111
131
  const TOAST_TYPES = ['success', 'warning', 'error', 'info'];
112
- const COLOR_ALIASES = (name) => (TOAST_TYPES.includes(name) ? [`.Toast[data-type='${name}']:not([data-color])`] : []);
132
+ const statusSelectors = (name) => [`[data-status='${name}']`, ...(TOAST_TYPES.includes(name) ? [`.Toast[data-type='${name}']:not([data-color], [data-status])`] : [])];
133
+ const IN_STATUS = '[data-status], [data-status] *, .Toast[data-type], .Toast[data-type] *';
113
134
 
114
135
  function roleBlocks(dictionary) {
115
136
  const scopes = new Map();
116
- const levels = new Map();
137
+ const statuses = new Map();
117
138
  for (const token of dictionary.allTokens) {
118
139
  if (token.path[0] === 'scope') {
119
140
  const [, name, slot] = token.path;
120
141
  if (!scopes.has(name)) scopes.set(name, []);
121
142
  scopes.get(name).push(slot);
122
143
  }
123
- if (token.path[0] === 'emphasis') {
124
- const [, level, part] = token.path;
144
+ if (token.path[0] === 'status-level') {
145
+ const [, name, level, part] = token.path;
146
+ if (!statuses.has(name)) statuses.set(name, new Map());
147
+ const levels = statuses.get(name);
125
148
  if (!levels.has(level)) levels.set(level, []);
126
149
  levels.get(level).push([part, String(token.original.$value)]);
127
150
  }
128
151
  }
129
152
  if (!scopes.size) return '';
130
153
  const point = (name) => scopes.get(name).map((slot) => `${varName(['scope', slot])}: var(${varName(['scope', name, slot])});`).join('\n');
131
- const rule = (selector, body) => `${selector} {\n${indent(body, 2)}\n}`;
132
- const blocks = [rule(':root', point('accent'))];
133
- for (const name of scopes.keys()) blocks.push(rule([`[data-color='${name}']`, ...COLOR_ALIASES(name)].join(',\n'), point(name)));
134
- for (const [level, parts] of levels) {
135
- const color = parts.map(([, v]) => v.match(/^\{scope\.([^.]+)\./)?.[1]).find(Boolean);
136
- const paint = parts
154
+ const rule = (selectors, body) => `${selectors.join(',\n')} {\n${indent(body, 2)}\n}`;
155
+ const pairOf = (parts) => parts.map(([, v]) => v.match(/^\{pair\.([^.]+)\./)?.[1]).find(Boolean);
156
+ const paint = (parts) =>
157
+ parts
137
158
  .map(([part, v]) => {
138
159
  const slot = v.match(/^\{scope\.[^.]+\.([^}]+)\}$/)?.[1];
139
- return `${varName(['e', part])}: ${slot ? `var(${varName(['scope', slot])})` : v};`;
160
+ return `${varName(['e', part])}: ${slot ? `var(${varName(['scope', slot])})` : refsToVars(v)};`;
140
161
  })
141
162
  .join('\n');
142
- const selectors = [`[data-emphasis='${level}']`, ...(VARIANT_LEVELS[level] ?? [])];
143
- blocks.push(rule(selectors.join(',\n'), paint));
144
- // Accent levels follow the scope around them, so data-color on a section re-colors them;
145
- // chrome or a named palette stays put unless the element itself names a color.
146
- if (color && color !== 'accent') blocks.push(rule(selectors.map((sel) => `${sel}:not([data-color])`).join(',\n'), point(color)));
163
+
164
+ const blocks = [rule([':root'], point('accent'))];
165
+ for (const name of scopes.keys()) blocks.push(rule([`[data-color='${name}']`, ...(statuses.has(name) && name !== 'default' ? statusSelectors(name) : [])], point(name)));
166
+
167
+ // Every level's style, read from whatever scope is in effect: a status's palette, data-color's, or the accent.
168
+ for (const [level, parts] of statuses.get('default') ?? []) {
169
+ const selectors = [`[data-emphasis='${level}']`, ...(DEFAULT_LEVELS[level] ?? [])];
170
+ const style = STYLES[roles.emphasis[level].style];
171
+ blocks.push(rule(selectors, paint(parts.map(([part]) => [part, style[part] === 'transparent' ? 'transparent' : `{scope.any.${style[part]}}`]))));
172
+ // Default status: the pair the brand picked, or the level's own color. Accent levels follow
173
+ // the scope around them, so data-color on a section re-colors them; chrome or a named palette
174
+ // stays put unless the element names a color or sits in a status.
175
+ if (pairOf(parts)) {
176
+ blocks.push(rule([`:is(${selectors.join(', ')}):not(${IN_STATUS})`], paint(parts)));
177
+ continue;
178
+ }
179
+ const color = parts.map(([, v]) => v.match(/^\{scope\.([^.]+)\./)?.[1]).find(Boolean);
180
+ if (color && color !== 'accent') blocks.push(rule(selectors.map((sel) => `${sel}:not([data-color], ${IN_STATUS})`), point(color)));
181
+ }
182
+ // Other statuses: the scope already holds the status's palette; only a picked pair needs its own rule.
183
+ for (const [name, levels] of statuses) {
184
+ if (name === 'default') continue;
185
+ for (const [level, parts] of levels) {
186
+ if (!pairOf(parts)) continue;
187
+ const selectors = `:is(${[`[data-emphasis='${level}']`, ...(DEFAULT_LEVELS[level] ?? [])].join(', ')})`;
188
+ blocks.push(rule(statusSelectors(name).flatMap((s) => [`${s} ${selectors}`, `${s}${selectors}`]), paint(parts)));
189
+ }
147
190
  }
148
191
  return blocks.join('\n\n');
149
192
  }
@@ -284,10 +327,10 @@ StyleDictionary.registerFormat({
284
327
  /**
285
328
  * Text styles are written as one DTCG typography token each (tokens/semantic/typography.json).
286
329
  * CSS can't use a composite, so this expands each into its parts plus a `font` shorthand:
287
- * font.base → font.base.font, .font-family, .font-size, .font-weight, .line-height,
330
+ * font.body-base → font.body-base.font, .font-family, .font-size, .font-weight, .line-height,
288
331
  * .letter-spacing (and .text-transform when $extensions["sl.text-transform"] is set)
289
- * so --sl-font-base-font-size and friends exist for components. A text style whose value is
290
- * another text style ("{font.base}") takes that style's parts.
332
+ * so --sl-font-body-base-font-size and friends exist for components. A text style whose value is
333
+ * another text style ("{font.body-base}") takes that style's parts.
291
334
  */
292
335
  const TEXT_PARTS = { fontFamily: 'font-family', fontSize: 'font-size', fontWeight: 'font-weight', lineHeight: 'line-height', letterSpacing: 'letter-spacing' };
293
336
  export const TEXT_STYLE_TYPE = 'typography';
@@ -422,6 +465,7 @@ export async function buildTokens(config) {
422
465
  prefix = config.prefix;
423
466
  const { brand, applied, files } = generate(config.brandFile, config.generatedDir);
424
467
  brandName = brand.name;
468
+ roles = resolveRoles(brand);
425
469
  const out = config.outDir + path.sep;
426
470
 
427
471
  const sd = new StyleDictionary({
@@ -15,7 +15,7 @@ adapter in this folder, and each one only renames. No adapter does color math.
15
15
  | `ag-grid-v33` | `ag-grid-v33.js`, `.d.ts`, `.css` | AG Grid 33 to 35 (Theming API). |
16
16
  | `ag-grid-v32` | `ag-grid-v32.css` | AG Grid 31 and 32 (CSS themes). All three come from `ag-grid-settings.mjs`. |
17
17
  | `design-system` | `design-system/`, `AGENTS.md` | A Claude Design System (claude.ai), so Claude designs, makes decks and builds UI with the brand. `AGENTS.md` is the same brand book for coding agents, plus how to load the theme. See below. |
18
- | `figma` | `figma.json` | Figma variables in three collections: Brand, Pairs, Theme (Light and Dark modes). Aliases point back to Brand, so a palette change flows through. Fluid sizes use their large-screen value. |
18
+ | `figma` | `figma.json` | Figma variables, text styles and effect styles (elevation/xs to 2xl, focus/ring, focus/field, focus/field-error): hidden Brand and Scale collections, and Size, Theme, Status and Emphasis with modes for desktop, tablet and mobile sizes, light, dark, high contrast and color vision, `data-status` and `data-emphasis`, plus a one-mode layout for Figma Starter. Read by `loomiere figma push` and the Loomiere plugin; see [../figma/README.md](../figma/README.md). |
19
19
 
20
20
  `loomiere build` writes them all to the theme's `exports/` folder, with `catalog.json` listing what's there.
21
21
  `"exports": ["ag-grid", "tailwind"]` in `loomiere.config.json` writes only those (an id prefix picks a
@@ -45,12 +45,12 @@ export const SETTINGS = [
45
45
  ['cellEditingShadow', '{field.shadow-focus}', null],
46
46
 
47
47
  // Type: cells use the condense text style, the header uses condense-subtitle, whole.
48
- ['fontFamily', '{font.condense.font-family}', '--ag-font-family'],
49
- ['fontSize', '{font.condense.font-size}', '--ag-font-size'],
50
- ['fontWeight', '{font.condense.font-weight}', null],
51
- ['headerFontFamily', '{font.condense-subtitle.font-family}', null],
52
- ['headerFontSize', '{font.condense-subtitle.font-size}', null],
53
- ['headerFontWeight', '{font.condense-subtitle.font-weight}', null],
48
+ ['fontFamily', '{font.body-condense.font-family}', '--ag-font-family'],
49
+ ['fontSize', '{font.body-condense.font-size}', '--ag-font-size'],
50
+ ['fontWeight', '{font.body-condense.font-weight}', null],
51
+ ['headerFontFamily', '{font.subtitle-condense.font-family}', null],
52
+ ['headerFontSize', '{font.subtitle-condense.font-size}', null],
53
+ ['headerFontWeight', '{font.subtitle-condense.font-weight}', null],
54
54
 
55
55
  // Sizes
56
56
  ['spacing', '{space.2}', '--ag-grid-size'],
@@ -142,7 +142,7 @@ export const SETTINGS = [
142
142
  ['buttonHoverBorder', '{stroke.default} solid {emphasis.standard.border-hover}', null],
143
143
  ['buttonActiveBackgroundColor', '{emphasis.standard.bg-active}', null],
144
144
  ['buttonBorderRadius', '{radius.control}', null],
145
- ['buttonFontWeight', '{font.condense-trigger.font-weight}', null],
145
+ ['buttonFontWeight', '{font.trigger-condense.font-weight}', null],
146
146
  ['iconButtonColor', '{icon-button.color}', null],
147
147
  ['iconButtonHoverColor', '{icon-button.color-hover}', null],
148
148
  ['iconButtonHoverBackgroundColor', '{icon-button.background-hover}', null],
@@ -21,11 +21,11 @@ function resolver(theme) {
21
21
  const extraCss = (fixes = '') => `/* Letter spacing comes from the condense and condense-subtitle text styles. */
22
22
  .ag-root-wrapper,
23
23
  .ag-popup {
24
- letter-spacing: var(--sl-font-condense-letter-spacing);
24
+ letter-spacing: var(--sl-font-body-condense-letter-spacing);
25
25
  }
26
26
  .ag-header-cell-text {
27
- letter-spacing: var(--sl-font-condense-subtitle-letter-spacing);
28
- line-height: var(--sl-font-condense-subtitle-line-height);
27
+ letter-spacing: var(--sl-font-subtitle-condense-letter-spacing);
28
+ line-height: var(--sl-font-subtitle-condense-line-height);
29
29
  }
30
30
  ${fixes}`;
31
31
 
@@ -99,9 +99,9 @@ ${lines.join('\n')}
99
99
 
100
100
  /* The old themes have no header type settings: the header uses the condense-subtitle text style. */
101
101
  .ag-theme-loomiere .ag-header-cell-text {
102
- font-family: var(--${theme.prefix}-font-condense-subtitle-font-family);
103
- font-size: var(--${theme.prefix}-font-condense-subtitle-font-size);
104
- font-weight: var(--${theme.prefix}-font-condense-subtitle-font-weight);
102
+ font-family: var(--${theme.prefix}-font-subtitle-condense-font-family);
103
+ font-size: var(--${theme.prefix}-font-subtitle-condense-font-size);
104
+ font-weight: var(--${theme.prefix}-font-subtitle-condense-font-weight);
105
105
  }
106
106
  .ag-theme-loomiere .ag-header-cell:hover {
107
107
  background-color: var(--${theme.prefix}-table-header-background-hover);