Skip to content
Closed
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 configurator/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
// Persist the navigation prefs so a reload restores where the user was.
// Restore (with validation) happens in store.svelte.js via sanitiseUiState.
$effect(() => {
const snapshot = JSON.stringify({ domain: ui.domain, outputMode: ui.outputMode, uiTheme: ui.uiTheme, bundle: ui.bundle });
const snapshot = JSON.stringify({ domain: ui.domain, outputMode: ui.outputMode, uiTheme: ui.uiTheme, bundle: ui.bundle, showTokens: ui.showTokens });
try {
localStorage.setItem(UI_STORAGE_KEY, snapshot);
} catch {
Expand Down
1 change: 1 addition & 0 deletions configurator/src/components/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<div class="hdr__group" role="group" aria-label="Session">
<button class:ok={shareCopied} onclick={shareLink} disabled={modCount === 0} title="Copy shareable configuration link" aria-label="Copy shareable configuration link">{shareCopied ? '✓' : '🔗'}</button>
<button onclick={() => (ui.uiTheme = ui.uiTheme === 'dark' ? 'light' : 'dark')} aria-pressed={ui.uiTheme === 'light'} title="Toggle configurator theme" aria-label="Toggle configurator theme">{ui.uiTheme === 'dark' ? '☀' : '☾'}</button>
<button onclick={() => (ui.showTokens = !ui.showTokens)} aria-pressed={ui.showTokens} title={ui.showTokens ? 'Hide token names' : 'Show token names'} aria-label={ui.showTokens ? 'Hide token names' : 'Show token names'}>{ui.showTokens ? '--' : '{ }'}</button>
</div>
<div class="hdr__group" role="group" aria-label="Workspace">
<button class="hdr__optional" onclick={() => (ui.sidebarOpen = !ui.sidebarOpen)} aria-pressed={ui.sidebarOpen} title="Toggle sidebar" aria-label="Toggle sidebar">{ui.sidebarOpen ? '◧' : '▤'}</button>
Expand Down
4 changes: 3 additions & 1 deletion configurator/src/components/editors/ColorStudio.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import BrandColorRow from '../BrandColorRow.svelte';
import ColorAssignments from '../ColorAssignments.svelte';
import FriendlyControl from '../FriendlyControl.svelte';
import { ui } from '../../lib/store.svelte.js';
import ShadeRamp from '../ShadeRamp.svelte';
import StudioFrame from './StudioFrame.svelte';

const showToken = $derived(ui.showTokens);
const main = BRAND_COLOR_KEYS.filter((c) => c.group === 'brand');
const status = BRAND_COLOR_KEYS.filter((c) => c.group === 'status');
const tuning = [
Expand Down Expand Up @@ -60,7 +62,7 @@

<details class="panel__card cfg-card" open><summary>Core brand colors</summary><div class="rows">{#each main as {key,label} (key)}<BrandColorRow colorKey={key} {label} />{/each}</div></details>
<details><summary>Semantic colors</summary><div class="rows">{#each status as {key,label} (key)}<BrandColorRow colorKey={key} {label} />{/each}</div></details>
<details><summary>Contrast & palette tuning</summary><div class="rows">{#each tuning as token (token.name)}<FriendlyControl {token} showToken />{/each}</div></details>
<details><summary>Contrast & palette tuning</summary><div class="rows">{#each tuning as token (token.name)}<FriendlyControl {token} {showToken} />{/each}</div></details>
<details><summary>Semantic role preview</summary><ColorAssignments /></details>
<details><summary>Generated shade ramp</summary><ShadeRamp /></details>
</div>
Expand Down
5 changes: 3 additions & 2 deletions configurator/src/components/editors/StudioControls.svelte
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
<script>
import ControlSection from '../ControlSection.svelte';
import FriendlyControl from '../FriendlyControl.svelte';
import { overrides } from '../../lib/store.svelte.js';
import { overrides, ui } from '../../lib/store.svelte.js';

let { groups = [] } = $props();
const showToken = $derived(ui.showTokens);
</script>

<div class="studio-controls">
{#each groups as group (group.title)}
<ControlSection title={group.title} hint={group.hint} modifiedCount={group.tokens.filter((token) => overrides[token.name] != null).length}>
<div class="studio-controls__rows">
{#each group.tokens as token (token.name)}
<FriendlyControl {token} showToken />
<FriendlyControl {token} {showToken} />
{/each}
</div>
</ControlSection>
Expand Down
4 changes: 3 additions & 1 deletion configurator/src/lib/store.svelte.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ export const ui = $state({
uiTheme: savedUi.uiTheme ?? 'dark',
/** Selected dist bundle id for the install/setup picker (see lib/bundles.js). */
bundle: savedUi.bundle ?? 'optimal',
/** Show raw --sf-* token names in friendly/studio controls. */
showTokens: savedUi.showTokens ?? false,
/** Sidebar collapse — for narrow viewports / a focus-mode. */
sidebarOpen: true,
/**
Expand Down Expand Up @@ -109,7 +111,7 @@ export const savedThemes = $state(loadSavedThemes());
* Load the persisted UI preferences (domain / output format / theme / bundle),
* validated against the live taxonomy. Persisting happens in App.svelte
* via an $effect over the same fields.
* @returns {{ domain?: string, outputMode?: 'layer'|'root', uiTheme?: 'light'|'dark', bundle?: string }}
* @returns {{ domain?: string, outputMode?: 'layer'|'root', uiTheme?: 'light'|'dark', bundle?: string, showTokens?: boolean }}
*/
function loadUiState() {
if (typeof localStorage === 'undefined') return {};
Expand Down
6 changes: 5 additions & 1 deletion configurator/src/lib/uiState.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const UI_STORAGE_KEY = 'slashed-configurator/ui/v1';
* simply ignored — the flat IA has no complexity mode.
*
* @param {string|null} raw JSON string from localStorage (or null)
* @returns {{ domain?: string, outputMode?: 'layer'|'root', uiTheme?: 'light'|'dark', bundle?: string }}
* @returns {{ domain?: string, outputMode?: 'layer'|'root', uiTheme?: 'light'|'dark', bundle?: string, showTokens?: boolean }}
*/
export function sanitiseUiState(raw) {
const out = {};
Expand Down Expand Up @@ -49,5 +49,9 @@ export function sanitiseUiState(raw) {
out.bundle = parsed.bundle;
}

if (typeof parsed.showTokens === 'boolean') {
out.showTokens = parsed.showTokens;
}

return out;
}
33 changes: 31 additions & 2 deletions configurator/tests-components/studios.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ import MotionStudio from '../src/components/editors/MotionStudio.svelte';
import EffectsStudio from '../src/components/editors/EffectsStudio.svelte';
import FriendlyControl from '../src/components/FriendlyControl.svelte';
import { tokenByName } from '../src/lib/model.js';
import { clearAll, overrides } from '../src/lib/store.svelte.js';
import { clearAll, overrides, ui } from '../src/lib/store.svelte.js';

beforeEach(() => clearAll());
beforeEach(() => {
clearAll();
ui.showTokens = false;
});

const STUDIOS = [
['Typography Studio', TypographyStudio, ['The quick brown fox', 'Fluid scale']],
Expand All @@ -46,12 +49,38 @@ describe('visual studios', () => {
}

test('Typography Studio switches scopes and renders active panel controls', async () => {
ui.showTokens = true;
const { getByRole, getByText, getAllByText } = render(TypographyStudio);
await fireEvent.click(getByRole('tab', { name: 'Headings' }));
expect(getAllByText('Heading aliases, line-height, tracking and max-width constraints.').length).toBeGreaterThan(0);
expect(getByText('--sf-h1-size')).toBeInTheDocument();
});



test('Studio token names follow the global show/hide token setting', async () => {
const { queryByText, getByText } = render(TypographyStudio);

expect(queryByText('--sf-font-body')).not.toBeInTheDocument();

ui.showTokens = true;
await tick();
expect(getByText('--sf-font-body')).toBeInTheDocument();

ui.showTokens = false;
await tick();
expect(queryByText('--sf-font-body')).not.toBeInTheDocument();
});

test('FriendlyControl can still reveal raw token info locally', async () => {
const token = tokenByName.get('--sf-heading-text-wrap');
const { getByRole, getByText, queryByText } = render(FriendlyControl, { props: { token } });

expect(queryByText('--sf-heading-text-wrap')).not.toBeInTheDocument();
await fireEvent.click(getByRole('button', { name: 'Show token' }));
expect(getByText('--sf-heading-text-wrap')).toBeInTheDocument();
});

test('FriendlyControl renders schema select controls and writes overrides', async () => {
const token = tokenByName.get('--sf-heading-text-wrap');
const { getByLabelText } = render(FriendlyControl, { props: { token, showToken: true } });
Expand Down