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
161 changes: 125 additions & 36 deletions integrations/bricks/admin-app/src/components/ColorTab.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,27 @@
* 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.
*
* The "dark_overrides_enabled" flag (stored in the colors section) controls
* whether custom dark values are included in generated CSS. When OFF, the
* values are preserved in the DB but skipped — pure auto-derivation is used.
*/
import { meta } from '../lib/stores.svelte.js';
import { meta, readField, writeField } from '../lib/stores.svelte.js';
import ColorRow from './ColorRow.svelte';
import AdvancedSection from './AdvancedSection.svelte';

const colors = meta.defaults?.colors ?? {};

/** Capitalize first letter for display labels (matches the legacy ucfirst()). */
const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);

/** Brand color keys — only the 6 core brand colors for the basic section. */
const brandEntries = Object.entries(colors.brand ?? {});

/** Whether custom dark overrides are active (default: true when not set). */
const darkOn = $derived(readField('colors', 'dark_overrides_enabled') !== '0');

function toggleDark() {
writeField('colors', 'dark_overrides_enabled', darkOn ? '0' : '1');
}
</script>

<section>
Expand Down Expand Up @@ -55,49 +64,68 @@
{/each}
</div>

<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}
<!-- ── Dark mode toggle ──────────────────────────────────────── -->
<div class="dark-toggle-section">
<div class="dark-toggle-row">
<button
type="button"
class="dark-toggle-btn"
class:on={darkOn}
onclick={toggleDark}
aria-pressed={darkOn}
>
<span class="dark-toggle-btn__track">
<span class="dark-toggle-btn__thumb"></span>
</span>
Custom dark mode colors
</button>
<span class="dark-toggle-status">
{darkOn ? 'Active — custom values override auto-derivation' : 'Off — dark mode auto-derived from light source colors'}
</span>
</div>
<p class="hint">
The framework auto-derives dark mode from light source colors using CSS relative color syntax
(<code>oklch(from light clamp(0.65, 0.95−l·0.5, 0.88) c·0.9 h)</code>). Turn this on only
when you need precise per-color dark overrides; auto-derivation is usually good enough.
</p>
</div>

<h2 class="dark-heading">Status Colors — Dark Mode</h2>
<p class="hint">
Optional overrides for dark mode. Leave empty for auto-derivation.
</p>
{#if darkOn}
<h2 class="dark-heading">Brand Colors — Dark Mode</h2>
<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>

<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>
<h2 class="dark-heading">Status Colors — Dark Mode</h2>
<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>
{/if}
</AdvancedSection>
</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; }
.hint { color: #50575e; margin-bottom: 16px; max-width: 720px; font-size: 13px; }
.hint code { background: #f0f0f1; padding: 1px 4px; border-radius: 3px; font-size: 11px; }
.rows {
border: 1px solid #f0f0f1;
border-radius: 4px;
Expand All @@ -108,4 +136,65 @@
background: #f8f8fc;
border-color: #e2e4e9;
}

/* ── Dark mode toggle ───────────────────────────────────────── */
.dark-toggle-section {
margin-top: 24px;
padding: 14px 16px;
background: #f6f7f7;
border: 1px solid #e0e0e0;
border-radius: 4px;
}
.dark-toggle-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.dark-toggle-btn {
display: flex;
align-items: center;
gap: 8px;
background: none;
border: none;
cursor: pointer;
padding: 0;
font-size: 13px;
font-weight: 600;
color: #1d2327;
}
.dark-toggle-btn__track {
position: relative;
display: inline-block;
width: 36px;
height: 20px;
background: #c3c4c7;
border-radius: 10px;
transition: background 150ms;
flex-shrink: 0;
}
.dark-toggle-btn.on .dark-toggle-btn__track {
background: #2271b1;
}
.dark-toggle-btn__thumb {
position: absolute;
top: 2px;
left: 2px;
width: 16px;
height: 16px;
background: white;
border-radius: 50%;
transition: left 150ms;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
.dark-toggle-btn.on .dark-toggle-btn__thumb {
left: 18px;
}
.dark-toggle-status {
font-size: 12px;
color: #50575e;
}
.dark-toggle-section .hint {
margin-bottom: 0;
}
</style>
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
<script>
import { exportTokens, importTokens } from '../lib/api.js';
import { tokens } from '../lib/stores.svelte.js';
import { generateExportCSS } from '../lib/export.js';

// ── Export ────────────────────────────────────────────────────────────────
let exporting = $state(false);
let exportError = $state('');

const exportCSS = $derived(generateExportCSS(tokens));
const canExportCSS = $derived(exportCSS.length > 0);

function downloadCSS() {
if (!exportCSS) return;
const blob = new Blob([exportCSS], { type: 'text/css' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `slashed-custom-${new Date().toISOString().slice(0, 10)}.css`;
a.click();
URL.revokeObjectURL(url);
}

async function handleExport() {
exporting = true;
exportError = '';
Expand Down Expand Up @@ -81,7 +97,10 @@
</p>
<div class="action-row">
<button type="button" class="btn btn--primary" onclick={handleExport} disabled={exporting}>
{exporting ? 'Exporting…' : 'Download token file'}
{exporting ? 'Exporting…' : 'Download token file (.json)'}
</button>
<button type="button" class="btn btn--secondary" onclick={downloadCSS} disabled={!canExportCSS} title={canExportCSS ? 'Download overrides as a standalone CSS file' : 'No overrides set yet'}>
Download CSS file
</button>
{#if exportError}
<span class="status status--err">{exportError}</span>
Expand Down
Loading