diff --git a/design/maestro-ds/CLAUDE.md b/design/maestro-ds/CLAUDE.md new file mode 100644 index 0000000..484b57c --- /dev/null +++ b/design/maestro-ds/CLAUDE.md @@ -0,0 +1,76 @@ +# CLAUDE.md — Maestro Design System + +Engineering conventions for working in this design-system repo. Read alongside `README.md` (design foundations) and `SKILL.md` (the design language quick-start). The phosphor-console aesthetic and the rules in SKILL.md are binding — this file covers **how the code is organized and how to extend it correctly**. + +## What this repo is + +A compiler-backed design system. On every change an automated compiler reads the sources and regenerates three files — **never hand-edit these**: + +- `_ds_bundle.js` — the compiled component library, exposed as `window.MaestroDesignSystem_a6a290` +- `_ds_manifest.json` — card / token / component index +- `_adherence.oxlintrc.json` — lint rules + +The only fixed entry point is `styles.css` (root). It must stay a list of `@import` lines — never inline rules there. + +## Layout + +``` +styles.css # entry — @import only +tokens/ # CSS custom properties, one file per concern + colors.css # base + signal + -dim + semantic aliases + [data-theme=light] scope + typography.css # --mono family, size/leading/tracking scale + effects.css # spacing, radii, borders, shadows, @keyframes + fonts.css # Google Fonts @import (IBM Plex Mono + Noto Sans SC) +guidelines/*.card.html # specimen cards for the Design System tab +components// # core | forms | surfaces + Name.jsx # the component (named export) + Name.d.ts # props contract (+ @startingPoint JSDoc if applicable) + Name.prompt.md # one-line what/when + JSX example + .card.html # @dsCard showcase for the whole group +ui_kits/console/ # desktop console recreation (composes components) +ui_kits/console_mobile/ # 390px mobile recreation +assets/icons/*.svg # double-tone icon set +``` + +## Authoring a component (the contract the compiler enforces) + +A component is discovered when a directory holds **all three**: `Name.jsx` (PascalCase, with `export function Name(props)`), `Name.d.ts`, and ideally `Name.prompt.md`. To add or change one: + +1. Write `Name.jsx` — React only, no npm deps, no CSS-in-JS libs. Style via the CSS custom properties (`var(--…)`). Self-contained; siblings may import each other by relative path. +2. Inject component CSS once via an `ensureCss()` guard that appends a ` +
+ diff --git a/design/maestro-ds/components/forms/ComplexitySeg.d.ts b/design/maestro-ds/components/forms/ComplexitySeg.d.ts new file mode 100644 index 0000000..3d8b89d --- /dev/null +++ b/design/maestro-ds/components/forms/ComplexitySeg.d.ts @@ -0,0 +1,16 @@ +/** + * 复杂度分段选择:HARD / MEDIUM / EASY 三段,选中段填充对应信号色 dim 底。 + * 可选第四段 auto(智能 · 由模型决定复杂度),青色信号。 + */ +export interface ComplexitySegProps { + value?: 'hard' | 'medium' | 'easy' | 'auto'; + defaultValue?: 'hard' | 'medium' | 'easy' | 'auto'; + onChange?: (value: 'hard' | 'medium' | 'easy' | 'auto') => void; + /** 追加 auto 段(青色):由模型决定复杂度 */ + includeAuto?: boolean; + /** auto 段文字,默认 'AUTO'(可传本地化「智能」) */ + autoLabel?: string; + /** 覆盖各段文字:{ hard, medium, easy, auto } 局部即可 */ + labels?: Partial>; +} +export declare function ComplexitySeg(props: ComplexitySegProps): JSX.Element; diff --git a/design/maestro-ds/components/forms/ComplexitySeg.jsx b/design/maestro-ds/components/forms/ComplexitySeg.jsx new file mode 100644 index 0000000..f351905 --- /dev/null +++ b/design/maestro-ds/components/forms/ComplexitySeg.jsx @@ -0,0 +1,32 @@ +const css = ` +.m-seg{display:inline-flex;border:1px solid var(--line);font-family:var(--mono);border-radius:var(--radius-sm,4px);overflow:hidden} +.m-seg button{font-family:var(--mono);padding:7px 13px;cursor:pointer;font-size:11px;font-weight:700;letter-spacing:.12em;color:var(--faint);background:transparent;border:none;border-radius:0} +.m-seg button + button{border-left:1px solid var(--line)} +.m-seg button.on-hard{background:var(--red-dim);color:var(--red)} +.m-seg button.on-medium{background:var(--amber-dim);color:var(--amber)} +.m-seg button.on-easy{background:var(--green-dim);color:var(--green)} +.m-seg button.on-auto{background:var(--cyan-dim);color:var(--cyan)} +`; +function ensureCss() { + if (typeof document === 'undefined' || document.getElementById('m-seg-css')) return; + const s = document.createElement('style'); s.id = 'm-seg-css'; s.textContent = css; + document.head.appendChild(s); +} +const BASE_OPTS = [['hard', 'HARD'], ['medium', 'MEDIUM'], ['easy', 'EASY']]; + +export function ComplexitySeg({ value, defaultValue = 'medium', onChange, includeAuto, autoLabel = 'AUTO', labels }) { + ensureCss(); + const [inner, setInner] = React.useState(defaultValue); + const cur = value !== undefined ? value : inner; + const opts = includeAuto ? [...BASE_OPTS, ['auto', autoLabel]] : BASE_OPTS; + return ( + + {opts.map(([v, lab]) => ( + + ))} + + ); +} diff --git a/design/maestro-ds/components/forms/ComplexitySeg.prompt.md b/design/maestro-ds/components/forms/ComplexitySeg.prompt.md new file mode 100644 index 0000000..7dbe932 --- /dev/null +++ b/design/maestro-ds/components/forms/ComplexitySeg.prompt.md @@ -0,0 +1,11 @@ +复杂度三段选择器,建任务表单用。选中段填充信号色 dim 底(红/琥珀/绿)。 + +```jsx + setCplx(v)} /> +``` + +可加第四段 `auto`(青色,「智能」= 由模型决定复杂度)。`autoLabel` 传本地化文字,`labels` 覆盖任意段: + +```jsx + +``` diff --git a/design/maestro-ds/components/forms/Input.d.ts b/design/maestro-ds/components/forms/Input.d.ts new file mode 100644 index 0000000..07ef3cd --- /dev/null +++ b/design/maestro-ds/components/forms/Input.d.ts @@ -0,0 +1,17 @@ +/** + * 文本/数字输入框:深底直角,focus 绿 dim 边框 + 1px ring。可带顶部小标签与红色必填星。 + */ +export interface InputProps { + /** 顶部 11px 字距标签 */ + label?: string; + /** 标签后追加红色 * */ + required?: boolean; + type?: 'text' | 'number'; + placeholder?: string; + value?: string; + defaultValue?: string; + onChange?: (value: string) => void; + width?: number | string; + style?: React.CSSProperties; +} +export declare function Input(props: InputProps): JSX.Element; diff --git a/design/maestro-ds/components/forms/Input.jsx b/design/maestro-ds/components/forms/Input.jsx new file mode 100644 index 0000000..b555a16 --- /dev/null +++ b/design/maestro-ds/components/forms/Input.jsx @@ -0,0 +1,28 @@ +const css = ` +.m-label{display:flex;flex-direction:column;gap:4px;font-family:var(--mono);font-size:11px;color:var(--muted);letter-spacing:.08em} +.m-input{font-family:var(--mono);font-size:13px;background:var(--bg-deep);color:var(--ink);border:1px solid var(--line);padding:7px 10px;outline:none;transition:border-color .12s;border-radius:var(--radius-sm,4px)} +.m-input:focus{border-color:var(--green-dim);box-shadow:0 0 0 1px var(--green-dim)} +.m-input::placeholder{color:var(--faint)} +.m-req{color:var(--red)} +`; +export function ensureFieldCss() { + if (typeof document === 'undefined' || document.getElementById('m-field-css')) return; + const s = document.createElement('style'); s.id = 'm-field-css'; s.textContent = css; + document.head.appendChild(s); +} + +export function Input({ label, required, type = 'text', placeholder, value, defaultValue, onChange, width, style }) { + ensureFieldCss(); + const ctrl = ( + onChange(e.target.value) : undefined} readOnly={value !== undefined && !onChange} /> + ); + if (!label) return ctrl; + return ( + + ); +} diff --git a/design/maestro-ds/components/forms/Input.prompt.md b/design/maestro-ds/components/forms/Input.prompt.md new file mode 100644 index 0000000..5061fb9 --- /dev/null +++ b/design/maestro-ds/components/forms/Input.prompt.md @@ -0,0 +1,6 @@ +输入框:bg-deep 底、直角、focus 时绿 dim 描边+ring。占位文案给真实示例(如 `/path/to/repo`)。 + +```jsx + + +``` diff --git a/design/maestro-ds/components/forms/Select.d.ts b/design/maestro-ds/components/forms/Select.d.ts new file mode 100644 index 0000000..badd0d8 --- /dev/null +++ b/design/maestro-ds/components/forms/Select.d.ts @@ -0,0 +1,13 @@ +/** + * 下拉选择(自绘弹层,非原生 select):触发器与 Input 同形制,弹出磷光风格选项菜单——当前项绿色 ▍ 标记 + 抬升底。选项文案用「值 · 中文」格式(如 "manual · 手动")。 + */ +export interface SelectProps { + label?: string; + required?: boolean; + options?: Array; + value?: string; + defaultValue?: string; + onChange?: (value: string) => void; + style?: React.CSSProperties; +} +export declare function Select(props: SelectProps): JSX.Element; diff --git a/design/maestro-ds/components/forms/Select.jsx b/design/maestro-ds/components/forms/Select.jsx new file mode 100644 index 0000000..d00c937 --- /dev/null +++ b/design/maestro-ds/components/forms/Select.jsx @@ -0,0 +1,69 @@ +import { ensureFieldCss } from './Input.jsx'; + +const css = ` +.m-select{position:relative;display:inline-flex;flex-direction:column;min-width:120px} +.m-select-trigger{font-family:var(--mono);font-size:13px;background:var(--bg-deep);color:var(--ink);border:1px solid var(--line);border-radius:var(--radius-sm,4px);padding:7px 10px;cursor:pointer;outline:none;transition:border-color .12s;display:flex;align-items:center;gap:10px;text-align:left;width:100%} +.m-select-trigger:hover{border-color:var(--muted)} +.m-select-trigger.open,.m-select-trigger:focus{border-color:var(--green-dim);box-shadow:0 0 0 1px var(--green-dim)} +.m-select-value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.m-select-caret{flex:none;font-size:8px;color:var(--faint);transition:transform .12s} +.m-select-trigger.open .m-select-caret{transform:rotate(180deg)} +.m-select-menu{position:absolute;top:calc(100% + 5px);left:0;right:0;z-index:80;display:grid;background:var(--bg-deep);border:1px solid var(--line);border-radius:var(--radius-md,6px);padding:4px;box-shadow:0 10px 30px rgba(0,0,0,.65);animation:maestro-rise .12s ease both;max-height:260px;overflow-y:auto} +.m-select-opt{font-family:var(--mono);font-size:12.5px;text-align:left;background:transparent;color:var(--ink);border:none;border-radius:4px;padding:7px 10px 7px 8px;cursor:pointer;display:flex;align-items:center;gap:7px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.m-select-opt:hover{background:var(--panel)} +.m-select-opt.on{background:var(--panel-2);color:var(--green);font-weight:700} +.m-select-mark{flex:none;width:8px;color:var(--green);font-size:10px} +`; +function ensureSelectCss() { + if (typeof document === 'undefined' || document.getElementById('m-select-css')) return; + const s = document.createElement('style'); s.id = 'm-select-css'; s.textContent = css; + document.head.appendChild(s); +} + +export function Select({ label, required, options = [], value, defaultValue, onChange, style }) { + ensureFieldCss(); + ensureSelectCss(); + const opts = options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o)); + const [inner, setInner] = React.useState(defaultValue !== undefined ? defaultValue : (opts[0] ? opts[0].value : '')); + const cur = value !== undefined ? value : inner; + const curOpt = opts.find((o) => o.value === cur); + const [open, setOpen] = React.useState(false); + const ref = React.useRef(null); + React.useEffect(() => { + if (!open) return; + const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; + const onKey = (e) => { if (e.key === 'Escape') setOpen(false); }; + document.addEventListener('mousedown', onDoc); + document.addEventListener('keydown', onKey); + return () => { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onKey); }; + }, [open]); + const pick = (v) => { + setInner(v); setOpen(false); + if (onChange) onChange(v); + }; + const ctrl = ( + + + {open ? ( + + {opts.map((o) => ( + + ))} + + ) : null} + + ); + if (!label) return ctrl; + return ( + + ); +} diff --git a/design/maestro-ds/components/forms/Select.prompt.md b/design/maestro-ds/components/forms/Select.prompt.md new file mode 100644 index 0000000..547a6eb --- /dev/null +++ b/design/maestro-ds/components/forms/Select.prompt.md @@ -0,0 +1,8 @@ +下拉选择,与 Input 同形制。选项标签遵循「英文值 · 中文说明」惯例。 + +```jsx + + + + + + + + + + \ No newline at end of file