Implement M1.5 visual design foundation: tokens, IBM Plex, theme toggle, chart themes

This commit is contained in:
2026-06-05 01:29:10 +03:00
parent 547edb85e4
commit 22f0556ef8
31 changed files with 713 additions and 136 deletions
+5 -4
View File
@@ -2,6 +2,7 @@
height: 100%;
width: 100%;
overflow: auto;
background: var(--bg);
}
.chart {
@@ -9,16 +10,16 @@
align-items: flex-start;
justify-content: center;
min-height: 100%;
padding: var(--space-2);
padding: var(--space-5);
}
.error {
margin: 0;
padding: var(--space-3);
padding: var(--space-5);
font-family: var(--font-mono);
font-size: 12px;
line-height: 1.5;
color: var(--color-error);
line-height: 1.6;
color: var(--support-error);
white-space: pre-wrap;
word-break: break-word;
}
+33 -25
View File
@@ -2,24 +2,26 @@
display: flex;
flex-direction: column;
height: 100%;
gap: var(--space-3);
}
.createNew {
flex: 0 0 auto;
padding: var(--space-2) var(--space-3);
border: 1px solid var(--color-border);
margin: var(--space-4);
height: 40px;
padding: 0 var(--space-5);
border: var(--border-width) solid transparent;
border-radius: var(--radius);
background: var(--color-accent);
color: var(--color-accent-contrast);
background: var(--accent);
color: var(--accent-contrast);
font: inherit;
font-weight: 600;
cursor: pointer;
text-align: left;
text-align: center;
transition: background var(--dur-fast) var(--ease);
}
.createNew:hover {
filter: brightness(1.05);
background: var(--accent-hover);
}
.list {
@@ -29,34 +31,36 @@
flex: 1 1 auto;
min-height: 0;
overflow: auto;
display: flex;
flex-direction: column;
gap: var(--space-1);
border-top: var(--border-width) solid var(--border);
}
.empty {
color: var(--color-text-muted);
color: var(--text-secondary);
font-size: 13px;
padding: var(--space-2);
padding: var(--space-5) var(--space-4);
}
.item {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
border: 1px solid transparent;
border-radius: var(--radius);
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
border-left: 2px solid transparent;
cursor: pointer;
transition: background var(--dur-fast) var(--ease);
}
.item + .item {
border-top: var(--border-width) solid var(--border);
}
.item:hover {
background: var(--color-surface);
background: var(--layer-01);
}
.active {
background: var(--color-surface);
border-color: var(--color-accent);
background: var(--layer-01);
border-left-color: var(--accent);
}
.itemMain {
@@ -64,7 +68,7 @@
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
gap: var(--space-1);
}
.name {
@@ -77,26 +81,30 @@
.date {
font-size: 11px;
color: var(--color-text-muted);
color: var(--text-secondary);
}
.delete {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: none;
color: var(--color-text-muted);
color: var(--text-secondary);
cursor: pointer;
font-size: 12px;
padding: var(--space-1);
border-radius: var(--radius);
opacity: 0;
transition: opacity var(--dur-fast) var(--ease);
}
.item:hover .delete {
.item:hover .delete,
.delete:focus-visible {
opacity: 1;
}
.delete:hover {
color: var(--color-error);
background: var(--color-bg);
color: var(--support-error);
}
+3 -2
View File
@@ -1,6 +1,7 @@
.editorPane {
position: relative;
height: 100%;
background: var(--bg);
}
.editor {
@@ -15,8 +16,8 @@
display: flex;
align-items: center;
justify-content: center;
color: var(--color-text-muted);
color: var(--text-secondary);
font-size: 13px;
background: var(--color-bg);
background: var(--bg);
pointer-events: none;
}
+4 -1
View File
@@ -42,6 +42,9 @@ export function SpecEditor() {
language: 'json',
automaticLayout: true,
minimap: { enabled: false },
// The editor is a Plex Mono surface per the design language (doc §3.1).
// Monaco needs an explicit family string — it can't read the CSS token.
fontFamily: "'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace",
fontSize: 13,
tabSize: 2,
wordWrap: 'on',
@@ -78,7 +81,7 @@ export function SpecEditor() {
// Editor theme follows the UI theme (M1: light/dark stock themes).
useEffect(() => {
monaco.editor.setTheme(uiTheme === 'experimental' ? 'vs-dark' : 'vs');
monaco.editor.setTheme(uiTheme === 'dark' ? 'vs-dark' : 'vs');
}, [uiTheme]);
return (
+21
View File
@@ -0,0 +1,21 @@
.toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
border: var(--border-width) solid transparent;
border-radius: var(--radius);
background: transparent;
color: var(--text-secondary);
cursor: pointer;
transition:
background var(--dur-fast) var(--ease),
color var(--dur-fast) var(--ease);
}
.toggle:hover {
background: var(--layer-02);
color: var(--text);
}
+51
View File
@@ -0,0 +1,51 @@
/**
* Theme toggle — a header control that flips light ⇄ dark (spec §07 Appearance).
*
* Interim home: the spec houses the UI-theme control inside the Settings modal,
* which arrives in M5. Until then this header button is the control; it persists
* through the same `ui.theme` settings key, so M5 can move it into Settings (or
* keep it as a shortcut) without changing what's stored.
*
* The button shows the icon of the theme you'll switch *to* (moon when light,
* sun when dark) and labels itself for screen readers. The focus ring comes from
* the shared rule in base.css; chart + editor follow the theme via their own
* store subscriptions, so flipping the store repaints everything.
*/
import { useAppStore } from '../stores/AppStore';
import styles from './ThemeToggle.module.css';
function MoonIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
</svg>
);
}
function SunIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="4.5" />
<path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
</svg>
);
}
export function ThemeToggle() {
const uiTheme = useAppStore((s) => s.uiTheme);
const toggleTheme = useAppStore((s) => s.toggleTheme);
const target = uiTheme === 'dark' ? 'light' : 'dark';
return (
<button
type="button"
className={styles.toggle}
onClick={toggleTheme}
aria-label={`Switch to ${target} theme`}
title={`Switch to ${target} theme`}
>
{uiTheme === 'dark' ? <SunIcon /> : <MoonIcon />}
</button>
);
}