Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"packages": [
"packages/*"
],
"version": "2.1.4"
"version": "3.0.0-alpha.0"
}
76 changes: 76 additions & 0 deletions packages/react-ui/UPGRADING.md
Original file line number Diff line number Diff line change
@@ -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 `<Canvas>`. 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 `<Canvas>` (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.
89 changes: 86 additions & 3 deletions packages/react-ui/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -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<string, string> 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)');
});
});
6 changes: 3 additions & 3 deletions packages/react-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/react-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "datocms-react-ui",
"version": "2.1.4",
"version": "3.0.0-alpha.0",
"description": "React components to use inside DatoCMS plugins",
"keywords": [
"datocms",
Expand Down Expand Up @@ -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.0-alpha.0",
"react-intersection-observer": "^8.31.0",
"react-select": "^5.2.1",
"scroll-into-view-if-needed": "^2.2.20"
Expand Down
38 changes: 19 additions & 19 deletions packages/react-ui/src/Button/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -128,7 +128,7 @@
line-height: 0.6;

svg {
fill: var(--accent-color);
fill: var(--color--ink-accent);
}
}

Expand Down
32 changes: 15 additions & 17 deletions packages/react-ui/src/ButtonGroup/Button/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,29 @@
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;
box-sizing: border-box;
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 {
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-ui/src/ButtonGroup/Group/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.Group {
display: flex;
align-items: stretch;
background-color: white;
background-color: var(--color--surface);
overflow: hidden;
}
Loading