diff --git a/lerna.json b/lerna.json index 6243203..9e36b0c 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": [ "packages/*" ], - "version": "2.1.4" + "version": "3.0.1-alpha.0" } diff --git a/packages/react-ui/UPGRADING.md b/packages/react-ui/UPGRADING.md new file mode 100644 index 0000000..57bd3d6 --- /dev/null +++ b/packages/react-ui/UPGRADING.md @@ -0,0 +1,76 @@ +# Upgrading datocms-react-ui + +## v3.0.0 — Semantic color tokens and dark-mode support + +This release reworks the color system around the host's semantic color +tokens. The CMS now computes a full color palette for the active theme +(including dark mode), and `datocms-react-ui` consumes it directly. All +built-in components automatically adapt to whichever theme the user has +selected. + +### Action required + +**Test your plugin in dark mode before publishing.** Upgrading to v3 opts +your plugin into the host's active theme. If the user has dark mode +enabled, your plugin renders with dark colors. + +Common things to audit: + +- **Hardcoded colors** in your CSS (e.g. `color: #333`, `background: white`). + They won't follow the theme; users in dark mode will see them as-is and + the contrast may break. +- **Hardcoded SVG fills** in custom icons. Use `fill="currentColor"` so they + inherit the surrounding `color`. +- **Custom CSS that mixes library components with your own colors.** Verify + the combinations look right in both themes. + +### What's new + +A new set of CSS custom properties is available inside ``. They +follow the host's active theme: + +- `--color--surface`, `--color--surface-hover`, `--color--surface-muted`, … +- `--color--ink`, `--color--ink-subtle`, `--color--ink-placeholder`, + `--color--ink-accent`, … +- `--color--border`, `--color--border-hover` +- Per-context variants: `--color--primary--surface`, `--color--primary--ink`, + `--color--tinted--surface`, `--color--accent--ink`, + `--color--selected--surface`, `--color--disabled--surface`, + `--color--danger--surface`, … +- Feedback: `--color--feedback-fail--ink`, + `--color--feedback-warning--surface`, `--color--feedback-success--ink`, … +- Plus diff, status, overlay, stacked, progress, tooltip, code and shadow + groups. + +See the `Canvas` JSDoc for the full reference. + +All built-in components (`Button`, `Dropdown`, `Section`, `TextInput`, +`SwitchInput`, `Toolbar`, `Tooltip`, …) now use these tokens. Built-in +icons render with `fill="currentColor"` and inherit the surrounding +`color`. + +### Deprecated CSS variables + +Two groups of legacy color variables remain available for backward +compatibility, but **both are deprecated and will be removed in a future +major version**. Migrate everything to the `--color--*` semantic tokens. + +**Structural legacy vars** — defined inside `` (e.g. +`--border-color`, `--base-body-color`, `--light-bg-color`, `--alert-color`, +`--add-color`, `--remove-color`, …). Each one now resolves to the closest +semantic token first, falling back to its original light-mode value if the +token is unavailable. So a v3 plugin that still uses them will follow the +active theme. + +**Theme-derived legacy vars** — `--accent-color`, `--primary-color`, +`--light-color`, `--dark-color`, `--semi-transparent-accent-color` (plus +their `*-rgb-components` counterparts). These are emitted from the +deprecated `ctx.theme` field, which the host now pins to **light values +only**, regardless of the active theme. Mixing these with the new +`--color--*` tokens in dark mode will produce a light accent on a dark +surface — visibly mismatched. Replace them with the corresponding +semantic tokens (`--color--accent--surface`, `--color--accent--ink`, +`--color--primary--surface`, etc.). + +Non-color tokens (`--spacing-*`, `--font-size-*`, `--font-weight-bold`, +`--material-ease`, font families) are stable and remain available. diff --git a/packages/react-ui/__tests__/index.test.ts b/packages/react-ui/__tests__/index.test.ts index abe192d..e175c35 100644 --- a/packages/react-ui/__tests__/index.test.ts +++ b/packages/react-ui/__tests__/index.test.ts @@ -1,5 +1,88 @@ -describe('connect()', () => { - it('works', () => { - expect(true).toBeTruthy(); +import { generateStyleFromCtx } from '../src/generateStyleFromCtx'; + +const baseCtx = { + bodyPadding: [10, 20, 10, 20] as [number, number, number, number], + theme: { + primaryColor: 'rgb(0, 76, 209)', + accentColor: 'rgb(0, 76, 209)', + semiTransparentAccentColor: 'rgb(0, 76, 209)', + lightColor: 'rgb(219, 234, 254)', + darkColor: 'rgb(0, 33, 90)', + }, + semanticColorTokensTheme: {}, +}; + +describe('generateStyleFromCtx', () => { + it('generates existing theme CSS variables with RGB components', () => { + const style = generateStyleFromCtx(baseCtx as any) as any; + + expect(style['--primary-color']).toBe('rgb(0, 76, 209)'); + expect(style['--primary-color-rgb-components']).toBe('0, 76, 209'); + expect(style['--accent-color']).toBe('rgb(0, 76, 209)'); + expect(style['--semi-transparent-accent-color']).toBe('rgb(0, 76, 209)'); + expect(style.padding).toBe('10px 20px 10px 20px'); + }); + + it('respects noBodyPadding flag', () => { + const style = generateStyleFromCtx(baseCtx as any, true) as any; + + expect(style.padding).toBeUndefined(); + expect(style['--primary-color']).toBe('rgb(0, 76, 209)'); + }); + + it('generates semantic color token CSS variables without RGB components', () => { + const ctx = { + ...baseCtx, + semanticColorTokensTheme: { + colorSurface: 'rgb(255, 255, 255)', + colorInk: 'rgb(52, 54, 58)', + colorFeedbackFailInk: 'rgb(255, 94, 73)', + }, + }; + + const style = generateStyleFromCtx(ctx as any) as any; + + expect(style['--color--surface']).toBe('rgb(255, 255, 255)'); + expect(style['--color--ink']).toBe('rgb(52, 54, 58)'); + expect(style['--color--feedback-fail--ink']).toBe('rgb(255, 94, 73)'); + + // No RGB components for semantic tokens + expect(style['--color--surface-rgb-components']).toBeUndefined(); + expect(style['--color--ink-rgb-components']).toBeUndefined(); + }); + + it('works when semanticColorTokensTheme is undefined', () => { + const style = generateStyleFromCtx(baseCtx as any) as any; + + expect(style['--primary-color']).toBe('rgb(0, 76, 209)'); + // Should not throw, no semantic token variables present + expect(style['--color--surface']).toBeUndefined(); + }); + + it('works when semanticColorTokensTheme is an empty object', () => { + const ctx = { + ...baseCtx, + semanticColorTokensTheme: {}, + }; + + const style = generateStyleFromCtx(ctx as any) as any; + + expect(style['--primary-color']).toBe('rgb(0, 76, 209)'); + const semanticKeys = Object.keys(style).filter((k: string) => k.startsWith('--color--')); + expect(semanticKeys).toHaveLength(0); + }); + + it('handles unknown future tokens via the Record signature', () => { + const ctx = { + ...baseCtx, + semanticColorTokensTheme: { + colorSomeFutureToken: 'rgb(100, 200, 50)', + }, + }; + + const style = generateStyleFromCtx(ctx as any) as any; + + // Unknown tokens fall back to camelToDash (single-dash) + expect(style['--color-some-future-token']).toBe('rgb(100, 200, 50)'); }); }); diff --git a/packages/react-ui/package-lock.json b/packages/react-ui/package-lock.json index c05cb56..b18aee9 100644 --- a/packages/react-ui/package-lock.json +++ b/packages/react-ui/package-lock.json @@ -1,17 +1,17 @@ { "name": "datocms-react-ui", - "version": "2.1.4", + "version": "3.0.1-alpha.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "datocms-react-ui", - "version": "2.1.4", + "version": "3.0.1-alpha.0", "license": "MIT", "dependencies": { "@floating-ui/react": "^0.27.16", "classnames": "^2.3.1", - "datocms-plugin-sdk": "^2.1.1", + "datocms-plugin-sdk": "^3.0.1-alpha.0", "react-intersection-observer": "^8.31.0", "react-select": "^5.2.1", "scroll-into-view-if-needed": "^2.2.20" diff --git a/packages/react-ui/package.json b/packages/react-ui/package.json index 517da53..2f19963 100644 --- a/packages/react-ui/package.json +++ b/packages/react-ui/package.json @@ -1,6 +1,6 @@ { "name": "datocms-react-ui", - "version": "2.1.4", + "version": "3.0.1-alpha.0", "description": "React components to use inside DatoCMS plugins", "keywords": [ "datocms", @@ -42,7 +42,7 @@ "dependencies": { "@floating-ui/react": "^0.27.16", "classnames": "^2.3.1", - "datocms-plugin-sdk": "^2.1.1", + "datocms-plugin-sdk": "^3.0.1-alpha.0", "react-intersection-observer": "^8.31.0", "react-select": "^5.2.1", "scroll-into-view-if-needed": "^2.2.20" diff --git a/packages/react-ui/src/Button/styles.module.css b/packages/react-ui/src/Button/styles.module.css index 6c236fb..7495b91 100644 --- a/packages/react-ui/src/Button/styles.module.css +++ b/packages/react-ui/src/Button/styles.module.css @@ -7,7 +7,7 @@ cursor: pointer; line-height: inherit; background-color: transparent; - color: var(--base-body-color); + color: var(--color--ink); -webkit-appearance: none; -moz-appearance: none; border-radius: 4px; @@ -32,56 +32,56 @@ } .buttonType-muted { - background-color: var(--light-color); - color: var(--accent-color); + background-color: var(--color--tinted--surface); + color: var(--color--tinted--ink); &.disabled { - background-color: var(--light-bg-color); - color: rgba(0, 0, 0, 0.2); + background-color: var(--color--disabled--surface); + color: var(--color--disabled--ink); &:hover, &:focus, &:active { - color: rgba(0, 0, 0, 0.2); + color: var(--color--disabled--ink); } } } .buttonType-primary { - background-color: var(--accent-color); - color: white; + background-color: var(--color--primary--surface); + color: var(--color--primary--ink); &:hover, &:focus, &:active { - color: white; + color: var(--color--primary--ink); } &.disabled { - background-color: var(--disabled-bg-color); - color: rgba(0, 0, 0, 0.2); + background-color: var(--color--disabled--surface); + color: var(--color--disabled--ink); &:hover, &:focus, &:active { - color: rgba(0, 0, 0, 0.2); + color: var(--color--disabled--ink); } } } .buttonType-negative { - background-color: var(--alert-color); - color: white; + background-color: var(--color--danger--surface); + color: var(--color--danger--ink); &:hover, &:focus, &:active { - color: white; - background-color: var(--alert-color); + color: var(--color--danger--ink); + background-color: var(--color--danger--surface); } &.disabled { - background-color: var(--disabled-bg-color); - color: rgba(0, 0, 0, 0.2); + background-color: var(--color--disabled--surface); + color: var(--color--disabled--ink); } } @@ -128,7 +128,7 @@ line-height: 0.6; svg { - fill: var(--accent-color); + fill: var(--color--ink-accent); } } diff --git a/packages/react-ui/src/ButtonGroup/Button/styles.module.css b/packages/react-ui/src/ButtonGroup/Button/styles.module.css index 5ec7555..de5a596 100644 --- a/packages/react-ui/src/ButtonGroup/Button/styles.module.css +++ b/packages/react-ui/src/ButtonGroup/Button/styles.module.css @@ -2,8 +2,8 @@ font-family: inherit; cursor: pointer; line-height: inherit; - background-color: white; - color: var(--base-body-color); + background-color: var(--color--surface); + color: var(--color--ink); -webkit-appearance: none; -moz-appearance: none; font-size: inherit; @@ -11,22 +11,20 @@ border: 0; padding: 0; padding: 7px 13px; - border-right: 1px solid var(--border-color); - border: 1px solid var(--border-color); - border-left-width: 0; + border: 1px solid var(--color--border); cursor: pointer; display: flex; align-items: center; justify-column: center; - color: rgba(var(--base-body-color-rgb-components, 0.6)); + color: var(--color--ink-subtle); } .Button:hover { - background-color: var(--light-bg-color); + background-color: var(--color--surface-hover); } .Button svg { - fill: var(--light-body-color); + fill: var(--color--ink-subtle); } .Button--s { @@ -35,31 +33,31 @@ .Button--disabled { cursor: not-allowed; - color: var(--light-body-color); + color: var(--color--disabled--ink); } .Button--disabled:hover { - background: white; + background: var(--color--surface); } .Button--selected { - background-color: var(--accent-color); - border-color: var(--accent-color); - color: white; + background-color: var(--color--accent--surface); + border-color: var(--color--selected--border); + color: var(--color--accent--ink); } .Button--selected svg { - fill: white; + fill: var(--color--accent--ink); } .Button--selected:hover { - background-color: var(--accent-color); + background-color: var(--color--accent--surface); } .Button--selected:hover, .Button--selected.Button--disabled { - background-color: rgba(var(--accent-color-rgb-components), 0.8); - border-color: rgba(var(--accent-color-rgb-components), 0.8); + background-color: var(--color--accent--surface); + border-color: var(--color--selected--border); } .Button:first-child { diff --git a/packages/react-ui/src/ButtonGroup/Group/styles.module.css b/packages/react-ui/src/ButtonGroup/Group/styles.module.css index 038352a..3c45190 100644 --- a/packages/react-ui/src/ButtonGroup/Group/styles.module.css +++ b/packages/react-ui/src/ButtonGroup/Group/styles.module.css @@ -1,6 +1,6 @@ .Group { display: flex; align-items: stretch; - background-color: white; + background-color: var(--color--surface); overflow: hidden; } diff --git a/packages/react-ui/src/Canvas/index.tsx b/packages/react-ui/src/Canvas/index.tsx index 1c64ecf..d4e1547 100644 --- a/packages/react-ui/src/Canvas/index.tsx +++ b/packages/react-ui/src/Canvas/index.tsx @@ -34,283 +34,188 @@ export type CanvasProps = { }; /** - * @example Color palette CSS variables + * @example Semantic color token CSS variables * - * Within the `Canvas` component, a color palette is made available as a set of - * CSS variables, allowing you to conform to the theme of the current - * environment: + * Inside `Canvas`, the host exposes a full semantic color palette as CSS + * custom properties. Components should reference these tokens directly — + * they adapt to the user's active theme (including dark mode) + * automatically. + * + * ### How to read a token name + * + * ``` + * --color--{property} // standalone (one -- after color) + * --color--{context}--{property} // context pair (two -- after color) + * ``` + * + * **Properties** — `surface` (backgrounds), `ink` (text/icons), + * `border` (1px lines), `outline` (focus rings), plus `fill` / `track` + * for progress bars. + * + * **Standalone** tokens work on any neutral page. **Contexts** are + * self-contained environments: always pair a `surface` with the `ink`, + * `border`, and hover states from the *same* context. Never mix — e.g. + * don't put `--color--primary--ink` on `--color--danger--surface`. + * + * Non-color tokens `--shadow--elevated` / `--shadow--float` / + * `--shadow--ambient` are ready-made `box-shadow` composites. * * ```js * - *
- * - * - * - * - * + *
+ *
- * --base-body-color - * - *
- *
+ * {[ + * ['--color--surface', 'Default page background'], + * ['--color--surface-hover', 'Hovered row / list item'], + * ['--color--surface-muted', 'Muted section / card background'], + * ['--color--ink', 'Primary text'], + * ['--color--ink-subtle', 'Secondary text / captions'], + * ['--color--ink-hover', 'Text under hover'], + * ['--color--ink-muted', 'De-emphasized text'], + * ['--color--ink-placeholder', 'Input placeholder text'], + * ['--color--ink-primary', 'Brand-highlighted text / icons'], + * ['--color--ink-accent', 'Links / accent text'], + * ['--color--ink-disabled', 'Disabled text'], + * ['--color--border', 'Default 1px border'], + * ['--color--border-hover', 'Border under hover'], + * ].map(([t, d]) => ( + * + * + * + * * - * - * - * - * - * - * - * - * - * - *
{t}{d}
- * --light-body-color - * - *
- *
- * --placeholder-body-color - * - *
- *
+ * ))} + * *
- *
- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
- * --light-bg-color - * - *
- *
- * --lighter-bg-color - * - *
- *
- * --disabled-bg-color - * - *
- *
- * --border-color - * - *
- *
- * --darker-border-color - * - *
- *
- * --alert-color - * - *
- *
- * --warning-color - * - *
- *
- * --notice-color - * - *
- *
- * --warning-bg-color - * - *
- *
- * --add-color - * - *
- *
- * --remove-color - * - *
- *
+ * + *
+ * + * {['--color--raised--surface', '--color--raised--surface-hover', '--color--raised--surface-active'] + * .map((t) => ())} + *
{t}
*
- *
- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
- * --accent-color - * - *
- *
- * --primary-color - * - *
- *
- * --light-color - * - *
- *
- * --dark-color - * - *
- *
+ * + *
+ * + * {['--color--primary--surface', '--color--primary--surface-hover', '--color--primary--surface-active', '--color--primary--surface-muted', '--color--primary--ink', '--color--primary--border'] + * .map((t) => ())} + *
{t}
+ *
+ * + *
+ * + * {['--color--tinted--surface', '--color--tinted--surface-hover', '--color--tinted--surface-active', '--color--tinted--ink'] + * .map((t) => ())} + *
{t}
+ *
+ * + *
+ * + * {['--color--accent--surface', '--color--accent--ink', + * '--color--selected--surface', '--color--selected--ink', '--color--selected--border', + * '--color--disabled--surface', '--color--disabled--ink', + * '--color--danger--surface', '--color--danger--ink', + * '--color--enterprise--surface'] + * .map((t) => ())} + *
{t}
+ *
+ * + *
+ * + * {['--color--focus--border', '--color--focus--outline'] + * .map((t) => ())} + *
{t}
+ *
+ * + *
+ * + * {['--color--feedback-fail--ink', '--color--feedback-fail--border', '--color--feedback-fail--outline', + * '--color--feedback-warning--ink', '--color--feedback-warning--surface', '--color--feedback-warning--border', + * '--color--feedback-success--ink', '--color--feedback-success--border'] + * .map((t) => ())} + *
{t}
+ *
+ * + *
+ * + * {['--color--highlight--surface'] + * .map((t) => ())} + *
{t}
+ *
+ * + *
+ * + * {['--color--diff-added--surface', '--color--diff-removed--surface', '--color--diff-changed--surface', + * '--color--diff-added--surface-subtle', '--color--diff-removed--surface-subtle', '--color--diff-changed--surface-subtle', + * '--color--diff-added--outline-subtle', '--color--diff-removed--outline-subtle', '--color--diff-changed--outline-subtle', + * '--color--diff-changed--border', '--color--diff-changed--border-negative'] + * .map((t) => ())} + *
{t}
+ *
+ * + *
+ * + * {['--color--status-draft--ink', '--color--status-outdated--ink', '--color--status-published--ink'] + * .map((t) => ())} + *
{t}Sample text
+ *
+ * + *
+ * + * {['--color--backdrop--surface', '--color--backdrop--ink', + * '--color--overlay--surface', '--color--overlay--surface-subtle', '--color--overlay--ink'] + * .map((t) => ())} + *
{t}
+ *
+ * + *
+ *

Stacked gives you three layers of depth (base → surface → raised) plus buttons, borders and a translucent fill. Use it when a dark inline panel needs internal hierarchy.

+ * + * {['--color--stacked--surface-base', '--color--stacked--surface', '--color--stacked--surface-raised', + * '--color--stacked--surface-hover', '--color--stacked--surface-translucent', + * '--color--stacked--surface-button', '--color--stacked--surface-button-active', + * '--color--stacked--ink', '--color--stacked--ink-subtle', '--color--stacked--border'] + * .map((t) => ())} + *
{t}
+ *
+ * + *
+ * + * {['--color--progress--track', '--color--progress--fill', '--color--progress--fill-hover'] + * .map((t) => ())} + *
{t}
+ *
+ * + *
+ * + * {['--color--tooltip--surface', '--color--tooltip--surface-hover', '--color--tooltip--ink', '--color--tooltip--ink-subtle'] + * .map((t) => ())} + *
{t}
+ *
+ * + *
+ * + * {['--color--code--surface', '--color--code--ink'] + * .map((t) => ())} + *
{t}
+ *
+ * + *
+ * + * {['--color--shadow-subtle', '--color--shadow', '--color--shadow-strong', '--color--scrollbar'] + * .map((t) => ())} + *
{t}
+ *
+ * + *
+ *
+ * {['--shadow--elevated', '--shadow--float', '--shadow--ambient'].map((t) => ( + *
+ *
+ * {t} + *
+ * ))} + *
*
* ; * ``` @@ -443,7 +348,7 @@ export type CanvasProps = { * *
*
*
*
*
*
* { diff --git a/packages/react-ui/src/Dropdown/styles.module.css b/packages/react-ui/src/Dropdown/styles.module.css index b2fe907..a021acc 100644 --- a/packages/react-ui/src/Dropdown/styles.module.css +++ b/packages/react-ui/src/Dropdown/styles.module.css @@ -10,35 +10,37 @@ .Dropdown__menu__search { padding: 7px; - border-bottom: 1px solid var(--border-color); + border-bottom: 1px solid var(--color--border); } .Dropdown__menu__search__input { display: block; box-sizing: border-box; width: 100%; - border: 1px solid var(--border-color); + border: 1px solid var(--color--border); appearance: none; + background-color: var(--color--surface); background-image: none; transition: border 0.2s var(--material-ease); resize: none; font-family: inherit; + color: inherit; padding: 8px; border-radius: 3px; font-size: 0.9em; &::placeholder { - color: var(--placeholder-body-color); + color: var(--color--ink-placeholder); } &:hover { - border-color: var(--darker-border-color); + border-color: var(--color--border-hover); } &:focus { outline: 0; - border-color: var(--accent-color); - box-shadow: 0 0 0 3px var(--semi-transparent-accent-color); + border-color: var(--color--focus--border); + box-shadow: 0 0 0 3px var(--color--focus--outline); } } @@ -49,8 +51,8 @@ .Dropdown__menu { min-width: 200px; - background-color: white; - box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2); + background-color: var(--color--raised--surface); + box-shadow: 0 3px 10px var(--color--shadow); border-radius: 4px; margin-bottom: var(--spacing-xl); padding: 1px 0; @@ -64,8 +66,8 @@ .Dropdown__menu__group__title { padding: 5px 15px 3px; - color: var(--light-body-color); - background-color: var(--light-bg-color); + color: var(--color--ink-subtle); + background-color: var(--color--surface-muted); text-transform: uppercase; font-size: var(--font-size-xs); } @@ -77,7 +79,7 @@ .Dropdown__menu__text { text-align: left; padding: 4px 15px; - color: var(--light-body-color); + color: var(--color--ink-subtle); position: relative; display: block; line-height: 1.2; @@ -86,7 +88,7 @@ .Dropdown__menu__option { text-align: left; padding: 4px 15px; - color: var(--base-body-color); + color: var(--color--ink); position: relative; text-decoration: none; white-space: nowrap; @@ -95,7 +97,7 @@ &:hover, &:focus { - background-color: var(--light-bg-color); + background-color: var(--color--surface-hover); } & > a { @@ -106,7 +108,7 @@ } .Dropdown__menu__option--is-selected { - background-color: var(--light-bg-color); + background-color: var(--color--selected--surface); } .Dropdown__menu__option--is-disabled { @@ -118,19 +120,19 @@ } .Dropdown__menu__option--is-dangerous { - color: var(--alert-color); + color: var(--color--feedback-fail--ink); svg { - fill: var(--alert-color); + fill: var(--color--feedback-fail--ink); } &:hover, &:focus { - background-color: var(--alert-color); - color: white; + background-color: var(--color--danger--surface); + color: var(--color--danger--ink); svg { - fill: white; + fill: var(--color--danger--ink); } } } @@ -161,7 +163,7 @@ border-radius: 4px; height: 8px; width: 8px; - background-color: var(--alert-color); + background-color: var(--color--danger--surface); } } @@ -186,8 +188,8 @@ padding-right: 8px; display: inline-block; vertical-align: middle; - color: var(--light-body-color); - fill: var(--light-body-color); + color: var(--color--ink-subtle); + fill: var(--color--ink-subtle); } } @@ -202,7 +204,7 @@ cursor: pointer; line-height: inherit; background-color: transparent; - color: var(--base-body-color); + color: var(--color--ink); -webkit-appearance: none; -moz-appearance: none; box-sizing: border-box; @@ -213,7 +215,7 @@ width: auto; opacity: 0; line-height: 10px; - color: var(--light-body-color); + color: var(--color--ink-subtle); padding: 3px; font-size: 13px; position: relative; @@ -234,21 +236,21 @@ } svg { - fill: var(--light-body-color); + fill: var(--color--ink-subtle); } } .Dropdown__menu__option__icon--delete { - color: var(--alert-color); + color: var(--color--feedback-fail--ink); svg { - fill: var(--alert-color); + fill: var(--color--feedback-fail--ink); } } .Dropdown__menu__separator { margin: 8px 0; height: 1px; - background-color: var(--border-color); + background-color: var(--color--border); } .Dropdown__menu { diff --git a/packages/react-ui/src/FieldError/styles.module.css b/packages/react-ui/src/FieldError/styles.module.css index 17c0df6..1093951 100644 --- a/packages/react-ui/src/FieldError/styles.module.css +++ b/packages/react-ui/src/FieldError/styles.module.css @@ -1,5 +1,5 @@ .fieldError { - color: var(--alert-color); + color: var(--color--feedback-fail--ink); line-height: 1.2; font-size: var(--font-size-xs); margin-top: var(--spacing-s); diff --git a/packages/react-ui/src/FieldHint/styles.module.css b/packages/react-ui/src/FieldHint/styles.module.css index 2d83927..cbd03c6 100644 --- a/packages/react-ui/src/FieldHint/styles.module.css +++ b/packages/react-ui/src/FieldHint/styles.module.css @@ -1,5 +1,5 @@ .fieldHint { - color: var(--light-body-color); + color: var(--color--ink-subtle); line-height: 1.2; font-size: var(--font-size-xs); margin-top: var(--spacing-s); diff --git a/packages/react-ui/src/FormLabel/styles.module.css b/packages/react-ui/src/FormLabel/styles.module.css index 9c43ed1..5cdab54 100644 --- a/packages/react-ui/src/FormLabel/styles.module.css +++ b/packages/react-ui/src/FormLabel/styles.module.css @@ -1,6 +1,6 @@ .formLabel { display: flex; - color: var(--light-body-color); + color: var(--color--ink-subtle); margin-bottom: var(--spacing-s); align-items: center; @@ -12,7 +12,7 @@ } .formLabel--error { - color: var(--alert-color); + color: var(--color--feedback-fail--ink); } .formLabel__label { diff --git a/packages/react-ui/src/HotKey/styles.module.css b/packages/react-ui/src/HotKey/styles.module.css index 5b088b5..588d9f7 100644 --- a/packages/react-ui/src/HotKey/styles.module.css +++ b/packages/react-ui/src/HotKey/styles.module.css @@ -17,6 +17,6 @@ .hotKeyKey { padding: 5px 8px; - background: var(--light-color); + background: var(--color--tinted--surface); border-radius: 3px; } diff --git a/packages/react-ui/src/Section/styles.module.css b/packages/react-ui/src/Section/styles.module.css index 3b27b41..9dd36dd 100644 --- a/packages/react-ui/src/Section/styles.module.css +++ b/packages/react-ui/src/Section/styles.module.css @@ -9,7 +9,7 @@ right: -30px; bottom: -20px; left: -30px; - box-shadow: 0 0 0 4px var(--accent-color); + box-shadow: 0 0 0 4px var(--color--focus--border); border-radius: 4px; animation: pageContentSectionHighligh 4s 0.25s ease-in-out forwards; pointer-events: none; @@ -30,7 +30,7 @@ left: 0; right: 0; height: 1px; - background-color: var(--border-color); + background-color: var(--color--border); z-index: 1; } } @@ -42,7 +42,7 @@ margin-right: var(--spacing-l); padding-left: var(--spacing-m); padding-right: var(--spacing-m); - background-color: white; + background-color: var(--color--surface); position: relative; z-index: 2; display: inline-flex; @@ -66,7 +66,7 @@ width: 0; border-top: 6px solid transparent; border-bottom: 6px solid transparent; - border-left: 6px solid var(--base-body-color); + border-left: 6px solid var(--color--ink); left: 14px; top: 50%; margin-top: -6px; @@ -85,14 +85,14 @@ @keyframes pageContentSectionHighligh { 0% { - box-shadow: 0 0 0 4px var(--accent-color), - 0 0 0 4px rgba(var(--accent-color-rgb-components), 0.7); + box-shadow: 0 0 0 4px var(--color--focus--border), + 0 0 0 4px var(--color--focus--outline); } 15% { - box-shadow: 0 0 0 4px var(--accent-color), 0 0 0 80px transparent; + box-shadow: 0 0 0 4px var(--color--focus--border), 0 0 0 80px transparent; } 75% { - box-shadow: 0 0 0 4px var(--accent-color), 0 0 0 80px transparent; + box-shadow: 0 0 0 4px var(--color--focus--border), 0 0 0 80px transparent; } 100% { box-shadow: 0 0 0 4px transparent, 0 0 0 80px transparent; diff --git a/packages/react-ui/src/SelectInput/index.tsx b/packages/react-ui/src/SelectInput/index.tsx index 25dec66..982146a 100644 --- a/packages/react-ui/src/SelectInput/index.tsx +++ b/packages/react-ui/src/SelectInput/index.tsx @@ -18,15 +18,15 @@ const themeConfig: ThemeConfig = (existing) => ({ borderRadius: 0, colors: { ...existing.colors, - primary25: 'var(--semi-transparent-accent-color)', + primary25: 'var(--color--surface-hover)', // disabled - neutral10: 'var(--border-color)', + neutral10: 'var(--color--border)', // normal - neutral20: 'var(--border-color)', + neutral20: 'var(--color--border)', // focused - primary: 'var(--accent-color)', + primary: 'var(--color--focus--border)', // hover - neutral30: 'var(--darker-border-color)', + neutral30: 'var(--color--border-hover)', }, }); @@ -35,7 +35,7 @@ const useStyles = (isDisabled?: boolean, error?: boolean) => { return { placeholder: (provided) => ({ ...provided, - color: 'var(--placeholder-body-color)', + color: 'var(--color--ink-placeholder)', }), container: (provided) => { return { @@ -55,57 +55,83 @@ const useStyles = (isDisabled?: boolean, error?: boolean) => { if (isFocused) { return { ...result, - borderColor: error ? 'var(--alert-color)' : 'var(--accent-color)', - backgroundColor: isDisabled ? 'var(--disabled-color)' : 'white', + borderColor: error + ? 'var(--color--feedback-fail--border)' + : 'var(--color--focus--border)', + backgroundColor: isDisabled + ? 'var(--color--disabled--surface)' + : 'var(--color--surface)', boxShadow: `0 0 0 3px ${ error - ? 'rgba(var(--alert-color-rgb-components), 0.2)' - : 'var(--semi-transparent-accent-color)' + ? 'var(--color--feedback-fail--outline)' + : 'var(--color--focus--outline)' }`, '&:hover': { - borderColor: error ? 'var(--alert-color)' : 'var(--accent-color)', + borderColor: error + ? 'var(--color--feedback-fail--border)' + : 'var(--color--focus--border)', }, }; } return { ...result, - borderColor: error ? 'var(--alert-color)' : 'var(--border-color)', - backgroundColor: isDisabled ? 'var(--disabled-color)' : 'white', + borderColor: error + ? 'var(--color--feedback-fail--border)' + : 'var(--color--border)', + backgroundColor: isDisabled + ? 'var(--color--disabled--surface)' + : 'var(--color--surface)', '&:hover': { borderColor: error - ? 'var(--alert-color)' - : 'var(--darker-border-color)', + ? 'var(--color--feedback-fail--border)' + : 'var(--color--border-hover)', }, }; }, multiValueRemove: (provided) => ({ ...provided, cursor: 'pointer', + color: 'var(--color--tinted--ink)', + ':hover': { + backgroundColor: 'var(--color--tinted--surface-hover)', + color: 'var(--color--tinted--ink)', + }, }), menu: (provided) => { return { ...provided, zIndex: 1000, minWidth: 250, + backgroundColor: 'var(--color--raised--surface)', }; }, - input: (provided) => { - const result = { - ...provided, + singleValue: (provided) => ({ + ...provided, + color: 'var(--color--ink)', + }), + input: (provided) => ({ + ...provided, + color: 'var(--color--ink)', + boxShadow: 'none', + 'input:focus': { boxShadow: 'none', - 'input:focus': { - boxShadow: 'none', - }, - }; - - return result; - }, + }, + }), + option: (provided, { isFocused, isSelected }) => ({ + ...provided, + backgroundColor: isSelected + ? 'var(--color--selected--surface)' + : isFocused + ? 'var(--color--surface-hover)' + : undefined, + color: 'var(--color--ink)', + }), multiValue: (provided) => { return { ...provided, zIndex: 100, - backgroundColor: 'var(--light-color)', + backgroundColor: 'var(--color--tinted--surface)', userSelect: 'none', }; }, @@ -113,6 +139,7 @@ const useStyles = (isDisabled?: boolean, error?: boolean) => { ...provided, fontSize: 'inherit', padding: 3, + color: 'var(--color--tinted--ink)', }), }; }, [isDisabled, error]); diff --git a/packages/react-ui/src/SidebarPanel/index.tsx b/packages/react-ui/src/SidebarPanel/index.tsx index 6a42521..5ee3db3 100644 --- a/packages/react-ui/src/SidebarPanel/index.tsx +++ b/packages/react-ui/src/SidebarPanel/index.tsx @@ -1,33 +1,8 @@ import classNames from 'classnames'; import React, { type ReactNode, useState } from 'react'; +import { CaretDownIcon, CaretUpIcon } from '../icons'; import s from './styles.module.css.json'; -function ChevronDownIcon() { - return ( - - - - ); -} - -function ChevronUpIcon() { - return ( - - - - ); -} - export type SidebarPanelProps = { title?: ReactNode; startOpen?: boolean; @@ -44,7 +19,7 @@ export type SidebarPanelProps = { *
* Content @@ -61,7 +36,7 @@ export type SidebarPanelProps = { * display: 'flex', * justifyContent: 'center', * alignItems: 'center', - * background: 'var(--light-bg-color)', + * background: 'var(--color--surface-muted)', * }} * > * Main content @@ -92,7 +67,7 @@ export function SidebarPanel({ >
{title}
- {open ? : } + {open ? : }
)} diff --git a/packages/react-ui/src/SidebarPanel/styles.module.css b/packages/react-ui/src/SidebarPanel/styles.module.css index 2224d9d..14a0e0a 100644 --- a/packages/react-ui/src/SidebarPanel/styles.module.css +++ b/packages/react-ui/src/SidebarPanel/styles.module.css @@ -1,13 +1,13 @@ .SidebarPanel { - border-bottom: 1px solid var(--border-color); + border-bottom: 1px solid var(--color--border); } .SidebarPanel__header { font-family: inherit; cursor: pointer; line-height: inherit; - background-color: white; - color: var(--base-body-color); + background-color: var(--color--surface); + color: var(--color--ink); -webkit-appearance: none; -moz-appearance: none; font-size: inherit; @@ -18,13 +18,13 @@ width: 100%; display: flex; align-items: center; - background-color: var(--light-bg-color); + background-color: var(--color--surface-muted); user-select: none; } .SidebarPanel__header:hover, .SidebarPanel__header:focus { - background-color: var(--lighter-bg-color); + background-color: var(--color--surface-hover); } .SidebarPanel__header__title { @@ -41,7 +41,7 @@ .SidebarPanel__content { padding: 20px; - background-color: white; + background-color: var(--color--surface); } .SidebarPanel__content--no-padding { diff --git a/packages/react-ui/src/Spinner/styles.module.css b/packages/react-ui/src/Spinner/styles.module.css index cb2437c..ddce775 100644 --- a/packages/react-ui/src/Spinner/styles.module.css +++ b/packages/react-ui/src/Spinner/styles.module.css @@ -12,7 +12,7 @@ .Spinner__bar { animation: Spinner__spin 1.2s linear infinite; - background-color: var(--light-body-color); + background-color: var(--color--ink-subtle); position: absolute; width: 40%; height: 14%; diff --git a/packages/react-ui/src/SplitView/SplitViewSash/styles.module.css b/packages/react-ui/src/SplitView/SplitViewSash/styles.module.css index c6ee5d0..e5e930a 100644 --- a/packages/react-ui/src/SplitView/SplitViewSash/styles.module.css +++ b/packages/react-ui/src/SplitView/SplitViewSash/styles.module.css @@ -3,7 +3,7 @@ position: absolute; top: 0; transition: background-color 0.2s 0.15s; - background-color: rgba(var(--light-color-components), 0); + background-color: transparent; width: 100%; z-index: 2; display: flex; @@ -13,7 +13,7 @@ .SplitViewSash:hover, .SplitViewSash--dragging { - background-color: var(--light-color); + background-color: var(--color--tinted--surface); } .SplitViewSash:hover:has(.SplitViewSash__content:hover), @@ -47,14 +47,14 @@ width: 20px; height: 20px; border-radius: 6px; - border: 1px solid var(--border-color); - background: white; + border: 1px solid var(--color--border); + background: var(--color--surface); z-index: 2; display: flex; align-items: center; justify-content: center; font-size: 10px; - color: var(--light-body-color); + color: var(--color--ink-subtle); } .SplitViewSash__content__button svg { @@ -63,6 +63,6 @@ } .SplitViewSash__content:hover .SplitViewSash__content__button { - background: var(--light-bg-color); - color: var(--base-body-color); + background: var(--color--surface-hover); + color: var(--color--ink); } diff --git a/packages/react-ui/src/SwitchField/styles.module.css b/packages/react-ui/src/SwitchField/styles.module.css index 4d2f579..71672f3 100644 --- a/packages/react-ui/src/SwitchField/styles.module.css +++ b/packages/react-ui/src/SwitchField/styles.module.css @@ -16,7 +16,7 @@ -ms-user-select: text; user-select: text; margin-bottom: 0; - color: var(--base-body-color); + color: var(--color--ink); } .switchField__below { diff --git a/packages/react-ui/src/SwitchInput/styles.module.css b/packages/react-ui/src/SwitchInput/styles.module.css index 299d6b3..7b632d8 100644 --- a/packages/react-ui/src/SwitchInput/styles.module.css +++ b/packages/react-ui/src/SwitchInput/styles.module.css @@ -1,8 +1,9 @@ .switchInput__inner { - color: #fff; + color: var(--color--primary--ink); font-size: 12px; position: absolute; left: 24px; + transition: left 0.3s cubic-bezier(0.35, 0, 0.25, 1); } .switchInput { @@ -14,8 +15,8 @@ line-height: 20px; vertical-align: middle; border-radius: 20px 20px; - border: 1px solid #ccc; - background-color: #ccc; + border: 1px solid var(--color--border); + background-color: var(--color--ink-muted); cursor: pointer; transition: all 0.3s cubic-bezier(0.35, 0, 0.25, 1); @@ -26,18 +27,20 @@ left: 2px; top: 1px; border-radius: 50% 50%; - background-color: #ffffff; + background-color: var(--color--surface); content: ' '; cursor: pointer; - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.26); + box-shadow: 0 2px 5px var(--color--shadow); transform: scale(1); - transition: left 0.3s cubic-bezier(0.35, 0, 0.25, 1); + transition: + left 0.3s cubic-bezier(0.35, 0, 0.25, 1), + transform 0.3s cubic-bezier(0.35, 0, 0.25, 1); animation-timing-function: cubic-bezier(0.35, 0, 0.25, 1); animation-duration: 0.3s; animation-name: switchInput__off; } - &:hover, + &:hover:after, &:focus:after { transform: scale(1.1); animation-name: switchInput__on; @@ -45,8 +48,8 @@ } .switchInput__checked { - border: 1px solid var(--accent-color); - background-color: var(--accent-color); + border: 1px solid var(--color--selected--border); + background-color: var(--color--primary--surface); .switchInput__inner { left: 6px; @@ -59,16 +62,16 @@ .switchInput__disabled { cursor: no-drop; - background: #ccc; - border-color: #ccc; + background: var(--color--disabled--surface); + border-color: var(--color--border); &:after { - background: #9e9e9e; + background: var(--color--disabled--ink); animation-name: none; cursor: no-drop; } - &:hover, + &:hover:after, &:focus:after { transform: scale(1); animation-name: none; diff --git a/packages/react-ui/src/TextInput/styles.module.css b/packages/react-ui/src/TextInput/styles.module.css index d2d1166..1554ea1 100644 --- a/packages/react-ui/src/TextInput/styles.module.css +++ b/packages/react-ui/src/TextInput/styles.module.css @@ -3,27 +3,29 @@ box-sizing: border-box; width: 100%; padding: 10px; - border: 1px solid var(--border-color); + border: 1px solid var(--color--border); appearance: none; border-radius: 0; + background-color: var(--color--surface); background-image: none; transition: border 0.2s var(--material-ease); font-size: var(--font-size-m); resize: none; font-family: inherit; + color: inherit; &::placeholder { - color: var(--placeholder-body-color); + color: var(--color--ink-placeholder); } &:hover { - border-color: var(--darker-border-color); + border-color: var(--color--border-hover); } &:focus { outline: 0; - border-color: var(--accent-color); - box-shadow: 0 0 0 3px var(--semi-transparent-accent-color); + border-color: var(--color--focus--border); + box-shadow: 0 0 0 3px var(--color--focus--outline); } } @@ -33,20 +35,20 @@ } .TextInput--disabled { - color: var(--light-body-color); - border-color: var(--border-color); - background: var(--lighter-bg-color); + color: var(--color--disabled--ink); + border-color: var(--color--border); + background: var(--color--disabled--surface); } .TextInput--error { - border-color: var(--alert-color); + border-color: var(--color--feedback-fail--border); &:hover, &:focus { - border-color: var(--alert-color); + border-color: var(--color--feedback-fail--border); } &:focus { - box-shadow: 0 0 0 3px rgba(var(--alert-color-rgb-components), 0.2); + box-shadow: 0 0 0 3px var(--color--feedback-fail--outline); } } diff --git a/packages/react-ui/src/TextareaInput/styles.module.css b/packages/react-ui/src/TextareaInput/styles.module.css index 268e4a7..1984bcd 100644 --- a/packages/react-ui/src/TextareaInput/styles.module.css +++ b/packages/react-ui/src/TextareaInput/styles.module.css @@ -3,27 +3,29 @@ box-sizing: border-box; width: 100%; padding: 10px; - border: 1px solid var(--border-color); + border: 1px solid var(--color--border); appearance: none; border-radius: 0; + background-color: var(--color--surface); background-image: none; transition: border 0.2s var(--material-ease); font-size: var(--font-size-m); resize: none; font-family: inherit; + color: inherit; &::placeholder { - color: var(--placeholder-body-color); + color: var(--color--ink-placeholder); } &:hover { - border-color: var(--darker-border-color); + border-color: var(--color--border-hover); } &:focus { outline: 0; - border-color: var(--accent-color); - box-shadow: 0 0 0 3px var(--semi-transparent-accent-color); + border-color: var(--color--focus--border); + box-shadow: 0 0 0 3px var(--color--focus--outline); } } @@ -33,20 +35,20 @@ } .TextareaInput--disabled { - color: var(--light-body-color); - border-color: var(--border-color); - background: var(--lighter-bg-color); + color: var(--color--disabled--ink); + border-color: var(--color--border); + background: var(--color--disabled--surface); } .TextareaInput--error { - border-color: var(--alert-color); + border-color: var(--color--feedback-fail--border); &:hover, &:focus { - border-color: var(--alert-color); + border-color: var(--color--feedback-fail--border); } &:focus { - box-shadow: 0 0 0 3px rgba(var(--alert-color-rgb-components), 0.2); + box-shadow: 0 0 0 3px var(--color--feedback-fail--outline); } } diff --git a/packages/react-ui/src/Toolbar/Button/styles.module.css b/packages/react-ui/src/Toolbar/Button/styles.module.css index 2208c4a..4d7c186 100644 --- a/packages/react-ui/src/Toolbar/Button/styles.module.css +++ b/packages/react-ui/src/Toolbar/Button/styles.module.css @@ -3,7 +3,7 @@ cursor: pointer; line-height: inherit; background-color: transparent; - color: var(--base-body-color); + color: var(--color--ink); -webkit-appearance: none; -moz-appearance: none; box-sizing: border-box; @@ -14,13 +14,13 @@ justify-content: center; width: 49px; min-height: 49px; - border-left: 1px solid var(--border-color); - border-right: 1px solid var(--border-color); + border-left: 1px solid var(--color--border); + border-right: 1px solid var(--color--border); } .Button:hover, .Button:focus { - background-color: var(--light-bg-color); + background-color: var(--color--surface-hover); } .Button:first-child { diff --git a/packages/react-ui/src/Toolbar/Toolbar/index.tsx b/packages/react-ui/src/Toolbar/Toolbar/index.tsx index 92c8423..8349ef3 100644 --- a/packages/react-ui/src/Toolbar/Toolbar/index.tsx +++ b/packages/react-ui/src/Toolbar/Toolbar/index.tsx @@ -23,7 +23,7 @@ export type ToolbarProps = { * display: 'flex', * justifyContent: 'center', * alignItems: 'center', - * background: 'var(--light-bg-color)', + * background: 'var(--color--surface-muted)', * height: '150px', * }} * > @@ -54,7 +54,7 @@ export type ToolbarProps = { * display: 'flex', * justifyContent: 'center', * alignItems: 'center', - * background: 'var(--light-bg-color)', + * background: 'var(--color--surface-muted)', * height: '150px', * }} * > @@ -83,7 +83,7 @@ export type ToolbarProps = { * display: 'flex', * justifyContent: 'center', * alignItems: 'center', - * background: 'var(--light-bg-color)', + * background: 'var(--color--surface-muted)', * height: '150px', * }} * > diff --git a/packages/react-ui/src/Toolbar/Toolbar/styles.module.css b/packages/react-ui/src/Toolbar/Toolbar/styles.module.css index c535e4e..ed753ad 100644 --- a/packages/react-ui/src/Toolbar/Toolbar/styles.module.css +++ b/packages/react-ui/src/Toolbar/Toolbar/styles.module.css @@ -1,7 +1,7 @@ .Toolbar { display: flex; - border-bottom: 1px solid var(--border-color); - border-top: 1px solid var(--border-color); + border-bottom: 1px solid var(--color--border); + border-top: 1px solid var(--color--border); align-items: stretch; position: relative; } diff --git a/packages/react-ui/src/Tooltip/TooltipContent/styles.module.css b/packages/react-ui/src/Tooltip/TooltipContent/styles.module.css index 9d41b47..0ee609a 100644 --- a/packages/react-ui/src/Tooltip/TooltipContent/styles.module.css +++ b/packages/react-ui/src/Tooltip/TooltipContent/styles.module.css @@ -1,7 +1,7 @@ .tooltip { padding: 10px 15px; - box-shadow: 0 1px 9px rgba(0, 0, 0, 0.2); - background: white; + box-shadow: 0 1px 9px var(--color--shadow); + background: var(--color--raised--surface); border-radius: 4px; max-width: 400px; word-wrap: break-word; diff --git a/packages/react-ui/src/Tooltip/TooltipDelayGroup/index.tsx b/packages/react-ui/src/Tooltip/TooltipDelayGroup/index.tsx index 9a7a8d7..b79052a 100644 --- a/packages/react-ui/src/Tooltip/TooltipDelayGroup/index.tsx +++ b/packages/react-ui/src/Tooltip/TooltipDelayGroup/index.tsx @@ -87,8 +87,8 @@ export type TooltipDelayGroupProps = { * display: 'flex', * gap: 'var(--spacing-xs)', * padding: 'var(--spacing-s)', - * borderRadius: 'var(--border-radius-m)', - * backgroundColor: 'var(--light-bg-color)' + * borderRadius: '4px', + * backgroundColor: 'var(--color--surface-muted)' * }}> * * diff --git a/packages/react-ui/src/VerticalSplit/index.tsx b/packages/react-ui/src/VerticalSplit/index.tsx index fbdef5c..fbab80a 100644 --- a/packages/react-ui/src/VerticalSplit/index.tsx +++ b/packages/react-ui/src/VerticalSplit/index.tsx @@ -61,7 +61,7 @@ function calculateSizes({ * Main content *
*
- *
+ *
* * * Secondary @@ -108,7 +108,7 @@ function calculateSizes({ * Sidebar *
*
- *
+ *
* * * Primary @@ -168,7 +168,7 @@ function calculateSizes({ * display: 'flex', * flexDirection: 'column', * height: '100%', - * borderLeft: '1px solid var(--border-color)', + * borderLeft: '1px solid var(--color--border)', * }} * > * @@ -233,7 +233,7 @@ function calculateSizes({ * display: 'flex', * flexDirection: 'column', * height: '100%', - * borderLeft: '1px solid var(--border-color)', + * borderLeft: '1px solid var(--color--border)', * }} * > * diff --git a/packages/react-ui/src/VerticalSplit/styles.module.css b/packages/react-ui/src/VerticalSplit/styles.module.css index 3595512..17d4fb3 100644 --- a/packages/react-ui/src/VerticalSplit/styles.module.css +++ b/packages/react-ui/src/VerticalSplit/styles.module.css @@ -1,6 +1,6 @@ .VerticalSplitPane__expand { transition: all 0.3s cubic-bezier(0.55, 0, 0.1, 1); - background: white; + background: var(--color--surface); cursor: pointer; position: absolute; top: 0; @@ -10,12 +10,12 @@ } .VerticalSplitPane__expand:hover { - background: var(--light-bg-color); + background: var(--color--surface-hover); animation: VerticalSplitPane__expand 0.6s cubic-bezier(0.55, 0, 0.1, 1); } .VerticalSplitPane__expand.VerticalSplitPane__expand { - border-right: 1px solid var(--border-color); + border-right: 1px solid var(--color--border); transform-origin: left; } @@ -23,7 +23,7 @@ } .VerticalSplitPane__expand.VerticalSplitPane__expand--right { - border-left: 1px solid var(--border-color); + border-left: 1px solid var(--color--border); transform-origin: right; } @@ -45,11 +45,7 @@ left: 0; right: 0; bottom: 0; - background: linear-gradient( - to bottom, - rgba(48, 48, 47, 0.5), - rgba(48, 48, 47, 0.3) - ); + background: var(--color--overlay--surface); z-index: 12; height: 100%; overflow: hidden; @@ -76,8 +72,8 @@ position: absolute; top: 0; bottom: 0; - background: white; - box-shadow: 0 0 15px rgba(0, 0, 0, 0.4); + background: var(--color--surface); + box-shadow: 0 0 15px var(--color--shadow-strong); } .VerticalSplitPaneOverlay__secondary--left { diff --git a/packages/react-ui/src/generateStyleFromCtx/index.ts b/packages/react-ui/src/generateStyleFromCtx/index.ts index c9991ed..6f054d1 100644 --- a/packages/react-ui/src/generateStyleFromCtx/index.ts +++ b/packages/react-ui/src/generateStyleFromCtx/index.ts @@ -8,6 +8,151 @@ function camelToDash(str: string) { return str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`); } +/** + * Maps camelCase semantic token keys to their CSS custom property names + * (without the `--` prefix). The CSS names preserve the double-dash + * namespace separators used by the host application. + */ +const semanticTokenCssNames: Record = { + /* --- Standalone --- */ + colorSurface: 'color--surface', + colorSurfaceHover: 'color--surface-hover', + colorSurfaceMuted: 'color--surface-muted', + colorInk: 'color--ink', + colorInkSubtle: 'color--ink-subtle', + colorInkHover: 'color--ink-hover', + colorInkMuted: 'color--ink-muted', + colorInkPlaceholder: 'color--ink-placeholder', + colorInkPrimary: 'color--ink-primary', + colorInkAccent: 'color--ink-accent', + colorInkDisabled: 'color--ink-disabled', + colorBorder: 'color--border', + colorBorderHover: 'color--border-hover', + + /* --- Context: raised --- */ + colorRaisedSurface: 'color--raised--surface', + colorRaisedSurfaceHover: 'color--raised--surface-hover', + colorRaisedSurfaceActive: 'color--raised--surface-active', + + /* --- Context: primary --- */ + colorPrimarySurface: 'color--primary--surface', + colorPrimarySurfaceHover: 'color--primary--surface-hover', + colorPrimarySurfaceActive: 'color--primary--surface-active', + colorPrimarySurfaceMuted: 'color--primary--surface-muted', + colorPrimaryInk: 'color--primary--ink', + colorPrimaryBorder: 'color--primary--border', + + /* --- Context: tinted --- */ + colorTintedSurface: 'color--tinted--surface', + colorTintedSurfaceHover: 'color--tinted--surface-hover', + colorTintedSurfaceActive: 'color--tinted--surface-active', + colorTintedInk: 'color--tinted--ink', + + /* --- Context: accent --- */ + colorAccentSurface: 'color--accent--surface', + colorAccentInk: 'color--accent--ink', + + /* --- Context: selected --- */ + colorSelectedSurface: 'color--selected--surface', + colorSelectedInk: 'color--selected--ink', + colorSelectedBorder: 'color--selected--border', + + /* --- Context: disabled --- */ + colorDisabledSurface: 'color--disabled--surface', + colorDisabledInk: 'color--disabled--ink', + + /* --- Context: danger --- */ + colorDangerSurface: 'color--danger--surface', + colorDangerInk: 'color--danger--ink', + + /* --- Context: enterprise --- */ + colorEnterpriseSurface: 'color--enterprise--surface', + + /* --- Context: focus --- */ + colorFocusBorder: 'color--focus--border', + colorFocusOutline: 'color--focus--outline', + + /* --- Feedback --- */ + colorFeedbackFailInk: 'color--feedback-fail--ink', + colorFeedbackFailBorder: 'color--feedback-fail--border', + colorFeedbackFailOutline: 'color--feedback-fail--outline', + colorFeedbackWarningInk: 'color--feedback-warning--ink', + colorFeedbackWarningSurface: 'color--feedback-warning--surface', + colorFeedbackSuccessInk: 'color--feedback-success--ink', + colorFeedbackWarningBorder: 'color--feedback-warning--border', + colorFeedbackSuccessBorder: 'color--feedback-success--border', + + /* --- Context: highlight --- */ + colorHighlightSurface: 'color--highlight--surface', + + /* --- Diffs --- */ + colorDiffAddedSurface: 'color--diff-added--surface', + colorDiffRemovedSurface: 'color--diff-removed--surface', + colorDiffChangedSurface: 'color--diff-changed--surface', + colorDiffAddedSurfaceSubtle: 'color--diff-added--surface-subtle', + colorDiffRemovedSurfaceSubtle: 'color--diff-removed--surface-subtle', + colorDiffChangedSurfaceSubtle: 'color--diff-changed--surface-subtle', + colorDiffAddedOutlineSubtle: 'color--diff-added--outline-subtle', + colorDiffRemovedOutlineSubtle: 'color--diff-removed--outline-subtle', + colorDiffChangedOutlineSubtle: 'color--diff-changed--outline-subtle', + colorDiffChangedBorder: 'color--diff-changed--border', + colorDiffChangedBorderNegative: 'color--diff-changed--border-negative', + + /* --- Status --- */ + colorStatusDraftInk: 'color--status-draft--ink', + colorStatusOutdatedInk: 'color--status-outdated--ink', + colorStatusPublishedInk: 'color--status-published--ink', + + /* --- Backdrop --- */ + colorBackdropSurface: 'color--backdrop--surface', + colorBackdropInk: 'color--backdrop--ink', + + /* --- Overlay --- */ + colorOverlaySurface: 'color--overlay--surface', + colorOverlaySurfaceSubtle: 'color--overlay--surface-subtle', + colorOverlayInk: 'color--overlay--ink', + + /* --- Stacked --- */ + colorStackedSurfaceBase: 'color--stacked--surface-base', + colorStackedSurface: 'color--stacked--surface', + colorStackedSurfaceRaised: 'color--stacked--surface-raised', + colorStackedInk: 'color--stacked--ink', + colorStackedInkSubtle: 'color--stacked--ink-subtle', + colorStackedBorder: 'color--stacked--border', + colorStackedSurfaceHover: 'color--stacked--surface-hover', + colorStackedSurfaceTranslucent: 'color--stacked--surface-translucent', + colorStackedSurfaceButton: 'color--stacked--surface-button', + colorStackedSurfaceButtonActive: 'color--stacked--surface-button-active', + + /* --- Progress --- */ + colorProgressTrack: 'color--progress--track', + colorProgressFill: 'color--progress--fill', + colorProgressFillHover: 'color--progress--fill-hover', + + /* --- Tooltip --- */ + colorTooltipSurface: 'color--tooltip--surface', + colorTooltipSurfaceHover: 'color--tooltip--surface-hover', + colorTooltipInk: 'color--tooltip--ink', + colorTooltipInkSubtle: 'color--tooltip--ink-subtle', + + /* --- Code --- */ + colorCodeSurface: 'color--code--surface', + colorCodeInk: 'color--code--ink', + + /* --- Shadows --- */ + colorShadowSubtle: 'color--shadow-subtle', + colorShadow: 'color--shadow', + colorShadowStrong: 'color--shadow-strong', + + /* --- Scrollbar --- */ + colorScrollbar: 'color--scrollbar', + + /* --- Shadow composites --- */ + shadowElevated: 'shadow--elevated', + shadowFloat: 'shadow--float', + shadowAmbient: 'shadow--ambient', +}; + export function generateStyleFromCtx( ctx: BaseCtx, noBodyPadding = false, @@ -25,5 +170,13 @@ export function generateStyleFromCtx( ], ]), ), + ...(ctx.semanticColorTokensTheme + ? Object.fromEntries( + Object.entries(ctx.semanticColorTokensTheme).map(([k, v]) => [ + `--${semanticTokenCssNames[k] ?? camelToDash(k)}`, + v, + ]), + ) + : undefined), }; } diff --git a/packages/react-ui/src/icons.tsx b/packages/react-ui/src/icons.tsx index 9a7bd14..7536ca8 100644 --- a/packages/react-ui/src/icons.tsx +++ b/packages/react-ui/src/icons.tsx @@ -16,6 +16,7 @@ export function BackIcon({ return ( **Host contract.** `ctx.theme` is light-only; `ctx.semanticColorTokensTheme` +> is theme-aware. This split keeps existing plugins safe by construction +> — no v2 plugin can stumble into dark mode — while letting v3 plugins +> opt into the active theme by reading the new field. + +### Action required + +If your plugin uses `datocms-react-ui`, see +[its upgrade notes](../react-ui/UPGRADING.md) — most of the visible +changes are there, including the dark-mode audit checklist. + +If your plugin reads `ctx.theme` directly, you can keep doing so for now. +Migrating to `ctx.semanticColorTokensTheme` will let your plugin follow +the user's active theme (including dark mode). diff --git a/packages/sdk/manifest.json b/packages/sdk/manifest.json index a1a1d62..357d8bc 100644 --- a/packages/sdk/manifest.json +++ b/packages/sdk/manifest.json @@ -3109,13 +3109,34 @@ }, "theme": { "comment": { - "markdownText": "An object containing the theme colors for the current DatoCMS project." + "markdownText": "An object containing the theme colors for the current DatoCMS project.", + "deprecatedMarkdownText": "Use `semanticColorTokensTheme` instead. This property is kept\nfor backward compatibility with third-party plugins." }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 87 + "lineNumber": 92 }, "type": "Theme" + }, + "semanticColorTokensTheme": { + "comment": { + "markdownText": "Semantic color tokens for the current DatoCMS project, pre-computed by\nthe host. Only available on DatoCMS hosts that support the new token\nsystem." + }, + "location": { + "filePath": "src/ctx/base.ts", + "lineNumber": 99 + }, + "type": "SemanticColorTokensTheme" + }, + "colorScheme": { + "comment": { + "markdownText": "The appearance color scheme the host CMS is currently using. Resolved —\n`'system'` is already expanded to `'light'` or `'dark'` by the host.\n\nThe SDK runtime reflects this onto `document.documentElement` as\n`data-theme=\"light\"` / `data-theme=\"dark\"` so plugin CSS can branch\nwith `[data-theme=\"dark\"] { … }` selectors. For non-CSS decisions\n(choosing a logo asset, a syntax-highlighting preset, …) branch on\n`ctx.colorScheme` directly." + }, + "location": { + "filePath": "src/ctx/base.ts", + "lineNumber": 111 + }, + "type": "'light' | 'dark'" } } }, @@ -3131,7 +3152,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 97 + "lineNumber": 121 }, "type": "Partial>" }, @@ -3141,7 +3162,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 104 + "lineNumber": 128 }, "type": "Partial>" }, @@ -3151,7 +3172,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 111 + "lineNumber": 135 }, "type": "Partial>" }, @@ -3161,7 +3182,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 118 + "lineNumber": 142 }, "type": "Partial>" }, @@ -3171,7 +3192,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 125 + "lineNumber": 149 }, "type": "Partial>" } @@ -3192,7 +3213,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 168 + "lineNumber": 353 }, "type": "(itemTypeId: string) => Promise" }, @@ -3203,7 +3224,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 187 + "lineNumber": 372 }, "type": "(itemTypeId: string) => Promise" }, @@ -3214,7 +3235,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 204 + "lineNumber": 389 }, "type": "() => Promise" }, @@ -3225,7 +3246,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 217 + "lineNumber": 402 }, "type": "() => Promise" }, @@ -3236,7 +3257,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 230 + "lineNumber": 415 }, "type": "() => Promise" } @@ -3255,7 +3276,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 252 + "lineNumber": 437 }, "type": "(params: Record) => Promise" }, @@ -3266,7 +3287,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 303 + "lineNumber": 488 }, "type": "(\n fieldId: string,\n changes: FieldAppearanceChange[],\n ) => Promise" } @@ -3285,7 +3306,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 408 + "lineNumber": 593 }, "type": "(message: string) => Promise" }, @@ -3296,7 +3317,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 423 + "lineNumber": 608 }, "type": "(message: string) => Promise" }, @@ -3307,7 +3328,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 447 + "lineNumber": 632 }, "type": "(\n toast: Toast,\n ) => Promise" } @@ -3326,7 +3347,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 333 + "lineNumber": 518 }, "type": "(itemTypeId: string) => Promise" }, @@ -3337,7 +3358,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 354 + "lineNumber": 539 }, "type": "{\n (\n itemTypeId: string,\n options: { multiple: true; initialLocationQuery?: ItemListLocationQuery },\n ): Promise;\n (\n itemTypeId: string,\n options?: {\n multiple: false;\n initialLocationQuery?: ItemListLocationQuery;\n },\n ): Promise;\n }" }, @@ -3348,7 +3369,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 386 + "lineNumber": 571 }, "type": "(itemId: string) => Promise" } @@ -3367,7 +3388,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 474 + "lineNumber": 659 }, "type": "{\n (options: { multiple: true }): Promise;\n (options?: { multiple: false }): Promise;\n }" }, @@ -3378,7 +3399,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 502 + "lineNumber": 687 }, "type": "(\n uploadId: string,\n ) => Promise<(Upload & { deleted?: true }) | null>" }, @@ -3389,7 +3410,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 530 + "lineNumber": 715 }, "type": "(\n /** The \"single asset\" field structure */\n fileFieldValue: FileFieldValue,\n /** Shows metadata information for a specific locale */\n locale?: string,\n ) => Promise" } @@ -3408,7 +3429,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 561 + "lineNumber": 746 }, "type": "(modal: Modal) => Promise" }, @@ -3419,7 +3440,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 598 + "lineNumber": 783 }, "type": "(options: ConfirmOptions) => Promise" } @@ -3438,7 +3459,7 @@ }, "location": { "filePath": "src/ctx/base.ts", - "lineNumber": 612 + "lineNumber": 797 }, "type": "(path: string) => Promise" } diff --git a/packages/sdk/package-lock.json b/packages/sdk/package-lock.json index fa5c7e8..ffeb94e 100644 --- a/packages/sdk/package-lock.json +++ b/packages/sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "datocms-plugin-sdk", - "version": "2.1.1", + "version": "3.0.1-alpha.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "datocms-plugin-sdk", - "version": "2.1.1", + "version": "3.0.1-alpha.0", "license": "MIT", "dependencies": { "@datocms/cma-client": "*", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 8e1958a..cce1879 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "datocms-plugin-sdk", - "version": "2.1.1", + "version": "3.0.1-alpha.0", "description": "DatoCMS Plugin SDK", "keywords": [ "datocms", diff --git a/packages/sdk/src/connect.ts b/packages/sdk/src/connect.ts index 4e1ccd3..f669446 100644 --- a/packages/sdk/src/connect.ts +++ b/packages/sdk/src/connect.ts @@ -136,6 +136,15 @@ export type FullConnectParameters = AssetSourcesHook & UploadSidebarsHook & ValidateManualFieldExtensionParametersHook; +function applyColorScheme(properties: unknown): void { + if (typeof document === 'undefined') return; + const next = (properties as { colorScheme?: 'light' | 'dark' } | null) + ?.colorScheme; + if (next !== 'light' && next !== 'dark') return; + if (document.documentElement.dataset.theme === next) return; + document.documentElement.dataset.theme = next; +} + export async function connect( rawConfiguration: Partial = {}, ): Promise { @@ -185,6 +194,7 @@ export async function connect( ), ), onChange(newSettings: unknown) { + applyColorScheme(newSettings); if (onChangeListener) { onChangeListener(newSettings); } @@ -212,6 +222,7 @@ export async function connect( const methods = await penpalConnection.promise; const initialProperties = await methods.getSettings(); + applyColorScheme(initialProperties); if (initialProperties.mode === 'onBoot') { let currentProperties = initialProperties; diff --git a/packages/sdk/src/ctx/base.ts b/packages/sdk/src/ctx/base.ts index 6053311..a6b877e 100644 --- a/packages/sdk/src/ctx/base.ts +++ b/packages/sdk/src/ctx/base.ts @@ -83,8 +83,32 @@ type ProjectProperties = { locale: string; }; - /** An object containing the theme colors for the current DatoCMS project */ + /** + * An object containing the theme colors for the current DatoCMS project + * + * @deprecated Use `semanticColorTokensTheme` instead. This property is kept + * for backward compatibility with third-party plugins. + */ theme: Theme; + + /** + * Semantic color tokens for the current DatoCMS project, pre-computed by + * the host. Only available on DatoCMS hosts that support the new token + * system. + */ + semanticColorTokensTheme: SemanticColorTokensTheme; + + /** + * The appearance color scheme the host CMS is currently using. Resolved — + * `'system'` is already expanded to `'light'` or `'dark'` by the host. + * + * The SDK runtime reflects this onto `document.documentElement` as + * `data-theme="light"` / `data-theme="dark"` so plugin CSS can branch + * with `[data-theme="dark"] { … }` selectors. For non-CSS decisions + * (choosing a logo asset, a syntax-highlighting preset, …) branch on + * `ctx.colorScheme` directly. + */ + colorScheme: 'light' | 'dark'; }; /** @@ -125,7 +149,12 @@ type EntityReposProperties = { ssoUsers: Partial>; }; -/** An object containing the theme colors for the current DatoCMS project */ +/** + * An object containing the theme colors for the current DatoCMS project + * + * @deprecated Use `SemanticColorTokensTheme` instead. This type is kept for + * backward compatibility with third-party plugins. + */ export type Theme = { primaryColor: string; accentColor: string; @@ -134,6 +163,162 @@ export type Theme = { darkColor: string; }; +/** + * Known semantic color tokens provided by the DatoCMS host. + * All properties are optional — the host may not send all of them. + * + * camelCase keys are converted to kebab-case CSS custom properties via + * `camelToDash`. For example, `colorRaisedSurface` becomes + * `--color-raised-surface`. + */ +type KnownSemanticColorTokens = { + /* --- Standalone --- */ + colorSurface?: string; + colorSurfaceHover?: string; + colorSurfaceMuted?: string; + colorInk?: string; + colorInkSubtle?: string; + colorInkHover?: string; + colorInkMuted?: string; + colorInkPlaceholder?: string; + colorInkPrimary?: string; + colorInkAccent?: string; + colorInkDisabled?: string; + colorBorder?: string; + colorBorderHover?: string; + + /* --- Context: raised --- */ + colorRaisedSurface?: string; + colorRaisedSurfaceHover?: string; + colorRaisedSurfaceActive?: string; + + /* --- Context: primary --- */ + colorPrimarySurface?: string; + colorPrimarySurfaceHover?: string; + colorPrimarySurfaceActive?: string; + colorPrimarySurfaceMuted?: string; + colorPrimaryInk?: string; + colorPrimaryBorder?: string; + + /* --- Context: tinted --- */ + colorTintedSurface?: string; + colorTintedSurfaceHover?: string; + colorTintedSurfaceActive?: string; + colorTintedInk?: string; + + /* --- Context: accent --- */ + colorAccentSurface?: string; + colorAccentInk?: string; + + /* --- Context: selected --- */ + colorSelectedSurface?: string; + colorSelectedInk?: string; + colorSelectedBorder?: string; + + /* --- Context: disabled --- */ + colorDisabledSurface?: string; + colorDisabledInk?: string; + + /* --- Context: danger --- */ + colorDangerSurface?: string; + colorDangerInk?: string; + + /* --- Context: enterprise --- */ + colorEnterpriseSurface?: string; + + /* --- Context: focus --- */ + colorFocusBorder?: string; + colorFocusOutline?: string; + + /* --- Feedback --- */ + colorFeedbackFailInk?: string; + colorFeedbackFailBorder?: string; + colorFeedbackFailOutline?: string; + colorFeedbackWarningInk?: string; + colorFeedbackWarningSurface?: string; + colorFeedbackSuccessInk?: string; + colorFeedbackWarningBorder?: string; + colorFeedbackSuccessBorder?: string; + + /* --- Context: highlight --- */ + colorHighlightSurface?: string; + + /* --- Diffs --- */ + colorDiffAddedSurface?: string; + colorDiffRemovedSurface?: string; + colorDiffChangedSurface?: string; + colorDiffAddedSurfaceSubtle?: string; + colorDiffRemovedSurfaceSubtle?: string; + colorDiffChangedSurfaceSubtle?: string; + colorDiffAddedOutlineSubtle?: string; + colorDiffRemovedOutlineSubtle?: string; + colorDiffChangedOutlineSubtle?: string; + colorDiffChangedBorder?: string; + colorDiffChangedBorderNegative?: string; + + /* --- Status --- */ + colorStatusDraftInk?: string; + colorStatusOutdatedInk?: string; + colorStatusPublishedInk?: string; + + /* --- Backdrop --- */ + colorBackdropSurface?: string; + colorBackdropInk?: string; + + /* --- Overlay --- */ + colorOverlaySurface?: string; + colorOverlaySurfaceSubtle?: string; + colorOverlayInk?: string; + + /* --- Stacked --- */ + colorStackedSurfaceBase?: string; + colorStackedSurface?: string; + colorStackedSurfaceRaised?: string; + colorStackedInk?: string; + colorStackedInkSubtle?: string; + colorStackedBorder?: string; + colorStackedSurfaceHover?: string; + colorStackedSurfaceTranslucent?: string; + colorStackedSurfaceButton?: string; + colorStackedSurfaceButtonActive?: string; + + /* --- Progress --- */ + colorProgressTrack?: string; + colorProgressFill?: string; + colorProgressFillHover?: string; + + /* --- Tooltip --- */ + colorTooltipSurface?: string; + colorTooltipSurfaceHover?: string; + colorTooltipInk?: string; + colorTooltipInkSubtle?: string; + + /* --- Code --- */ + colorCodeSurface?: string; + colorCodeInk?: string; + + /* --- Shadows --- */ + colorShadowSubtle?: string; + colorShadow?: string; + colorShadowStrong?: string; + + /* --- Scrollbar --- */ + colorScrollbar?: string; + + /* --- Shadow composites --- */ + shadowElevated?: string; + shadowFloat?: string; + shadowAmbient?: string; +}; + +/** + * Semantic color tokens for the current DatoCMS project, pre-computed by the + * host. Known tokens get autocomplete; unknown tokens are accepted via the + * index signature for forward compatibility. + */ +export type SemanticColorTokensTheme = KnownSemanticColorTokens & + Record; + export type BaseMethods = LoadDataMethods & UpdatePluginParametersMethods & ToastMethods & diff --git a/packages/sdk/src/manifest.ts b/packages/sdk/src/manifest.ts index 66b62b1..c5f4533 100644 --- a/packages/sdk/src/manifest.ts +++ b/packages/sdk/src/manifest.ts @@ -3340,13 +3340,37 @@ export const manifest: Manifest = { comment: { markdownText: 'An object containing the theme colors for the current DatoCMS project.', + deprecatedMarkdownText: + 'Use `semanticColorTokensTheme` instead. This property is kept\nfor backward compatibility with third-party plugins.', }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 87, + lineNumber: 92, }, type: 'Theme', }, + semanticColorTokensTheme: { + comment: { + markdownText: + 'Semantic color tokens for the current DatoCMS project, pre-computed by\nthe host. Only available on DatoCMS hosts that support the new token\nsystem.', + }, + location: { + filePath: 'src/ctx/base.ts', + lineNumber: 99, + }, + type: 'SemanticColorTokensTheme', + }, + colorScheme: { + comment: { + markdownText: + 'The appearance color scheme the host CMS is currently using. Resolved —\n`\'system\'` is already expanded to `\'light\'` or `\'dark\'` by the host.\n\nThe SDK runtime reflects this onto `document.documentElement` as\n`data-theme="light"` / `data-theme="dark"` so plugin CSS can branch\nwith `[data-theme="dark"] { … }` selectors. For non-CSS decisions\n(choosing a logo asset, a syntax-highlighting preset, …) branch on\n`ctx.colorScheme` directly.', + }, + location: { + filePath: 'src/ctx/base.ts', + lineNumber: 111, + }, + type: "'light' | 'dark'", + }, }, }, { @@ -3363,7 +3387,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 97, + lineNumber: 121, }, type: 'Partial>', }, @@ -3374,7 +3398,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 104, + lineNumber: 128, }, type: 'Partial>', }, @@ -3385,7 +3409,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 111, + lineNumber: 135, }, type: 'Partial>', }, @@ -3396,7 +3420,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 118, + lineNumber: 142, }, type: 'Partial>', }, @@ -3407,7 +3431,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 125, + lineNumber: 149, }, type: 'Partial>', }, @@ -3431,7 +3455,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 168, + lineNumber: 353, }, type: '(itemTypeId: string) => Promise', }, @@ -3444,7 +3468,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 187, + lineNumber: 372, }, type: '(itemTypeId: string) => Promise', }, @@ -3457,7 +3481,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 204, + lineNumber: 389, }, type: '() => Promise', }, @@ -3470,7 +3494,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 217, + lineNumber: 402, }, type: '() => Promise', }, @@ -3483,7 +3507,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 230, + lineNumber: 415, }, type: '() => Promise', }, @@ -3505,7 +3529,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 252, + lineNumber: 437, }, type: '(params: Record) => Promise', }, @@ -3518,7 +3542,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 303, + lineNumber: 488, }, type: '(\n fieldId: string,\n changes: FieldAppearanceChange[],\n ) => Promise', }, @@ -3540,7 +3564,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 408, + lineNumber: 593, }, type: '(message: string) => Promise', }, @@ -3553,7 +3577,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 423, + lineNumber: 608, }, type: '(message: string) => Promise', }, @@ -3566,7 +3590,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 447, + lineNumber: 632, }, type: '(\n toast: Toast,\n ) => Promise', }, @@ -3588,7 +3612,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 333, + lineNumber: 518, }, type: '(itemTypeId: string) => Promise', }, @@ -3601,7 +3625,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 354, + lineNumber: 539, }, type: '{\n (\n itemTypeId: string,\n options: { multiple: true; initialLocationQuery?: ItemListLocationQuery },\n ): Promise;\n (\n itemTypeId: string,\n options?: {\n multiple: false;\n initialLocationQuery?: ItemListLocationQuery;\n },\n ): Promise;\n }', }, @@ -3614,7 +3638,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 386, + lineNumber: 571, }, type: '(itemId: string) => Promise', }, @@ -3636,7 +3660,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 474, + lineNumber: 659, }, type: '{\n (options: { multiple: true }): Promise;\n (options?: { multiple: false }): Promise;\n }', }, @@ -3649,7 +3673,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 502, + lineNumber: 687, }, type: '(\n uploadId: string,\n ) => Promise<(Upload & { deleted?: true }) | null>', }, @@ -3662,7 +3686,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 530, + lineNumber: 715, }, type: '(\n /** The "single asset" field structure */\n fileFieldValue: FileFieldValue,\n /** Shows metadata information for a specific locale */\n locale?: string,\n ) => Promise', }, @@ -3684,7 +3708,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 561, + lineNumber: 746, }, type: '(modal: Modal) => Promise', }, @@ -3697,7 +3721,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 598, + lineNumber: 783, }, type: '(options: ConfirmOptions) => Promise', }, @@ -3718,7 +3742,7 @@ export const manifest: Manifest = { }, location: { filePath: 'src/ctx/base.ts', - lineNumber: 612, + lineNumber: 797, }, type: '(path: string) => Promise', },