From 73f80d27c9f3612d1756fa305f6a02fd2fe7e092 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Wed, 24 Jun 2026 22:13:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(agent):=20=E6=8E=A5=E9=80=9A=20plan=20?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=94=BE=E8=A1=8C=EF=BC=88autoApprovePlan?= =?UTF-8?q?=EF=BC=89+=20=E9=A1=B9=E7=9B=AE=E9=85=8D=E7=BD=AE=E5=8F=AF?= =?UTF-8?q?=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 之前 auto_approve_plan/auto_approve_exec/checks 列与 Project 类型已存在,但 PatchProjectInput 不含它们 → 无法启用(dead config)。本次接通: - PatchProjectInput + patchProject 支持 autoApprovePlan/autoApproveExec/checks - API PATCH /projects 透传三字段 - ingest decompose-result:auto-approved + autoApprovePlan + 全 easy + 子任务≤5 → store.decide(accept) 跳过 plan_review 直达 decomposed(默认关) Co-Authored-By: Claude Opus 4.8 --- design/maestro-ds/CLAUDE.md | 76 + design/maestro-ds/SKILL.md | 50 + design/maestro-ds/_adherence.oxlintrc.json | 389 ++ design/maestro-ds/_ds_bundle.js | 6186 +++++++++++++++++ design/maestro-ds/_ds_manifest.json | 1 + design/maestro-ds/assets/icons/add.svg | 1 + design/maestro-ds/assets/icons/close.svg | 1 + design/maestro-ds/assets/icons/config.svg | 1 + design/maestro-ds/assets/icons/deps.svg | 1 + design/maestro-ds/assets/icons/event.svg | 1 + design/maestro-ds/assets/icons/flow.svg | 1 + design/maestro-ds/assets/icons/gate.svg | 1 + design/maestro-ds/assets/icons/git.svg | 1 + design/maestro-ds/assets/icons/project.svg | 1 + design/maestro-ds/assets/icons/ready.svg | 1 + design/maestro-ds/assets/icons/reason.svg | 1 + design/maestro-ds/assets/icons/running.svg | 1 + design/maestro-ds/assets/icons/search.svg | 1 + design/maestro-ds/assets/icons/sync.svg | 1 + design/maestro-ds/assets/icons/task.svg | 1 + design/maestro-ds/components/core/Button.d.ts | 16 + design/maestro-ds/components/core/Button.jsx | 31 + .../components/core/Button.prompt.md | 10 + .../components/core/ComplexityBadge.d.ts | 7 + .../components/core/ComplexityBadge.jsx | 17 + .../components/core/ComplexityBadge.prompt.md | 6 + .../components/core/CountBadge.d.ts | 11 + .../maestro-ds/components/core/CountBadge.jsx | 40 + .../components/core/CountBadge.prompt.md | 9 + .../components/core/QuotaMeter.d.ts | 15 + .../maestro-ds/components/core/QuotaMeter.jsx | 24 + .../components/core/QuotaMeter.prompt.md | 9 + .../components/core/SectionHead.d.ts | 14 + .../components/core/SectionHead.jsx | 17 + .../components/core/SectionHead.prompt.md | 6 + .../components/core/StatusChip.d.ts | 12 + .../maestro-ds/components/core/StatusChip.jsx | 46 + .../components/core/StatusChip.prompt.md | 9 + .../maestro-ds/components/core/core.card.html | 53 + .../components/forms/ComplexitySeg.d.ts | 16 + .../components/forms/ComplexitySeg.jsx | 32 + .../components/forms/ComplexitySeg.prompt.md | 11 + design/maestro-ds/components/forms/Input.d.ts | 17 + design/maestro-ds/components/forms/Input.jsx | 28 + .../components/forms/Input.prompt.md | 6 + .../maestro-ds/components/forms/Select.d.ts | 13 + design/maestro-ds/components/forms/Select.jsx | 69 + .../components/forms/Select.prompt.md | 8 + .../maestro-ds/components/forms/Textarea.d.ts | 16 + .../maestro-ds/components/forms/Textarea.jsx | 17 + .../components/forms/Textarea.prompt.md | 6 + .../components/forms/forms.card.html | 27 + .../components/surfaces/EventItem.d.ts | 14 + .../components/surfaces/EventItem.jsx | 38 + .../components/surfaces/EventItem.prompt.md | 8 + .../components/surfaces/GateCard.d.ts | 33 + .../components/surfaces/GateCard.jsx | 61 + .../components/surfaces/GateCard.prompt.md | 10 + .../maestro-ds/components/surfaces/Panel.d.ts | 9 + .../maestro-ds/components/surfaces/Panel.jsx | 10 + .../components/surfaces/Panel.prompt.md | 5 + .../components/surfaces/Timeline.d.ts | 18 + .../components/surfaces/Timeline.jsx | 28 + .../components/surfaces/Timeline.prompt.md | 9 + .../maestro-ds/components/surfaces/Toast.d.ts | 10 + .../maestro-ds/components/surfaces/Toast.jsx | 17 + .../components/surfaces/Toast.prompt.md | 6 + .../components/surfaces/surfaces.card.html | 46 + .../guidelines/brand-glyphs.card.html | 28 + .../guidelines/brand-logo.card.html | 64 + .../guidelines/brand-motion.card.html | 13 + .../guidelines/brand-texture.card.html | 11 + .../guidelines/colors-ink.card.html | 10 + .../guidelines/colors-light.card.html | 26 + .../guidelines/colors-signals.card.html | 52 + .../guidelines/colors-surfaces.card.html | 13 + .../guidelines/colors-tints.card.html | 13 + .../guidelines/spacing-borders.card.html | 11 + .../guidelines/spacing-glow.card.html | 10 + .../guidelines/spacing-radius.card.html | 11 + .../guidelines/spacing-scale.card.html | 14 + .../guidelines/type-family.card.html | 12 + .../guidelines/type-scale.card.html | 14 + .../guidelines/type-tracking.card.html | 13 + design/maestro-ds/readme.md | 79 + design/maestro-ds/styles.css | 4 + design/maestro-ds/tokens/colors.css | 81 + design/maestro-ds/tokens/effects.css | 45 + design/maestro-ds/tokens/fonts.css | 4 + design/maestro-ds/tokens/typography.css | 30 + .../ui_kits/console/ArchiveSection.jsx | 182 + .../maestro-ds/ui_kits/console/EventPanel.jsx | 45 + .../ui_kits/console/GateSection.jsx | 101 + design/maestro-ds/ui_kits/console/Sidebar.jsx | 397 ++ .../maestro-ds/ui_kits/console/TaskTree.jsx | 317 + design/maestro-ds/ui_kits/console/Topbar.jsx | 172 + design/maestro-ds/ui_kits/console/data.js | 99 + design/maestro-ds/ui_kits/console/i18n.js | 231 + design/maestro-ds/ui_kits/console/index.html | 183 + .../ui_kits/console_mobile/MobileChrome.jsx | 93 + .../ui_kits/console_mobile/MobileViews.jsx | 198 + .../ui_kits/console_mobile/index.html | 90 + src/api/server.ts | 3 + src/daemon/ingest.ts | 32 + src/store/store.ts | 8 + test/ingest.test.ts | 49 + todo/todo.html | 555 ++ 107 files changed, 10970 insertions(+) create mode 100644 design/maestro-ds/CLAUDE.md create mode 100644 design/maestro-ds/SKILL.md create mode 100644 design/maestro-ds/_adherence.oxlintrc.json create mode 100644 design/maestro-ds/_ds_bundle.js create mode 100644 design/maestro-ds/_ds_manifest.json create mode 100644 design/maestro-ds/assets/icons/add.svg create mode 100644 design/maestro-ds/assets/icons/close.svg create mode 100644 design/maestro-ds/assets/icons/config.svg create mode 100644 design/maestro-ds/assets/icons/deps.svg create mode 100644 design/maestro-ds/assets/icons/event.svg create mode 100644 design/maestro-ds/assets/icons/flow.svg create mode 100644 design/maestro-ds/assets/icons/gate.svg create mode 100644 design/maestro-ds/assets/icons/git.svg create mode 100644 design/maestro-ds/assets/icons/project.svg create mode 100644 design/maestro-ds/assets/icons/ready.svg create mode 100644 design/maestro-ds/assets/icons/reason.svg create mode 100644 design/maestro-ds/assets/icons/running.svg create mode 100644 design/maestro-ds/assets/icons/search.svg create mode 100644 design/maestro-ds/assets/icons/sync.svg create mode 100644 design/maestro-ds/assets/icons/task.svg create mode 100644 design/maestro-ds/components/core/Button.d.ts create mode 100644 design/maestro-ds/components/core/Button.jsx create mode 100644 design/maestro-ds/components/core/Button.prompt.md create mode 100644 design/maestro-ds/components/core/ComplexityBadge.d.ts create mode 100644 design/maestro-ds/components/core/ComplexityBadge.jsx create mode 100644 design/maestro-ds/components/core/ComplexityBadge.prompt.md create mode 100644 design/maestro-ds/components/core/CountBadge.d.ts create mode 100644 design/maestro-ds/components/core/CountBadge.jsx create mode 100644 design/maestro-ds/components/core/CountBadge.prompt.md create mode 100644 design/maestro-ds/components/core/QuotaMeter.d.ts create mode 100644 design/maestro-ds/components/core/QuotaMeter.jsx create mode 100644 design/maestro-ds/components/core/QuotaMeter.prompt.md create mode 100644 design/maestro-ds/components/core/SectionHead.d.ts create mode 100644 design/maestro-ds/components/core/SectionHead.jsx create mode 100644 design/maestro-ds/components/core/SectionHead.prompt.md create mode 100644 design/maestro-ds/components/core/StatusChip.d.ts create mode 100644 design/maestro-ds/components/core/StatusChip.jsx create mode 100644 design/maestro-ds/components/core/StatusChip.prompt.md create mode 100644 design/maestro-ds/components/core/core.card.html create mode 100644 design/maestro-ds/components/forms/ComplexitySeg.d.ts create mode 100644 design/maestro-ds/components/forms/ComplexitySeg.jsx create mode 100644 design/maestro-ds/components/forms/ComplexitySeg.prompt.md create mode 100644 design/maestro-ds/components/forms/Input.d.ts create mode 100644 design/maestro-ds/components/forms/Input.jsx create mode 100644 design/maestro-ds/components/forms/Input.prompt.md create mode 100644 design/maestro-ds/components/forms/Select.d.ts create mode 100644 design/maestro-ds/components/forms/Select.jsx create mode 100644 design/maestro-ds/components/forms/Select.prompt.md create mode 100644 design/maestro-ds/components/forms/Textarea.d.ts create mode 100644 design/maestro-ds/components/forms/Textarea.jsx create mode 100644 design/maestro-ds/components/forms/Textarea.prompt.md create mode 100644 design/maestro-ds/components/forms/forms.card.html create mode 100644 design/maestro-ds/components/surfaces/EventItem.d.ts create mode 100644 design/maestro-ds/components/surfaces/EventItem.jsx create mode 100644 design/maestro-ds/components/surfaces/EventItem.prompt.md create mode 100644 design/maestro-ds/components/surfaces/GateCard.d.ts create mode 100644 design/maestro-ds/components/surfaces/GateCard.jsx create mode 100644 design/maestro-ds/components/surfaces/GateCard.prompt.md create mode 100644 design/maestro-ds/components/surfaces/Panel.d.ts create mode 100644 design/maestro-ds/components/surfaces/Panel.jsx create mode 100644 design/maestro-ds/components/surfaces/Panel.prompt.md create mode 100644 design/maestro-ds/components/surfaces/Timeline.d.ts create mode 100644 design/maestro-ds/components/surfaces/Timeline.jsx create mode 100644 design/maestro-ds/components/surfaces/Timeline.prompt.md create mode 100644 design/maestro-ds/components/surfaces/Toast.d.ts create mode 100644 design/maestro-ds/components/surfaces/Toast.jsx create mode 100644 design/maestro-ds/components/surfaces/Toast.prompt.md create mode 100644 design/maestro-ds/components/surfaces/surfaces.card.html create mode 100644 design/maestro-ds/guidelines/brand-glyphs.card.html create mode 100644 design/maestro-ds/guidelines/brand-logo.card.html create mode 100644 design/maestro-ds/guidelines/brand-motion.card.html create mode 100644 design/maestro-ds/guidelines/brand-texture.card.html create mode 100644 design/maestro-ds/guidelines/colors-ink.card.html create mode 100644 design/maestro-ds/guidelines/colors-light.card.html create mode 100644 design/maestro-ds/guidelines/colors-signals.card.html create mode 100644 design/maestro-ds/guidelines/colors-surfaces.card.html create mode 100644 design/maestro-ds/guidelines/colors-tints.card.html create mode 100644 design/maestro-ds/guidelines/spacing-borders.card.html create mode 100644 design/maestro-ds/guidelines/spacing-glow.card.html create mode 100644 design/maestro-ds/guidelines/spacing-radius.card.html create mode 100644 design/maestro-ds/guidelines/spacing-scale.card.html create mode 100644 design/maestro-ds/guidelines/type-family.card.html create mode 100644 design/maestro-ds/guidelines/type-scale.card.html create mode 100644 design/maestro-ds/guidelines/type-tracking.card.html create mode 100644 design/maestro-ds/readme.md create mode 100644 design/maestro-ds/styles.css create mode 100644 design/maestro-ds/tokens/colors.css create mode 100644 design/maestro-ds/tokens/effects.css create mode 100644 design/maestro-ds/tokens/fonts.css create mode 100644 design/maestro-ds/tokens/typography.css create mode 100644 design/maestro-ds/ui_kits/console/ArchiveSection.jsx create mode 100644 design/maestro-ds/ui_kits/console/EventPanel.jsx create mode 100644 design/maestro-ds/ui_kits/console/GateSection.jsx create mode 100644 design/maestro-ds/ui_kits/console/Sidebar.jsx create mode 100644 design/maestro-ds/ui_kits/console/TaskTree.jsx create mode 100644 design/maestro-ds/ui_kits/console/Topbar.jsx create mode 100644 design/maestro-ds/ui_kits/console/data.js create mode 100644 design/maestro-ds/ui_kits/console/i18n.js create mode 100644 design/maestro-ds/ui_kits/console/index.html create mode 100644 design/maestro-ds/ui_kits/console_mobile/MobileChrome.jsx create mode 100644 design/maestro-ds/ui_kits/console_mobile/MobileViews.jsx create mode 100644 design/maestro-ds/ui_kits/console_mobile/index.html create mode 100644 todo/todo.html 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