Skip to content
Merged
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
53 changes: 47 additions & 6 deletions integrations/bricks/admin-app/src/components/ColorTab.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
* Colors tab body. Pulls the brand and status defaults from the
* hydrated metadata and renders a ColorRow per token.
*
* Note how thin this is compared to render_tab_colors() in
* class-admin-page.php: there's no separate hex_hints lookup ceremony,
* no ucfirst() label munging in PHP, no manual table markup. The
* table-of-rows shape is just a single each block.
* Supports both light-mode (source) and dark-mode (optional override)
* colors. Dark-mode values are optional β€” if left empty, the framework
* auto-derives dark variants from the light source tokens via relative
* color syntax. Setting an explicit dark value overrides auto-derivation.
*/
import { meta } from '../lib/stores.svelte.js';
import ColorRow from './ColorRow.svelte';
Expand All @@ -18,7 +18,7 @@
</script>

<section>
<h2>Brand Colors</h2>
<h2>Brand Colors β€” Light Mode</h2>
<p class="hint">
Pick a color via the HEX input, or switch to <em>Advanced</em> to paste any
CSS color value (oklch, rgb, hsl, var(...), etc). Whatever you save is fed
Expand All @@ -37,7 +37,26 @@
{/each}
</div>

<h2 class="status-heading">Status Colors</h2>
<h2 class="dark-heading">Brand Colors β€” Dark Mode</h2>
<p class="hint">
Optional overrides for dark mode. Leave empty to let the framework auto-derive
dark variants from the light source colors. Set explicit values for full
control over dark-mode appearance.
</p>

<div class="rows rows--dark">
{#each Object.entries(colors.brand_dark ?? colors.brand ?? {}) as [name] (name)}
<ColorRow
storeKey={`brand_dark_${name}`}
label={cap(name)}
hexHint={colors.brand_dark_hex_hints?.[name] ?? ''}
rawHint={colors.brand_dark?.[name] ?? ''}
cssVar={`--sf-color-${name}-dark`}
/>
{/each}
</div>

<h2 class="status-heading">Status Colors β€” Light Mode</h2>
<div class="rows">
{#each Object.entries(colors.status ?? {}) as [name, oklch] (name)}
<ColorRow
Expand All @@ -49,16 +68,38 @@
/>
{/each}
</div>

<h2 class="dark-heading">Status Colors β€” Dark Mode</h2>
<p class="hint">
Optional overrides for dark mode. Leave empty for auto-derivation.
</p>

<div class="rows rows--dark">
{#each Object.entries(colors.status_dark ?? colors.status ?? {}) as [name] (name)}
<ColorRow
storeKey={`status_dark_${name}`}
label={cap(name)}
hexHint={colors.status_dark_hex_hints?.[name] ?? ''}
rawHint={colors.status_dark?.[name] ?? ''}
cssVar={`--sf-color-${name}-dark`}
/>
{/each}
</div>
</section>

<style>
h2 { margin-top: 0; }
.status-heading { margin-top: 24px; }
.dark-heading { margin-top: 24px; }
.hint { color: #50575e; margin-bottom: 16px; max-width: 720px; }
.rows {
border: 1px solid #f0f0f1;
border-radius: 4px;
padding: 0 12px;
background: #fcfcfd;
}
.rows--dark {
background: #f8f8fc;
border-color: #e2e4e9;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@
for (const name of brand) {
const v = colors[`brand_${name}`];
if (v) pairs.push(`--sf-color-${name}-light:${v}`);
const vd = colors[`brand_dark_${name}`];
if (vd) pairs.push(`--sf-color-${name}-dark:${vd}`);
}
for (const name of statuses) {
const v = colors[`status_${name}`];
if (v) pairs.push(`--sf-color-${name}-light:${v}`);
const vd = colors[`status_dark_${name}`];
if (vd) pairs.push(`--sf-color-${name}-dark:${vd}`);
}
if (typography.font_body) pairs.push(`--sf-font-body:${typography.font_body}`);
if (typography.font_heading) pairs.push(`--sf-font-heading:${typography.font_heading}`);
Expand All @@ -65,10 +69,14 @@
for (const name of brand) {
const v = colors[`brand_${name}`];
if (v) decls.push(`--sf-color-${name}-light: ${v}`);
const vd = colors[`brand_dark_${name}`];
if (vd) decls.push(`--sf-color-${name}-dark: ${vd}`);
}
for (const name of statuses) {
const v = colors[`status_${name}`];
if (v) decls.push(`--sf-color-${name}-light: ${v}`);
const vd = colors[`status_dark_${name}`];
if (vd) decls.push(`--sf-color-${name}-dark: ${vd}`);
}
if (typography.font_body) decls.push(`--sf-font-body: ${typography.font_body}`);
if (typography.font_heading) decls.push(`--sf-font-heading: ${typography.font_heading}`);
Expand Down
1 change: 1 addition & 0 deletions integrations/bricks/admin-app/src/lib/cheatsheet-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const variableGroups = [
description: "Brand, status, semantic, and derived color tokens. Source colors use oklch(); the framework derives light-dark pairs, palettes, and alpha scales automatically.",
tokens: [
{ name: "--sf-color-{brand}-light", description: "Source color for the brand (oklch registered value). Set this to rebrand." },
{ name: "--sf-color-{brand}-dark", description: "Optional dark-mode override. If set, replaces the auto-derived dark variant for full per-mode control." },
{ name: "--sf-color-{brand}", description: "Resolved light-dark adaptive color for use in rules." },
{ name: "--sf-color-{brand}-{50-950}", description: "Palette scale: 50 (lightest) to 950 (darkest) mixed with base/text." },
{ name: "--sf-color-{brand}-a{5-95}", description: "Alpha scale: 5% to 95% opacity of the brand color." },
Expand Down
2 changes: 1 addition & 1 deletion integrations/bricks/assets/admin-app/app.css

Large diffs are not rendered by default.

18 changes: 10 additions & 8 deletions integrations/bricks/assets/admin-app/app.js

Large diffs are not rendered by default.

20 changes: 18 additions & 2 deletions integrations/bricks/includes/class-css-generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public static function get_override_css() {
private static function generate_color_declarations( $settings ) {
$declarations = array();

// Brand colors: brand_primary -> --sf-color-primary-light.
// Brand colors (light): brand_primary -> --sf-color-primary-light.
$brand_colors = array( 'primary', 'secondary', 'tertiary', 'action', 'neutral', 'base' );
foreach ( $brand_colors as $color ) {
$key = 'brand_' . $color;
Expand All @@ -170,7 +170,15 @@ private static function generate_color_declarations( $settings ) {
}
}

// Status colors: status_success -> --sf-color-success-light.
// Brand colors (dark): brand_dark_primary -> --sf-color-primary-dark.
foreach ( $brand_colors as $color ) {
$key = 'brand_dark_' . $color;
if ( ! empty( $settings[ $key ] ) ) {
$declarations[] = '--sf-color-' . $color . '-dark: ' . $settings[ $key ] . ';';
}
}

// Status colors (light): status_success -> --sf-color-success-light.
$status_colors = array( 'success', 'warning', 'error', 'info', 'danger' );
foreach ( $status_colors as $color ) {
$key = 'status_' . $color;
Expand All @@ -179,6 +187,14 @@ private static function generate_color_declarations( $settings ) {
}
}

// Status colors (dark): status_dark_success -> --sf-color-success-dark.
foreach ( $status_colors as $color ) {
$key = 'status_dark_' . $color;
if ( ! empty( $settings[ $key ] ) ) {
$declarations[] = '--sf-color-' . $color . '-dark: ' . $settings[ $key ] . ';';
}
}

return $declarations;
}

Expand Down
30 changes: 30 additions & 0 deletions integrations/bricks/includes/class-token-defaults.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,28 @@ public static function get_colors() {
'neutral' => 'oklch(0.45 0.02 260)',
'base' => 'oklch(0.98 0.005 260)',
),
'brand_dark' => array(
'primary' => '',
'secondary' => '',
'tertiary' => '',
'action' => '',
'neutral' => '',
'base' => '',
),
'status' => array(
'success' => 'oklch(0.48 0.17 150)',
'warning' => 'oklch(0.75 0.17 80)',
'error' => 'oklch(0.62 0.20 35)',
'info' => 'oklch(0.48 0.15 240)',
'danger' => 'oklch(0.48 0.24 12)',
),
'status_dark' => array(
'success' => '',
'warning' => '',
'error' => '',
'info' => '',
'danger' => '',
),
'brand_hex_hints' => array(
'primary' => '#4338ca',
'secondary' => '#1e293b',
Expand All @@ -85,13 +100,28 @@ public static function get_colors() {
'neutral' => '#64748b',
'base' => '#fafafa',
),
'brand_dark_hex_hints' => array(
'primary' => '#818cf8',
'secondary' => '#cbd5e1',
'tertiary' => '#c084fc',
'action' => '#22d3ee',
'neutral' => '#94a3b8',
'base' => '#1e1e2e',
),
'status_hex_hints' => array(
'success' => '#16a34a',
'warning' => '#ca8a04',
'error' => '#dc2626',
'info' => '#2563eb',
'danger' => '#dc2626',
),
'status_dark_hex_hints' => array(
'success' => '#4ade80',
'warning' => '#fbbf24',
'error' => '#f87171',
'info' => '#60a5fa',
'danger' => '#f87171',
),
);
}

Expand Down
32 changes: 28 additions & 4 deletions integrations/bricks/slashed-bricks.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,39 @@ function slashed_bricks_admin_init() {
// talks to the REST controller below for save/reset.
new Slashed_Bricks_Admin_Page_Svelte();

// REST endpoints powering the Svelte SPA. Sanitization and storage
// are delegated to the shared helper classes so every admin
// surface persists data identically.
new Slashed_Bricks_REST_Controller();
// REST routes are registered via the dedicated rest_api_init hook
// (slashed_bricks_rest_routes_init) so they work on both admin and
// non-admin requests. No need to instantiate the controller here.
}
if ( is_admin() ) {
add_action( 'plugins_loaded', 'slashed_bricks_admin_init' );
}

/**
* Register REST routes unconditionally via rest_api_init.
*
* WordPress fires `rest_api_init` exclusively during REST dispatch β€”
* never on normal admin or frontend requests. Hooking here guarantees
* the routes exist regardless of `is_admin()` state and eliminates the
* risk of dependency drift between admin and REST init paths.
*
* On admin requests the REST controller is ALSO instantiated inside
* `slashed_bricks_admin_init()` (which fires earlier, at plugins_loaded).
* That's harmless: WordPress deduplicates routes by namespace+path, and
* `require_once` prevents re-declaration of class files. The admin path
* keeps it so the controller is available for wp_localize_script (the
* NAMESPACE constant) without an extra require.
*/
function slashed_bricks_rest_routes_init() {
require_once SLASHED_BRICKS_PATH . 'includes/class-token-defaults.php';
require_once SLASHED_BRICKS_PATH . 'includes/class-token-sanitizer.php';
require_once SLASHED_BRICKS_PATH . 'includes/class-tab-registry.php';
require_once SLASHED_BRICKS_PATH . 'includes/class-rest-controller.php';

( new Slashed_Bricks_REST_Controller() )->register_routes();
}
add_action( 'rest_api_init', 'slashed_bricks_rest_routes_init' );

/**
* Data managers: early initialization at plugins_loaded.
*
Expand Down