Add/Edit Color Settings + Palette to color tab#845
Open
bph wants to merge 23 commits into
Open
Conversation
Adds an "Edit Theme Settings" entry to the CBT sidebar (cog icon, between Create Theme Variation and Edit Theme Metadata) that opens a modal with a single Color tab containing two panels: - Color Settings: toggles for default/custom presets and link color. - Palette: add / edit name / edit slug / pick color / remove rows. Modal owns the working state, seeded from `getCurrentTheme().theme_json` and reseeded after a successful save via `invalidateResolution`. Update button is disabled when nothing has changed and shows a count of pending field changes when dirty. Save calls `POST /create-block-theme/v1/theme-settings` (landed in #843) and surfaces success/error notices as snackbars. Modal stays open on success so the user can keep editing. Foundation only — Gradients and Duotone panels, and the Dimensions / Typography / Shadows / Templates tabs, land in follow-up PRs. Refs #838
Adds a header row above the palette ItemGroup with "Name" and "Slug" labels aligned to the underlying input columns. Spacer cells match the swatch button (left) and remove button (right) widths so columns line up. Refs #838
Reads the user-origin Global Styles entity via __experimentalGetCurrentGlobalStylesId and surfaces the warning only when the saved record or its in-editor edits contain non-empty top-level settings/styles slices. Lists which slices are customized (color, typography, spacing, ...) so the user knows what would conflict. When the theme has no user-level customizations the warning is hidden entirely — there is nothing to conflict with. Refs #838
Wraps the comma-separated section list in a <strong> tag so the user's eye lands on what's customized before reading the rest of the sentence. Refs #838
Replaces BaseControl.VisualLabel with plain styled spans so the Name and Slug column headers have consistent, predictable padding. Header labels are inset 12px to line up with the visible text inside the TextControl inputs below. Refs #838
The outer wrapper padding already matches the row Item's internal padding, so the inner padding-left on labels was overshooting by ~12px and pushing Name / Slug headers visibly right of the input text. Removing it lands the labels closer to the input text edge. Refs #838
Switches the Color Settings panel from a single vertical stack of two toggle groups to a CSS grid that places "Default presets" and "Custom presets" side by side on modals wider than the WordPress $break-small breakpoint (600px), collapsing to a single column below it. Adds a short help string under each toggle so users understand the effect without leaving the modal. The combination roughly halves the panel's height on a typical modal width, pushing the Palette panel above the fold. Refs #838
CSS grid defaults to align-items: stretch which let the inner VStacks vertically center their contents, producing visibly offset group headers across the two columns. Forcing align-items: start lines up Default Presets and Custom Presets headers (and their first toggles). Refs #838
The previously-imported `edit` icon from @wordpress/icons was not rendering visibly in the sidebar menu, leaving the Edit Theme Metadata row icon-less and visually misaligned from the surrounding entries. Switching to the `pencil` icon — the one the Site Editor uses elsewhere for "edit metadata"-style actions — restores the icon and lines all sidebar entries up. Refs #838
The previous title ("Color Settings") was redundant with the surrounding
"Color" tab and didn't describe the panel's actual content. The new
title names exactly what the toggles control.
Refs #838
The Palette PanelBody header already names the section, so the duplicate "Color presets" sub-label was noise. Replaces it with a right-aligned "+ Add a color" tertiary button that combines the labeling and the add affordance into one element. Refs #838
Adds a Site-Editor-style summary row at the top of the Color tab that shows up to five overlapping swatches from the active palette plus a right-aligned "Edit palette" button (with chevron). Clicking the button expands the Palette accordion, which now defaults to closed and is driven by controlled \`opened\` / \`onToggle\` state. The Default and custom presets accordion stays default-open as before. Refs #838
Previously, clicking "Edit palette" while the accordion was already open did nothing (setState was a no-op so React skipped the render that would have caused a scroll). Now the click handler always calls scrollIntoView on a wrapper ref around the Palette PanelBody, regardless of whether the accordion was open or closed. Refs #838
Drops the border / rounded corners around the palette summary row and left-aligns the "Edit palette" button so it sits right next to the swatch stack instead of being pushed to the far right. Reduces visual noise on a modal that already has enough horizontal rules. Refs #838
Without a palette, the summary card is hidden and the Palette accordion would otherwise default to closed — leaving the "Add a color" button hidden behind it. Force the accordion open whenever \`palette.length\` is zero so the empty-state add affordance is always visible. Refs #838
Clicking "Add a color" appends a row to the bottom of the palette list, which on a tall list ends up below the modal's visible area. Add an effect that compares the row count against the previous render and scrolls the new last row into view when it grows. Edits and removes do not trigger a scroll. Refs #838
When the palette was empty the accordion was force-open so the "Add a color" button stayed visible. Adding a color flipped palette.length from 0 to 1, the force-open condition stopped applying, and the accordion collapsed — hiding the row that had just been added. Track palette.length transitions and explicitly set isPaletteOpen=true on the 0→1 jump so the accordion stays expanded after the first add. Refs #838
Three minor visual tweaks aligned with the broader Edit Theme Settings modal direction: - Palette: bump remove-button iconSize to 20px so the minus icon matches the visual weight of the surrounding swatch button. - Palette section header: nudge the right-edge action -8px so "+ Add a color" aligns with the PanelBody chevron in the accordion header directly above. - Modal: drop the top border on the first PanelBody when it sits below a non-panel sibling (our PaletteSummary card), so the boundary doesn't double-up with the summary card's bottom edge. Refs #838
The endpoint returns the merged theme.json on success. Use that
response directly to update colorSettings, palette, AND snapshot
instead of relying on invalidateResolution('getCurrentTheme') to
trigger a re-fetch.
Two problems with the previous approach:
- `getCurrentTheme` is entity-record-backed; invalidating it doesn't
reliably re-fetch immediately, so the modal's reseed effect could
fire late (or not at all in the same interaction), leaving the
Update button showing pending changes even after a successful save.
- Other views (e.g. View theme.json) read through the same cached
path and were similarly stuck on pre-save data, making it look as
if booleans (Link color, etc.) hadn't persisted when in fact they
had on disk.
Driving state directly from the response makes the dirty count drop
to 0 immediately on save, and the invalidate call is kept so other
callers eventually refresh.
Refs #838
The modal previously rendered the cached \`getCurrentTheme\` resolution without re-resolving it, so writes made by the Edit Theme Settings flow since the last fetch did not show up until the page was reloaded. Invalidating the resolution on mount triggers a fresh fetch so the modal always shows the on-disk theme.json. Refs #838
The /theme-settings endpoint wraps the merged theme.json in
{ status, theme_json: <merged> }, but the client was reading
response.settings.color directly — which is always undefined. As a
result every save snapped the local state back to
COLOR_SETTINGS_DEFAULTS and cleared the palette in the UI (the on-disk
theme.json was correct; only the modal's in-memory state was wrong).
Most visible symptom: toggling Default gradients or Default duotone
off and clicking Update made the toggle pop right back to on. Link
color set to true behaved the same way (snapped back to false).
Refs #838
invalidateResolution alone wasn't enough: useSelect returns the cached
theme synchronously on mount and the modal renders that stale data
before the refetch lands. Replace useSelect with a direct
apiFetch('/wp/v2/themes?status=active') in a mount effect so the modal
always shows the on-disk theme.json.
Keep invalidateResolution so other subscribers eventually see fresh
data too.
Refs #838
Contributor
Author
|
Here is the latest video Screen.Recording.2026-05-22.at.17.13.20.mov |
Contributor
Author
|
Claude and Codex did a first pass at reviewing this PR. |
Bugs / correctness: - addColor: derive next index from the max existing `new-color-N` suffix instead of `value.length + 1`. Add → Remove → Add no longer produces duplicate slugs (P1). - Palette rows: assign a stable client-only `__cbtRowId` at creation time and use it as the React key, so removing a middle row no longer leaks Dropdown/ColorPicker state to subsequent rows. The ID is stripped from the payload before posting. - Update button: disable when any palette row has an empty slug; show a tooltip explaining why. Prevents the modal from POSTing an entry with `slug: ""`. Concurrent edits / lost updates (Codex P2, reviewer #12, #13): - Build a minimal patch on save: only the color-settings keys that differ from the snapshot, and the palette only if the user touched it. Stops unrelated toggles from clobbering a concurrent palette edit (RFC 7396 list-replacement) made elsewhere. - Skip the post-refresh reseed effect while the user has unsaved edits, so a mid-flight cache invalidation doesn't silently wipe in-progress work. - changeCount now reports `dirtyColorKeys.length + (paletteDirty ? 1 : 0)` — toggles count individually, palette counts as one unit. Documented in the PR description. i18n: - Update label uses `_n( 'Update (%d change)', 'Update (%d changes)', count )` so single-change pluralization is correct. - Section list in the warning notice uses `Intl.ListFormat` for locale-aware joining instead of a hard-coded English comma, with a plain `, `.join() fallback for environments without it. SCSS / observability: - Replace hard-coded `#757575`, `#ddd`, `#fff` with `$gray-700`, `$gray-300`, `$white` from `@wordpress/base-styles/_colors`. - json-editor-modal: log fetch failures via `console.error` instead of swallowing them silently. Refs #838
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a new Edit Theme Settings entry to the Create Block Theme sidebar (cog icon, between Create Theme Variation and Edit Theme Metadata). Clicking it opens a modal with a single Color tab containing two panels:
defaultPalette,defaultGradients,defaultDuotone,custom,customGradient,customDuotone) and thelinkcolor control.settings.color.palette(add row, edit name and slug, pick color viaColorPicker, remove row), withName/Slugcolumn headers above the rows.The modal owns the working state for both panels, seeded from
getCurrentTheme().theme_json.settings.color. Clicking Update posts a partial-theme.jsonpayload toPOST /create-block-theme/v1/theme-settings(the endpoint that landed in #843) and reseeds the panels from the refreshed server state after a successful save. The modal stays open on success so the user can keep editing —theme.json-only edits do not need a page reload.This is the foundation PR for the Edit Theme Settings feature. Gradients, Duotone, and the Dimensions / Typography / Shadows / Templates tabs land in follow-up PRs (#839–#842).
Screenshots
(See the modal under: Site Editor → Create Block Theme sidebar → Edit Theme Settings)
Notable UX details
Update (N changes)when there are pending edits, plainUpdateotherwise. Disabled when nothing has changed and shows a busy state during save.@wordpress/notices.Test plan
theme.json.theme.json.theme.json.Refs
Updates since open
$break-small, with per-toggle help copy; pushes the Palette panel above the fold.pencilicon so its row icon renders consistently with the other menu entries.Further updates
+with a labeled "+ Add a color" tertiary button.+ Add a colorto align with the PanelBody chevron, dropped the doubled border between the summary card and the first accordion.Save / refresh cycle
theme_jsonpayload so the Update button resets immediately and the in-memory state matches what was just persisted./wp/v2/themes?status=activedirectly viaapiFetchon every open, so closing the Edit Theme Settings modal and going straight to View theme.json shows the on-disk file, not the previous cached snapshot.Review feedback addressed
Single commit covering the must-fix + should-fix items from review:
new-color-Nsuffix.__cbtRowIdassigned at creation (stripped from the payload before send), so removing a middle row no longer leaks Dropdown/ColorPicker state._n()soUpdate (1 change)vs.Update (3 changes)pluralize correctly.Intl.ListFormatfor locale-aware joining (with,.join() fallback).#757575/#ddd/#fffreplaced with$gray-700/$gray-300/$whitefrom@wordpress/base-styles.apiFetchfailures in View theme.json now log to console.Deferred to follow-ups:
cog, same as Editor Preferences) — aesthetic, not blocking.requestAnimationFrametiming on the Edit-palette scroll — current build opens the PanelBody synchronously, no transition; revisit if a future Gutenberg version animates the accordion.