From aa9a2230e5a375a8617efd3e0a4090f42f6d6f13 Mon Sep 17 00:00:00 2001 From: evanlowe <62918515+evanlowe@users.noreply.github.com> Date: Sat, 19 Sep 2026 21:51:27 +0800 Subject: [PATCH] feat(studio): refine component library organization and interactions --- frontend/README.md | 15 +- frontend/SPEC.md | 9 +- .../components-preview/ComponentsPreview.tsx | 235 ++++---- .../components-preview/PreviewContents.tsx | 86 +++ frontend/src/components-preview/README.md | 37 +- .../components-preview/api/ComponentApi.tsx | 16 +- .../components-preview/components-preview.css | 138 ++++- .../examples/AgentNodePreview.tsx | 9 +- .../examples/AppLayoutPreview.tsx | 5 +- .../examples/BasicNodePreview.tsx | 7 +- .../examples/ButtonPreview.css | 15 +- .../examples/ButtonPreview.tsx | 114 ++-- .../examples/ButtonsPreview.tsx | 2 - .../examples/CanvasBackgroundPreview.tsx | 9 +- .../examples/CardLayoutPreview.tsx | 4 +- .../examples/CardsPreview.tsx | 2 - .../examples/CheckboxPreview.tsx | 4 +- .../examples/CodeBlockPreview.tsx | 4 +- .../examples/ConversationFlowPreview.css | 2 +- .../examples/ConversationFlowPreview.tsx | 20 +- .../examples/DashedZonePreview.tsx | 7 +- .../examples/DatePickerPreview.tsx | 14 +- .../examples/DetailPageLayoutPreview.tsx | 4 +- .../examples/DividerPreview.tsx | 4 +- .../examples/DrawerPreview.tsx | 5 +- .../examples/DropdownPreview.tsx | 4 +- .../examples/EmptyStatePreview.tsx | 10 +- .../examples/ErrorStatePreview.tsx | 5 +- .../examples/FileExplorerPreview.tsx | 10 +- .../examples/FileUploadPreview.tsx | 12 +- .../examples/FilterTabsPreview.tsx | 4 +- .../examples/FormFieldPreview.tsx | 9 +- .../examples/FormLabelPreview.tsx | 24 +- .../examples/FormLabelRowPreview.tsx | 8 + .../examples/GlassIconButtonGroupPreview.tsx | 4 +- .../examples/GlassIconButtonPreview.tsx | 4 +- .../examples/GlassTabsPreview.tsx | 8 +- .../examples/HeaderPreview.css | 5 + .../examples/HeaderPreview.tsx | 19 +- .../examples/IndexLayoutPreview.tsx | 5 +- .../examples/InfoCardPreview.tsx | 4 +- .../examples/InputPreview.tsx | 9 + .../examples/InputWithHeaderIconPreview.tsx | 5 +- .../examples/InputWithTailIconPreview.tsx | 2 +- .../examples/ItemPreview.tsx | 7 +- .../examples/LabelPreview.tsx | 19 +- .../examples/LoadMoreAreaPreview.tsx | 5 +- .../examples/LoadingPreview.css | 8 +- .../examples/LoadingPreview.tsx | 10 +- .../examples/LongRunningStatePreview.css | 23 +- .../examples/LongRunningStatePreview.tsx | 50 +- .../examples/MenuPreview.tsx | 30 +- .../examples/ModalButtonPreview.tsx | 5 +- .../examples/ModalLayoutPreview.tsx | 4 +- .../examples/PillTabsPreview.tsx | 8 +- .../examples/PillTagPreview.tsx | 4 +- .../examples/PromoCardPreview.tsx | 7 +- .../examples/PromptInputPreview.tsx | 15 +- .../examples/RadioPreview.tsx | 7 +- .../examples/ResourceCardPreview.tsx | 7 +- .../examples/ResourcePageLayoutPreview.css | 7 +- .../examples/ResourcePageLayoutPreview.tsx | 10 +- .../examples/ScrollAreaPreview.tsx | 12 +- .../examples/SelectPreview.tsx | 6 +- .../examples/SidebarPreview.tsx | 12 +- .../examples/SliderPreview.tsx | 15 +- .../examples/SwitchPreview.tsx | 4 +- .../examples/TableHeaderControlsPreview.tsx | 2 +- .../examples/TablePreview.tsx | 7 +- .../examples/TextareaPreview.tsx | 4 +- .../examples/ToastPreview.css | 2 +- .../examples/ToastPreview.tsx | 13 +- .../examples/TokensPreview.css | 2 +- .../examples/TokensPreview.tsx | 7 +- .../examples/UnderlineTabsPreview.tsx | 6 +- .../foundation/Specification.md | 88 +++ .../foundation/SpecificationPreview.css | 23 + .../foundation/SpecificationPreview.tsx | 42 ++ frontend/src/components-preview/navigation.ts | 27 + .../ConversationFlow/ConversationBlocks.tsx | 9 +- .../ConversationFlow/ConversationFlow.css | 44 +- .../ConversationFlow/ConversationFlow.tsx | 7 +- .../ConversationRichContent.css | 2 - .../ConversationStepEntrance.tsx | 77 +++ .../ai-app/ConversationFlow/README.md | 2 + .../composites/CodeBlock/CodeBlock.css | 11 - .../composites/CodeBlock/CodeBlock.tsx | 10 +- .../composites/FormLabelRow/FormLabelRow.css | 13 +- .../composites/FormLabelRow/FormLabelRow.tsx | 9 +- .../components/composites/Header/Header.css | 9 - .../components/composites/Header/Header.tsx | 1 + .../LongRunningState/LongRunningState.css | 264 ++------- .../LongRunningState/LongRunningState.tsx | 252 ++------- .../composites/LongRunningState/README.md | 51 +- .../components/primitives/Button/Button.css | 110 ++-- .../components/primitives/Button/Button.tsx | 4 +- .../primitives/DatePicker/DatePicker.css | 2 - .../primitives/DatePicker/DatePicker.tsx | 2 +- .../components/primitives/Loading/Loading.tsx | 15 +- frontend/src/components/tokens/theme.css | 8 +- frontend/src/components/tokens/tokens.ts | 8 +- frontend/tests/conversationFlow.test.tsx | 60 +++ frontend/tests/longRunningState.test.tsx | 131 ++--- .../{index-BRhKl68A.js => index-CJviP90a.js} | 506 +++++++++--------- ...BUUV54ep.js => CodeDiffEditor-s8hBhMMg.js} | 2 +- ...2DfS.js => FileExplorerEditor-CFdSh-rV.js} | 2 +- ...0u.js => MarkdownPromptEditor-CzsU5wfw.js} | 2 +- ....js => QuickAgentCreateDialog-DPeLFn25.js} | 2 +- .../{arc-B_nCGVGZ.js => arc-DbRAgn4v.js} | 2 +- ...hannel-CtLIg3Cq.js => channel-CMu2nD1d.js} | 2 +- ...ex.es-CsLwbSjL.js => index.es-RLQOD81J.js} | 2 +- ...n-7JeyXssI.js => jspdf.es.min-Bfqi7g1m.js} | 6 +- ...{linear-Bd0gp8ms.js => linear-tbDTDhU7.js} | 2 +- ...{index-B-Mv2mK2.css => index-BD-gRaai.css} | 4 +- ...XW.js => abnfDiagram-N423BO3Z-BaiVCyla.js} | 2 +- ... architectureDiagram-T3A2C74G-CxqE4ID3.js} | 2 +- ...1.js => blockDiagram-VBNYF7ZC-VKFCsK0p.js} | 2 +- ...oA7z.js => c4Diagram-5PPSVZJV-B3f7URa9.js} | 2 +- ...D5yhknlg.js => chunk-2GRJ4B5K-C-Qd5HYM.js} | 2 +- ...CyccXa7d.js => chunk-2Q5K7J3B-Cuc5ie7-.js} | 2 +- ...CBgp1FHA.js => chunk-5RXB4S5H-DkAcAzhi.js} | 2 +- ...vUjcD3vu.js => chunk-5VM5RSS4-B3rPasDn.js} | 2 +- ...j9aiRH_7.js => chunk-6Q2QTUOP-BxvHVKr0.js} | 2 +- ...BTSZzrkC.js => chunk-GF5L2VYU-DxVoovyk.js} | 2 +- ...BB3xX8QT.js => chunk-JWPE2WC7-BZ3X4qaY.js} | 2 +- ...BMo0zsm5.js => chunk-KBJHAD2P-CjQn-4mK.js} | 2 +- ...CywjY4-3.js => chunk-RYQCIY6F-CGpD7Uj6.js} | 2 +- ...DhKwTljB.js => chunk-XXDRQBXY-sABBpUdy.js} | 2 +- .../mermaid/classDiagram-JCYQIIEL-BUtjNsWs.js | 1 + .../mermaid/classDiagram-JCYQIIEL-CYAXibHo.js | 1 - .../classDiagram-v2-OCEON4UE-BUtjNsWs.js | 1 + .../classDiagram-v2-OCEON4UE-CYAXibHo.js | 1 - ...l.js => cose-bilkent-JH36ORCC-CB1m4BoC.js} | 2 +- ...klTWXi.js => cynefin-OW5HDTMX-Ckz3rKt1.js} | 2 +- ...js => cynefinDiagram-MW4NZA55-B8TWbKqb.js} | 2 +- ...D4MZxk-p.js => dagre-VZM6K2ZE-jG98pt9I.js} | 2 +- ...yMfVa-.js => diagram-7IWD3JNH-DZ76pD-Q.js} | 2 +- ...7AagsF.js => diagram-B4RE2ZJO-rB1S1yfJ.js} | 2 +- ...hN9cTh.js => diagram-LBJQPF4R-CrDpozB-.js} | 2 +- ...6z702U.js => diagram-Q27KOJAE-UDl8YDCB.js} | 2 +- ...KVBGmh.js => diagram-UB23O5K3-Cn76qpdV.js} | 2 +- ...2N.js => ebnfDiagram-BXEA7PRR-18Vh_BWX.js} | 2 +- ...fDi4.js => erDiagram-JOGREHBK-Cy7w0v1h.js} | 2 +- ...Ip.js => flowDiagram-UKHOOZJN-DZkAHgmK.js} | 2 +- ...8.js => ganttDiagram-PKOTCBZU-COvFwH2O.js} | 2 +- ...s => gitGraphDiagram-DS77QQ5N-Bn7ckgBg.js} | 2 +- ...VQ.js => infoDiagram-6WML65LV-BGQQjDep.js} | 2 +- ...s => ishikawaDiagram-WSZJBQD7-DVDLfh5B.js} | 2 +- ...js => journeyDiagram-NVQOT4AX-C65sCAhe.js} | 2 +- ...=> kanban-definition-27J2QSJJ-BTbzJ_ZQ.js} | 2 +- ...e-BpZvGNH3.js => mermaid.core-DeGzkuWe.js} | 8 +- ...> mindmap-definition-FAOFIHXS-DUAns_22.js} | 2 +- ...ZXv.js => pegDiagram-VL7TDLO6-jG4c0tDl.js} | 2 +- ...w4N.js => pieDiagram-7S7Q4E2Y-CsT4L9vZ.js} | 2 +- ...s => quadrantDiagram-CIZ2JOQS-BTnoKIrq.js} | 2 +- ...s => railroadDiagram-AXF67PYL-B8dHCT_8.js} | 2 +- ...> requirementDiagram-LRYGKXZP-CJhYPk23.js} | 2 +- ....js => sankeyDiagram-W5VNT64P-DemPr4LT.js} | 2 +- ...s => sequenceDiagram-SI44F4Z6-s4R2WnG5.js} | 2 +- ...15.js => sizeCapture-X5ZJPWSS-CHGscOn5.js} | 2 +- ...h.js => stateDiagram-OKZ733FA-BiXkLbKS.js} | 2 +- .../stateDiagram-v2-UEYNNEHI-CVc3nAiM.js | 1 + .../stateDiagram-v2-UEYNNEHI-Cdnedrlb.js | 1 - ...hzwE.js => swimlanes-SLNWSIFB-29nR2vPa.js} | 4 +- .../swimlanesDiagram-ULZ7WXOC-D6zY6Lub.js | 8 - .../swimlanesDiagram-ULZ7WXOC-oPtzMUJA.js | 8 + ... timeline-definition-Z64GVDOM-b5AcyL5V.js} | 2 +- ...bu.js => vennDiagram-T6HMQDX7-K3fkHwPY.js} | 2 +- ...js => wardleyDiagram-T6FBY63Y-CU_6HeHe.js} | 2 +- ...js => xychartDiagram-ELKLHX3M-DyeSCJVl.js} | 2 +- veadk/webui/index.html | 4 +- 171 files changed, 1674 insertions(+), 1505 deletions(-) create mode 100644 frontend/src/components-preview/PreviewContents.tsx create mode 100644 frontend/src/components-preview/examples/FormLabelRowPreview.tsx create mode 100644 frontend/src/components-preview/examples/InputPreview.tsx create mode 100644 frontend/src/components-preview/foundation/Specification.md create mode 100644 frontend/src/components-preview/foundation/SpecificationPreview.css create mode 100644 frontend/src/components-preview/foundation/SpecificationPreview.tsx create mode 100644 frontend/src/components-preview/navigation.ts create mode 100644 frontend/src/components/ai-app/ConversationFlow/ConversationStepEntrance.tsx rename veadk/webui/assets/app/{index-BRhKl68A.js => index-CJviP90a.js} (82%) rename veadk/webui/assets/chunks/{CodeDiffEditor-BUUV54ep.js => CodeDiffEditor-s8hBhMMg.js} (99%) rename veadk/webui/assets/chunks/{FileExplorerEditor-alc62DfS.js => FileExplorerEditor-CFdSh-rV.js} (96%) rename veadk/webui/assets/chunks/{MarkdownPromptEditor-BPvUcz0u.js => MarkdownPromptEditor-CzsU5wfw.js} (99%) rename veadk/webui/assets/chunks/{QuickAgentCreateDialog-DXGVTm8f.js => QuickAgentCreateDialog-DPeLFn25.js} (97%) rename veadk/webui/assets/chunks/{arc-B_nCGVGZ.js => arc-DbRAgn4v.js} (98%) rename veadk/webui/assets/chunks/{channel-CtLIg3Cq.js => channel-CMu2nD1d.js} (53%) rename veadk/webui/assets/chunks/{index.es-CsLwbSjL.js => index.es-RLQOD81J.js} (99%) rename veadk/webui/assets/chunks/{jspdf.es.min-7JeyXssI.js => jspdf.es.min-Bfqi7g1m.js} (99%) rename veadk/webui/assets/chunks/{linear-Bd0gp8ms.js => linear-tbDTDhU7.js} (98%) rename veadk/webui/assets/styles/{index-B-Mv2mK2.css => index-BD-gRaai.css} (52%) rename veadk/webui/assets/visualizations/mermaid/{abnfDiagram-N423BO3Z-Ckp_oCXW.js => abnfDiagram-N423BO3Z-BaiVCyla.js} (86%) rename veadk/webui/assets/visualizations/mermaid/{architectureDiagram-T3A2C74G-5LJW4V8K.js => architectureDiagram-T3A2C74G-CxqE4ID3.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{blockDiagram-VBNYF7ZC-ATNReLr1.js => blockDiagram-VBNYF7ZC-VKFCsK0p.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{c4Diagram-5PPSVZJV-DtdQoA7z.js => c4Diagram-5PPSVZJV-B3f7URa9.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{chunk-2GRJ4B5K-D5yhknlg.js => chunk-2GRJ4B5K-C-Qd5HYM.js} (93%) rename veadk/webui/assets/visualizations/mermaid/{chunk-2Q5K7J3B-CyccXa7d.js => chunk-2Q5K7J3B-Cuc5ie7-.js} (66%) rename veadk/webui/assets/visualizations/mermaid/{chunk-5RXB4S5H-CBgp1FHA.js => chunk-5RXB4S5H-DkAcAzhi.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{chunk-5VM5RSS4-vUjcD3vu.js => chunk-5VM5RSS4-B3rPasDn.js} (83%) rename veadk/webui/assets/visualizations/mermaid/{chunk-6Q2QTUOP-j9aiRH_7.js => chunk-6Q2QTUOP-BxvHVKr0.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{chunk-GF5L2VYU-BTSZzrkC.js => chunk-GF5L2VYU-DxVoovyk.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{chunk-JWPE2WC7-BB3xX8QT.js => chunk-JWPE2WC7-BZ3X4qaY.js} (78%) rename veadk/webui/assets/visualizations/mermaid/{chunk-KBJHAD2P-BMo0zsm5.js => chunk-KBJHAD2P-CjQn-4mK.js} (87%) rename veadk/webui/assets/visualizations/mermaid/{chunk-RYQCIY6F-CywjY4-3.js => chunk-RYQCIY6F-CGpD7Uj6.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{chunk-XXDRQBXY-DhKwTljB.js => chunk-XXDRQBXY-sABBpUdy.js} (53%) create mode 100644 veadk/webui/assets/visualizations/mermaid/classDiagram-JCYQIIEL-BUtjNsWs.js delete mode 100644 veadk/webui/assets/visualizations/mermaid/classDiagram-JCYQIIEL-CYAXibHo.js create mode 100644 veadk/webui/assets/visualizations/mermaid/classDiagram-v2-OCEON4UE-BUtjNsWs.js delete mode 100644 veadk/webui/assets/visualizations/mermaid/classDiagram-v2-OCEON4UE-CYAXibHo.js rename veadk/webui/assets/visualizations/mermaid/{cose-bilkent-JH36ORCC-r5ofRJEl.js => cose-bilkent-JH36ORCC-CB1m4BoC.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{cynefin-OW5HDTMX-D8klTWXi.js => cynefin-OW5HDTMX-Ckz3rKt1.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{cynefinDiagram-MW4NZA55-Dyesmk9p.js => cynefinDiagram-MW4NZA55-B8TWbKqb.js} (98%) rename veadk/webui/assets/visualizations/mermaid/{dagre-VZM6K2ZE-D4MZxk-p.js => dagre-VZM6K2ZE-jG98pt9I.js} (97%) rename veadk/webui/assets/visualizations/mermaid/{diagram-7IWD3JNH-BYyMfVa-.js => diagram-7IWD3JNH-DZ76pD-Q.js} (96%) rename veadk/webui/assets/visualizations/mermaid/{diagram-B4RE2ZJO-217AagsF.js => diagram-B4RE2ZJO-rB1S1yfJ.js} (97%) rename veadk/webui/assets/visualizations/mermaid/{diagram-LBJQPF4R-CphN9cTh.js => diagram-LBJQPF4R-CrDpozB-.js} (94%) rename veadk/webui/assets/visualizations/mermaid/{diagram-Q27KOJAE-C96z702U.js => diagram-Q27KOJAE-UDl8YDCB.js} (98%) rename veadk/webui/assets/visualizations/mermaid/{diagram-UB23O5K3-BvKVBGmh.js => diagram-UB23O5K3-Cn76qpdV.js} (95%) rename veadk/webui/assets/visualizations/mermaid/{ebnfDiagram-BXEA7PRR-Bw-Ku-2N.js => ebnfDiagram-BXEA7PRR-18Vh_BWX.js} (87%) rename veadk/webui/assets/visualizations/mermaid/{erDiagram-JOGREHBK-VHa0fDi4.js => erDiagram-JOGREHBK-Cy7w0v1h.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{flowDiagram-UKHOOZJN-Hmo_eBIp.js => flowDiagram-UKHOOZJN-DZkAHgmK.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{ganttDiagram-PKOTCBZU-C2tlUiE8.js => ganttDiagram-PKOTCBZU-COvFwH2O.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{gitGraphDiagram-DS77QQ5N-BithI0OM.js => gitGraphDiagram-DS77QQ5N-Bn7ckgBg.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{infoDiagram-6WML65LV-CHF-grVQ.js => infoDiagram-6WML65LV-BGQQjDep.js} (69%) rename veadk/webui/assets/visualizations/mermaid/{ishikawaDiagram-WSZJBQD7-CdjLRB_o.js => ishikawaDiagram-WSZJBQD7-DVDLfh5B.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{journeyDiagram-NVQOT4AX-DCW6rG8w.js => journeyDiagram-NVQOT4AX-C65sCAhe.js} (98%) rename veadk/webui/assets/visualizations/mermaid/{kanban-definition-27J2QSJJ-DZ2j5ah6.js => kanban-definition-27J2QSJJ-BTbzJ_ZQ.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{mermaid.core-BpZvGNH3.js => mermaid.core-DeGzkuWe.js} (98%) rename veadk/webui/assets/visualizations/mermaid/{mindmap-definition-FAOFIHXS-DJNFDpV4.js => mindmap-definition-FAOFIHXS-DUAns_22.js} (98%) rename veadk/webui/assets/visualizations/mermaid/{pegDiagram-VL7TDLO6-jmRW3ZXv.js => pegDiagram-VL7TDLO6-jG4c0tDl.js} (87%) rename veadk/webui/assets/visualizations/mermaid/{pieDiagram-7S7Q4E2Y-DUz85w4N.js => pieDiagram-7S7Q4E2Y-CsT4L9vZ.js} (96%) rename veadk/webui/assets/visualizations/mermaid/{quadrantDiagram-CIZ2JOQS-DqE3BBQF.js => quadrantDiagram-CIZ2JOQS-BTnoKIrq.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{railroadDiagram-AXF67PYL-Bp91GmHk.js => railroadDiagram-AXF67PYL-B8dHCT_8.js} (84%) rename veadk/webui/assets/visualizations/mermaid/{requirementDiagram-LRYGKXZP-SIp-PyW1.js => requirementDiagram-LRYGKXZP-CJhYPk23.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{sankeyDiagram-W5VNT64P-B0ngoQEy.js => sankeyDiagram-W5VNT64P-DemPr4LT.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{sequenceDiagram-SI44F4Z6-DERdyOzt.js => sequenceDiagram-SI44F4Z6-s4R2WnG5.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{sizeCapture-X5ZJPWSS-BWMHss15.js => sizeCapture-X5ZJPWSS-CHGscOn5.js} (87%) rename veadk/webui/assets/visualizations/mermaid/{stateDiagram-OKZ733FA-CvIaU_Sh.js => stateDiagram-OKZ733FA-BiXkLbKS.js} (96%) create mode 100644 veadk/webui/assets/visualizations/mermaid/stateDiagram-v2-UEYNNEHI-CVc3nAiM.js delete mode 100644 veadk/webui/assets/visualizations/mermaid/stateDiagram-v2-UEYNNEHI-Cdnedrlb.js rename veadk/webui/assets/visualizations/mermaid/{swimlanes-SLNWSIFB-Dre9hzwE.js => swimlanes-SLNWSIFB-29nR2vPa.js} (99%) delete mode 100644 veadk/webui/assets/visualizations/mermaid/swimlanesDiagram-ULZ7WXOC-D6zY6Lub.js create mode 100644 veadk/webui/assets/visualizations/mermaid/swimlanesDiagram-ULZ7WXOC-oPtzMUJA.js rename veadk/webui/assets/visualizations/mermaid/{timeline-definition-Z64GVDOM-BkyKsLGP.js => timeline-definition-Z64GVDOM-b5AcyL5V.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{vennDiagram-T6HMQDX7-C9EQJFbu.js => vennDiagram-T6HMQDX7-K3fkHwPY.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{wardleyDiagram-T6FBY63Y-CGZx944j.js => wardleyDiagram-T6FBY63Y-CU_6HeHe.js} (99%) rename veadk/webui/assets/visualizations/mermaid/{xychartDiagram-ELKLHX3M-CRznHJCd.js => xychartDiagram-ELKLHX3M-DyeSCJVl.js} (99%) diff --git a/frontend/README.md b/frontend/README.md index 2e7200bbd..15881ace9 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1473,20 +1473,27 @@ AI APP 新增 [ConversationFlow](src/components/ai-app/ConversationFlow/README.m 共享组件目录见 [组件库说明](src/components/README.md),预览目录见 [Components Preview](src/components-preview/README.md) -运行 `npm run dev:components` 打开独立组件预览页,按基础组件、复合组件、布局、节点组件和 AI APP 分组浏览 Figma 组件 +运行 `npm run dev:components` 打开独立组件预览页,按 Foundation、Base、Block、AI App、Node、Layout 分组,组内按组件名称排序 +Foundation 下的 Specification 展示用户确认的前端开发规则,支持复制规范;内容与 AI 可读取的 [Specification.md](src/components-preview/foundation/Specification.md) 保持同一来源 +组件名称使用一级标题,变种使用二级标题;Input 的前后图标变种合并展示,参数表统一在底部,可通过页内目录直接跳转 +Form Label Row 和 Glass Icon Button Group 独立归入 Block,Card Layout 归入 Layout,原有页面与小节链接继续可用 +Progress 对应 LongRunningState,Text 和 Code 两种详情形式分别在二级标题下展示,支持 `#progress` 与原有 `#long-running-state` 链接 +窄屏提供可展开的组件目录和本页目录,原有组件链接继续可用 预览包含可复用控件、完整 Radio 卡片、表格、页面布局和 Prompt Input,提供持久化明暗主题、语义 Token 展示及从 TypeScript 接口生成的参数表 主次按钮默认等高,Select 支持选项副标题,Prompt Input 支持提示词列表轮播;组件交互与使用约定见预览说明 Menu 提供文字与箭头触发的面板菜单,支持分组、多级子菜单及可选图标,预览中可查看交互示例与参数表 Toast 提供四种状态和自定义操作,通过 ToastProvider 与 useToast 管理堆叠、自动关闭及悬停暂停,继承明暗主题 -Loading 提供无限路径与圆环两种加载图形,ScrollArea 和卡片触底加载复用无限路径,支持明暗主题与减少动态效果偏好 +Loading 提供无限路径(默认 32 × 16px)与圆环(默认 20 × 20px)两种加载图形,ScrollArea 和卡片触底加载复用无限路径,支持明暗主题与减少动态效果偏好 EmptyState 支持圆形背景内的 24px 图标、标题与详细说明,以及复用 Button 的横排操作组;ErrorState 使用相同尺寸的红色断链图标且无按钮,两者均可在基础组件预览中查看 +Button 各样式并排展示 Compact、Default、Large,按钮底端对齐,尺寸说明在下方;文字按钮高度为 28 / 32 / 36px、字号为 12 / 14 / 16px,图标随字号缩放,Pill 使用同一套尺寸 Button 支持 loading,加载时自动禁用且只显示 Ring 图标,保留按钮尺寸与无障碍名称 -LongRunningState 使用居中的双栏工作区,左侧展示任务进度与可回看的已完成步骤,右侧复用 Drawer 的明暗主题玻璃材质与 ScrollArea 展示详情;查看历史不会改变执行进度,可返回当前步骤继续跟随,详情即时替换并短暂淡入,代码日志复用 CodeBlock 高亮并支持隐藏行号 +LongRunningState 整体左对齐,依次展示正常字号的当前进度名称、快速循环滑动的加载条和无背景 ScrollArea;名称切换时向上渐隐再滑入下一名称,完成后停止动画并保留结果,支持减少动态效果偏好 +ConversationFlow 新增步骤先展开空间再渐显内容,状态更新不重复进场,减少动态效果时直接显示 资源页首次加载统一复用 Infinity Path,详情布局不包含 Sidebar ModalButton 复用 ModalLayout 并提供遮罩和进出动效;Drawer 以留有屏幕边距的浮动卡片打开;FileExplorer 组合文件树、CodeBlock 与 ScrollArea 展示文件内容 FileExplorer 按文件名显示常用文件图标并选择高亮语言,file.language 可覆盖,支持自动格式化、折行及可选编辑保存;CodeBlock 支持自动语法高亮、手动颜色 token 和纯文本,复制保留传入文本 Drawer 默认提供毛玻璃背景;FileUpload 复用 DashedZone 并提供文件选择与校验,Slider 支持原生拖动和键盘调整 -DatePicker 在基础组件中展示日期与日期时间选择;IndexLayout 在布局分组中展示首页;侧边栏会话示例位于复合组件的 Sidebar / 会话 +DatePicker 在基础组件中展示日期与日期时间选择;IndexLayout 在布局分组中展示首页;侧边栏会话示例位于 Block 分组的 Sidebar ### 组件库侧栏预览 diff --git a/frontend/SPEC.md b/frontend/SPEC.md index 10989a131..232376a96 100644 --- a/frontend/SPEC.md +++ b/frontend/SPEC.md @@ -3,6 +3,9 @@ 本文档是 AgentKit Studio 前端及其配套后端接口的 AI 开发规范。AI 在新增功能、 修复问题、生成代码或审查 PR 时,必须遵守本文档。 +前端开发还必须阅读 [Foundation / Specification](src/components-preview/foundation/Specification.md) +该文档包含用户确认的开发规则,也是组件库 Specification 页的唯一内容来源;本文件其他条款与之冲突时,以该文档中的规则为准 + 规范中的关键词含义如下: - **必须**:不可省略;不满足时不得合入。 @@ -120,8 +123,8 @@ hsl(var(--destructive)) ### 3.2 字体与文字 - 继续使用当前中英文系统字体栈,不得为单个页面引入网络字体。 -- 非代码界面默认继承系统字体。只有源代码、命令、终端输出、日志、文件路径和结构化 - 配置片段可以使用等宽字体。 +- 界面默认继承现有 UI 字体,没有用户明确的特殊说明时禁止使用代码字体或等宽字体 + 不因内容是源代码、命令、日志、路径或技术字段自动视为例外;用户明确指定后仅在指定区域使用 - 版本号、ID、模型名、Agent 名、Runtime 名、Badge、按钮、标题和普通技术字段不得仅因 “看起来技术化”就使用等宽字体。 - 正文通常为 `12.5px` 至 `14px`,页面标题和关键标题按现有组件层级递增。 @@ -578,7 +581,7 @@ AI 每次开发任务必须按以下顺序执行: - [ ] 没有新增通用图标库用法、Emoji 图标或远程图标。 - [ ] 使用现有颜色、间距、圆角和状态模式。 - [ ] 同一页面及同级面板的左右、上下边缘已按共享锚点完整对齐。 -- [ ] 非代码信息未使用等宽字体,且没有无必要的副标题、小标题或品牌小字。 +- [ ] 未经用户明确指定的区域没有使用等宽字体,且没有无必要的副标题、小标题或品牌小字 - [ ] 新增下拉、分段 Tab、表单或对话框已与对应基准组件逐项对齐。 - [ ] 没有复制一套近似 CSS;第二个同类组件已复用或提取共享展示行为。 - [ ] 键盘、IME、焦点和无障碍属性完整。 diff --git a/frontend/src/components-preview/ComponentsPreview.tsx b/frontend/src/components-preview/ComponentsPreview.tsx index 1aeb81342..96a91f10c 100644 --- a/frontend/src/components-preview/ComponentsPreview.tsx +++ b/frontend/src/components-preview/ComponentsPreview.tsx @@ -9,132 +9,175 @@ import { SliderPreview } from "./examples/SliderPreview"; import { IndexLayoutPreview } from "./examples/IndexLayoutPreview"; import { DatePickerPreview } from "./examples/DatePickerPreview"; import { LoadingPreview } from "./examples/LoadingPreview"; -import { ToastPreview } from "./examples/ToastPreview"; +import { ToastPreview, ToastHookApi } from "./examples/ToastPreview"; import { TablePreview } from "./examples/TablePreview"; import { RadioPreview } from "./examples/RadioPreview"; -import { InputWithHeaderIconPreview } from "./examples/InputWithHeaderIconPreview"; +import { InputPreview } from "./examples/InputPreview"; import { LoadMoreAreaPreview } from "./examples/LoadMoreAreaPreview"; import { ScrollArea } from "../components/primitives/ScrollArea"; import { ScrollAreaPreview } from "./examples/ScrollAreaPreview"; import { DividerPreview } from "./examples/DividerPreview"; import { DropdownPreview } from "./examples/DropdownPreview"; -import { MenuPreview } from "./examples/MenuPreview"; +import { MenuPreview, MenuEntryApi } from "./examples/MenuPreview"; import { PromptInputPreview } from "./examples/PromptInputPreview"; import { ConversationFlowPreview } from "./examples/ConversationFlowPreview"; -import { useSyncExternalStore } from "react"; -import { ButtonsPreview, CardsPreview, TabsPreview, InputWithTailIconPreview, ItemPreview, LabelPreview, SidebarPreview, SwitchPreview, CheckboxPreview, FormFieldPreview, DashedZonePreview, CodeBlockPreview, ModalLayoutPreview, HeaderPreview, PillTagPreview, FormLabelPreview, ResourcePageLayoutPreview, DetailPageLayoutPreview, TextareaPreview, SelectPreview, BasicNodePreview, AgentNodePreview, CanvasBackgroundPreview, TokensPreview } from "./examples"; +import { memo, useEffect, useRef, useState, useSyncExternalStore, type ComponentType } from "react"; +import { ComponentApi } from "./api/ComponentApi"; +import { PreviewContents } from "./PreviewContents"; +import { resolvePreviewLocation } from "./navigation"; +import { ButtonsPreview, CardsPreview, TabsPreview, ItemPreview, LabelPreview, SidebarPreview, SwitchPreview, CheckboxPreview, FormFieldPreview, DashedZonePreview, CodeBlockPreview, ModalLayoutPreview, HeaderPreview, PillTagPreview, FormLabelPreview, ResourcePageLayoutPreview, DetailPageLayoutPreview, TextareaPreview, SelectPreview, BasicNodePreview, AgentNodePreview, CanvasBackgroundPreview, TokensPreview } from "./examples"; import { useStudioTheme } from "../components/tokens/theme"; import { AppLayoutPreview } from "./examples/AppLayoutPreview"; +import { FormLabelRowPreview } from "./examples/FormLabelRowPreview"; +import { GlassIconButtonGroupPreview } from "./examples/GlassIconButtonGroupPreview"; +import { CardLayoutPreview } from "./examples/CardLayoutPreview"; +import { SpecificationPreview } from "./foundation/SpecificationPreview"; import "./components-preview.css"; -const examples = [ - { id: "app-layout", label: "App layout / 完整侧栏", Preview: AppLayoutPreview, group: "layouts" }, - { id: "date-picker", label: "Date Picker", Preview: DatePickerPreview, group: "primitives" }, - { id: "index-layout", label: "Index layout", Preview: IndexLayoutPreview, group: "layouts" }, - { id: "slider", label: "Slider", Preview: SliderPreview, group: "primitives" }, - { id: "file-upload", label: "File Upload", Preview: FileUploadPreview, group: "composites" }, - { id: "modal-button", label: "Modal Button", Preview: ModalButtonPreview, group: "composites" }, - { id: "drawer", label: "Drawer", Preview: DrawerPreview, group: "composites" }, - { id: "file-explorer", label: "File Explorer", Preview: FileExplorerPreview, group: "composites" }, - { id: "long-running-state", label: "Long-running State", Preview: LongRunningStatePreview, group: "composites" }, - { id: "empty-state", label: "Empty State", Preview: EmptyStatePreview, group: "primitives" }, - { id: "error-state", label: "Error State", Preview: ErrorStatePreview, group: "primitives" }, - { id: "loading", label: "Loading", Preview: LoadingPreview, group: "primitives" }, - { id: "toast", label: "Toast", Preview: ToastPreview, group: "primitives" }, - { id: "table", label: "Table", Preview: TablePreview, group: "primitives" }, - { id: "load-more", label: "下拉加载", Preview: LoadMoreAreaPreview, group: "composites" }, - { id: "scroll-area", label: "Scroll Area", Preview: ScrollAreaPreview, group: "primitives" }, - { id: "divider", label: "分割线", Preview: DividerPreview, group: "primitives" }, - { id: "dropdown", label: "Dropdown", Preview: DropdownPreview, group: "primitives" }, - { id: "prompt-input", label: "Prompt Input", Preview: PromptInputPreview, group: "ai-app" }, - { id: "conversation-flow", label: "Conversation Flow / 对话流", Preview: ConversationFlowPreview, group: "ai-app" }, - { id: "tokens", label: "Tokens", Preview: TokensPreview, group: "design" }, - { id: "canvas-background", label: "画布背景", Preview: CanvasBackgroundPreview, group: "nodes" }, - { id: "agent-node", label: "Agent 节点", Preview: AgentNodePreview, group: "nodes" }, - { id: "basic-node", label: "普通节点", Preview: BasicNodePreview, group: "nodes" }, - { id: "select", label: "Select", Preview: SelectPreview, group: "primitives" }, - { id: "menu", label: "Menu", Preview: MenuPreview, group: "primitives" }, - { id: "textarea", label: "Textarea", Preview: TextareaPreview, group: "primitives" }, - { id: "detail-page-layout", label: "Detail page", Preview: DetailPageLayoutPreview, group: "layouts" }, - { id: "form-label", label: "Form label", Preview: FormLabelPreview, group: "primitives" }, - { id: "resource-page-layout", label: "Resource page", Preview: ResourcePageLayoutPreview, group: "layouts" }, - { id: "pill-tag", label: "Pill tag", Preview: PillTagPreview, group: "primitives" }, - { id: "card", label: "Card", Preview: CardsPreview, group: "composites" }, - { id: "header", label: "Header", Preview: HeaderPreview, group: "composites" }, - { id: "modal-layout", label: "Modal layout", Preview: ModalLayoutPreview, group: "layouts" }, - { id: "code-block", label: "Code block", Preview: CodeBlockPreview, group: "composites" }, - { id: "button", label: "Button", Preview: ButtonsPreview, group: "primitives" }, - { id: "tabs", label: "Tabs", Preview: TabsPreview, group: "primitives" }, - { id: "input-tail-icon", label: "Input with tail icon", Preview: InputWithTailIconPreview, group: "primitives" }, - { id: "input-header-icon", label: "Input with header icon", Preview: InputWithHeaderIconPreview, group: "primitives" }, - { id: "label", label: "Label", Preview: LabelPreview, group: "primitives" }, - { id: "switch", label: "Switch", Preview: SwitchPreview, group: "primitives" }, - { id: "checkbox", label: "Checkbox", Preview: CheckboxPreview, group: "primitives" }, - { id: "radio", label: "Radio", Preview: RadioPreview, group: "primitives" }, - { id: "sidebar", label: "Sidebar / 会话", Preview: SidebarPreview, group: "composites" }, - { id: "form-field", label: "Form field", Preview: FormFieldPreview, group: "composites" }, - { id: "dashed-zone", label: "Dashed zone", Preview: DashedZonePreview, group: "composites" }, - { id: "item", label: "Item", Preview: ItemPreview, group: "composites" }, +type PreviewDefinition = { + id: string; + label: string; + Preview: ComponentType; + group: string; + apiNames: readonly string[]; + ApiDetails?: ComponentType; +}; + +const examples: PreviewDefinition[] = [ + { id: "app-layout", label: "App Layout", Preview: AppLayoutPreview, group: "layout", apiNames: ["AppLayout"] }, + { id: "date-picker", label: "Date Picker", Preview: DatePickerPreview, group: "base", apiNames: ["DatePicker"] }, + { id: "index-layout", label: "Index Layout", Preview: IndexLayoutPreview, group: "layout", apiNames: ["IndexLayout"] }, + { id: "slider", label: "Slider", Preview: SliderPreview, group: "base", apiNames: ["Slider"] }, + { id: "file-upload", label: "File Upload", Preview: FileUploadPreview, group: "block", apiNames: ["FileUpload"] }, + { id: "modal-button", label: "Modal Button", Preview: ModalButtonPreview, group: "block", apiNames: ["ModalButton"] }, + { id: "drawer", label: "Drawer", Preview: DrawerPreview, group: "block", apiNames: ["Drawer"] }, + { id: "file-explorer", label: "File Explorer", Preview: FileExplorerPreview, group: "block", apiNames: ["FileExplorer"] }, + { id: "long-running-state", label: "Progress", Preview: LongRunningStatePreview, group: "block", apiNames: ["LongRunningState"] }, + { id: "empty-state", label: "Empty State", Preview: EmptyStatePreview, group: "base", apiNames: ["EmptyState"] }, + { id: "error-state", label: "Error State", Preview: ErrorStatePreview, group: "base", apiNames: ["ErrorState"] }, + { id: "loading", label: "Loading", Preview: LoadingPreview, group: "base", apiNames: ["Loading"] }, + { id: "toast", label: "Toast", Preview: ToastPreview, group: "base", apiNames: ["Toast", "ToastProvider"], ApiDetails: ToastHookApi }, + { id: "table", label: "Table", Preview: TablePreview, group: "base", apiNames: ["Table", "TableCellText", "TableStatus"] }, + { id: "load-more", label: "Load More", Preview: LoadMoreAreaPreview, group: "block", apiNames: ["LoadMoreArea"] }, + { id: "scroll-area", label: "Scroll Area", Preview: ScrollAreaPreview, group: "base", apiNames: ["ScrollArea"] }, + { id: "divider", label: "Divider", Preview: DividerPreview, group: "base", apiNames: ["Divider"] }, + { id: "dropdown", label: "Dropdown", Preview: DropdownPreview, group: "base", apiNames: ["Dropdown"] }, + { id: "prompt-input", label: "Prompt Input", Preview: PromptInputPreview, group: "ai-app", apiNames: ["PromptInput", "SingleLinePromptInput"] }, + { id: "conversation-flow", label: "Conversation Flow", Preview: ConversationFlowPreview, group: "ai-app", apiNames: ["ConversationFlow", "ConversationMarkdown", "ConversationVisualization", "ConversationSurface"] }, + { id: "tokens", label: "Tokens", Preview: TokensPreview, group: "foundation", apiNames: [] }, + { id: "specification", label: "Specification", Preview: SpecificationPreview, group: "foundation", apiNames: [] }, + { id: "canvas-background", label: "Canvas Background", Preview: CanvasBackgroundPreview, group: "node", apiNames: ["CanvasBackground"] }, + { id: "agent-node", label: "Agent Node", Preview: AgentNodePreview, group: "node", apiNames: ["AgentNode"] }, + { id: "basic-node", label: "Basic Node", Preview: BasicNodePreview, group: "node", apiNames: ["BasicNode"] }, + { id: "select", label: "Select", Preview: SelectPreview, group: "base", apiNames: ["Select"] }, + { id: "menu", label: "Menu", Preview: MenuPreview, group: "base", apiNames: ["Menu"], ApiDetails: MenuEntryApi }, + { id: "textarea", label: "Textarea", Preview: TextareaPreview, group: "base", apiNames: ["Textarea"] }, + { id: "detail-page-layout", label: "Detail Page", Preview: DetailPageLayoutPreview, group: "layout", apiNames: ["DetailPageLayout"] }, + { id: "form-label", label: "Form Label", Preview: FormLabelPreview, group: "base", apiNames: ["FormLabel"] }, + { id: "form-label-row", label: "Form Label Row", Preview: FormLabelRowPreview, group: "block", apiNames: ["FormLabelRow"] }, + { id: "resource-page-layout", label: "Resource Page", Preview: ResourcePageLayoutPreview, group: "layout", apiNames: ["ResourcePageLayout"] }, + { id: "pill-tag", label: "Pill Tag", Preview: PillTagPreview, group: "base", apiNames: ["PillTag"] }, + { id: "card", label: "Card", Preview: CardsPreview, group: "block", apiNames: ["ResourceCard", "InfoCard", "InfoCardTitle", "InfoCardBody", "PromoCard"] }, + { id: "card-layout", label: "Card Layout", Preview: CardLayoutPreview, group: "layout", apiNames: ["CardLayout"] }, + { id: "header", label: "Header", Preview: HeaderPreview, group: "block", apiNames: ["Header"] }, + { id: "modal-layout", label: "Modal Layout", Preview: ModalLayoutPreview, group: "layout", apiNames: ["ModalLayout"] }, + { id: "code-block", label: "Code Block", Preview: CodeBlockPreview, group: "block", apiNames: ["CodeBlock"] }, + { id: "button", label: "Button", Preview: ButtonsPreview, group: "base", apiNames: ["Button", "GlassIconButton"] }, + { id: "glass-icon-button-group", label: "Glass Icon Button Group", Preview: GlassIconButtonGroupPreview, group: "block", apiNames: ["GlassIconButtonGroup"] }, + { id: "tabs", label: "Tabs", Preview: TabsPreview, group: "base", apiNames: ["UnderlineTabs", "PillTabs", "FilterTabs", "GlassTabs"] }, + { id: "input", label: "Input", Preview: InputPreview, group: "base", apiNames: ["InputWithHeaderIcon", "InputWithTailIcon"] }, + { id: "label", label: "Label", Preview: LabelPreview, group: "base", apiNames: ["Label"] }, + { id: "switch", label: "Switch", Preview: SwitchPreview, group: "base", apiNames: ["Switch"] }, + { id: "checkbox", label: "Checkbox", Preview: CheckboxPreview, group: "base", apiNames: ["Checkbox"] }, + { id: "radio", label: "Radio", Preview: RadioPreview, group: "base", apiNames: ["RadioCard", "Radio"] }, + { id: "sidebar", label: "Sidebar", Preview: SidebarPreview, group: "block", apiNames: ["Sidebar", "SidebarAccount", "SidebarGroupTitle", "SidebarItem", "SidebarItemWithIcon"] }, + { id: "form-field", label: "Form Field", Preview: FormFieldPreview, group: "block", apiNames: ["FormField"] }, + { id: "dashed-zone", label: "Dashed Zone", Preview: DashedZonePreview, group: "block", apiNames: ["DashedZone"] }, + { id: "item", label: "Item", Preview: ItemPreview, group: "block", apiNames: ["Item"] }, ]; const groups = [ - { id: "ai-app", label: "AI APP", items: examples.filter((item) => item.group === "ai-app") }, - { id: "design", label: "设计规范", items: examples.filter((item) => item.group === "design") }, - { id: "nodes", label: "节点组件", items: examples.filter((item) => item.group === "nodes") }, - { id: "primitives", label: "基础组件", items: examples.filter((item) => item.group === "primitives") }, - { id: "composites", label: "复合组件", items: examples.filter((item) => item.group === "composites") }, - { id: "layouts", label: "布局", items: examples.filter((item) => item.group === "layouts") }, -]; + { id: "foundation", label: "Foundation" }, + { id: "base", label: "Base" }, + { id: "block", label: "Block" }, + { id: "ai-app", label: "AI App" }, + { id: "node", label: "Node" }, + { id: "layout", label: "Layout" }, +].map(group => ({ + ...group, + items: examples.filter(item => item.group === group.id).sort((a, b) => a.label.localeCompare(b.label, "en")), +})); function subscribeToHash(onChange: () => void) { window.addEventListener("hashchange", onChange); return () => window.removeEventListener("hashchange", onChange); } -function getSelectedExample() { - const id = window.location.hash.slice(1); - if (["underline-tabs", "pill-tabs", "filter-tabs"].includes(id)) return "tabs"; - if (["resource-card", "info-card", "card-layout"].includes(id)) return "card"; - if (["glass-icon-button", "glass-icon-button-group"].includes(id)) return "button"; - return id; -} +const getHash = () => window.location.hash; + +const PreviewBody = memo(function PreviewBody({ example }: { example: PreviewDefinition }) { + const { Preview, ApiDetails } = example; + return <> +
+ {example.apiNames.length > 0 &&
+

参数与属性

+ + {ApiDetails && } +
} + ; +}); export function ComponentsPreview() { const [theme, changeTheme] = useStudioTheme(); - const selectedId = useSyncExternalStore(subscribeToHash, getSelectedExample, () => "button"); - const selected = examples.find((example) => example.id === selectedId) ?? examples.find((example) => example.id === "button")!; - const Preview = selected.Preview; - if (new URLSearchParams(window.location.search).get("fullscreen") === "app-layout") return ; + const [navigationOpen, setNavigationOpen] = useState(false); + const hash = useSyncExternalStore(subscribeToHash, getHash, () => "#button"); + const location = resolvePreviewLocation(hash); + const selected = examples.find(example => example.id === location.component) ?? examples.find(example => example.id === "button")!; + const scrollRef = useRef(null); + const fullscreen = new URLSearchParams(window.location.search).get("fullscreen") === "app-layout"; + + useEffect(() => { + document.title = `${selected.label} | AgentKit Studio Components`; + }, [selected.label]); + + if (fullscreen) return ; return (
- -

Components Preview

-
- - + +
+

Components Preview

+ +
+
+
+ + +
+
- -
- - +
+ + +

{selected.label}

+ +
+
); } diff --git a/frontend/src/components-preview/PreviewContents.tsx b/frontend/src/components-preview/PreviewContents.tsx new file mode 100644 index 000000000..9e9df5d31 --- /dev/null +++ b/frontend/src/components-preview/PreviewContents.tsx @@ -0,0 +1,86 @@ +import { useEffect, useRef, useState, type RefObject } from "react"; +import { ScrollArea } from "../components/primitives/ScrollArea"; + +type Heading = { id: string; label: string }; + +export function PreviewContents({ scrollRef, componentId, sectionId }: { + scrollRef: RefObject; + componentId: string; + sectionId: string; +}) { + const [headings, setHeadings] = useState([]); + const [activeId, setActiveId] = useState(""); + const mobileRef = useRef(null); + + useEffect(() => { + const scroll = scrollRef.current; + if (!scroll) return; + const nodes = Array.from(scroll.querySelectorAll("h2[data-preview-heading]")); + setHeadings(nodes.map(node => ({ id: node.id, label: node.textContent?.trim() || "" }))); + let frame = 0; + function updateActive() { + frame = 0; + const top = scroll!.getBoundingClientRect().top + 40; + let current = nodes[0]; + for (const node of nodes) { + if (node.getBoundingClientRect().top <= top) current = node; + } + setActiveId(current?.id || ""); + } + function scheduleUpdate() { + if (!frame) frame = requestAnimationFrame(updateActive); + } + scroll.addEventListener("scroll", scheduleUpdate, { passive: true }); + const observer = new ResizeObserver(scheduleUpdate); + observer.observe(scroll); + if (scroll.firstElementChild) observer.observe(scroll.firstElementChild); + updateActive(); + return () => { + scroll.removeEventListener("scroll", scheduleUpdate); + observer.disconnect(); + cancelAnimationFrame(frame); + }; + }, [componentId, scrollRef]); + + useEffect(() => { + const scroll = scrollRef.current; + if (!scroll) return; + if (mobileRef.current) mobileRef.current.open = false; + const heading = Array.from(scroll.querySelectorAll("h2[data-preview-heading]")).find(node => node.id === sectionId); + if (heading) { + heading.tabIndex = -1; + heading.focus({ preventScroll: true }); + scroll.scrollTo({ top: heading.getBoundingClientRect().top - scroll.getBoundingClientRect().top + scroll.scrollTop - 24 }); + setActiveId(heading.id); + } else { + scroll.scrollTo({ top: 0 }); + } + }, [componentId, sectionId, scrollRef]); + + const links = ; + + return <> + +

本页目录

+ {links} +
+
+ 本页目录 + {links} +
+ ; +} diff --git a/frontend/src/components-preview/README.md b/frontend/src/components-preview/README.md index 2ab02543d..5574dfba6 100644 --- a/frontend/src/components-preview/README.md +++ b/frontend/src/components-preview/README.md @@ -16,14 +16,25 @@ npm run dev:components ## 内容与使用 +导航依次为 Foundation、Base、Block、AI App、Node、Layout,组内按组件名称排序 +Base 对应基础组件,Block 对应复合组件;同一组件的变种集中在一个页面,例如 Input 包含 Leading Icon 和 Trailing Icon +Form Label Row 和 Glass Icon Button Group 独立归入 Block,Card Layout 归入 Layout,不作为基础组件的外观变种 +Progress 对应 LongRunningState 组件,正文 Text 和代码 Code 分别在二级标题下独立展示,各自支持步骤推进和重新开始;`#progress` 与原来的 `#long-running-state` 均可访问 +组件名称使用一级标题,变种使用二级标题,参数与属性统一放在页面底部 +桌面右侧的本页目录可跳转到各变种和参数区;窄屏使用可展开的组件目录和本页目录 +变种链接采用 `#组件/小节`,例如 `#button/button-loading-title`;原有组件、Tabs、Card、玻璃按钮和 Input 链接继续可用,迁移到独立页面的旧小节链接也会定位到对应组件 + + 组件实现在 `src/components/`,预览示例位于本目录的 `examples/`,示例直接使用真实组件 -- 设计规范:颜色、字体、尺寸、间距、圆角和动效 Tokens -- 基础组件:Button、Tabs、Label、FormLabel、InputWithTailIcon、InputWithHeaderIcon、Textarea、Select、DatePicker、Menu、Switch、Checkbox、Radio、RadioCard、PillTag、Divider、Dropdown、ScrollArea、Table、Toast、Loading、EmptyState、ErrorState、Slider -- 复合组件:Sidebar、Item、卡片、Header、FormField、FormLabelRow、DashedZone、CodeBlock、LongRunningState、ModalButton、Drawer、FileExplorer、FileUpload 和玻璃按钮组 -- 布局:AppLayout、CardLayout、ModalLayout、ResourcePageLayout、DetailPageLayout、IndexLayout -- 节点组件:BasicNode、AgentNode、CanvasBackground -- AI APP:PromptInput、ConversationFlow,对话流包含完整分析、富内容、多模态输入、多模态回复、授权与错误五个可交互示例 +- Foundation:Specification 前端开发规范,以及颜色、字体、尺寸、间距、圆角和动效 Tokens +- Base:Button、Tabs、Label、FormLabel、Input、Textarea、Select、DatePicker、Menu、Switch、Checkbox、Radio、RadioCard、PillTag、Divider、Dropdown、ScrollArea、Table、Toast、Loading、EmptyState、ErrorState、Slider +- Block:Sidebar、Item、卡片、Header、FormField、FormLabelRow、DashedZone、CodeBlock、Progress(LongRunningState)、ModalButton、Drawer、FileExplorer、FileUpload 和玻璃按钮组 +- Layout:AppLayout、CardLayout、ModalLayout、ResourcePageLayout、DetailPageLayout、IndexLayout +- Node:BasicNode、AgentNode、CanvasBackground +- AI App:PromptInput、ConversationFlow,对话流包含完整分析、富内容、多模态输入、多模态回复、授权与错误五个可交互示例 + +Specification 通过 `#specification` 访问,以 [Specification.md](foundation/Specification.md) 为唯一内容来源,支持页内目录和复制规范,AI 开发前可直接读取该文档 ConversationFlow 按顺序展示思考、工具输入输出、子智能体移交、最终回复和执行指标 用户消息靠右,模型回复靠左,不显示头像和名称;复用默认 600px 高的 ScrollArea,通过 height / scrollAreaProps 配置,示例不包含输入框和执行计划 @@ -31,9 +42,10 @@ ECharts / Mermaid 实际渲染,支持源码切换和放大查看;文件预 `fromStudioTurns` 可接入现有 Studio 消息;组件行为、数据字段及全部回调见预览下方参数表与 [对话流说明](../components/ai-app/ConversationFlow/README.md) Button、Tabs 和 Card 的变体集中展示,旧预览链接仍可访问 -Primary 和 Secondary 普通按钮默认同为 32px 高、上下 5px 内边距,图标按钮保持 28px +Button 的每种样式并排展示 Compact、Default、Large,按钮底端对齐,尺寸说明放在按钮下方;文字按钮高度为 28 / 32 / 36px、字号为 12 / 14 / 16px、图标为 14 / 16 / 18px,Pill 共用相同尺寸;纯图标按钮高度为 20 / 28 / 36px +尺寸说明读取各按钮实际渲染的高度、字号或图标尺寸,尺寸变量变化时同步更新 透明 Icon Button 通过 hoverEffect 选择 background(默认,悬停显示背景)或 icon(仅高亮图标);两种模式共用尺寸和主题 tokens,会话尾部不覆盖 Button 的图标、圆角或 hover 样式 -示例按 Figma 画布尺寸展示;示例数据和页面导航不属于组件 API +固定宽度的 Header、Canvas Background 和 Resource Page 示例保留设计尺寸,在各自的 ScrollArea 内横向查看;示例数据和页面导航不属于组件 API ModalLayout 提供 header、空白 body 插槽和 footer,实际遮罩与焦点管理由使用方提供 FormLabel 的 required 表示必填标记,表单控件需要同时设置原生 required 属性 Textarea 的 counter 为独立插槽,预览保留原稿计数 @@ -59,7 +71,7 @@ Toast 复用中性面板、状态色和 Button,支持提示、成功、警告 悬停或键盘聚焦时暂停计时;F6 可聚焦通知区,Escape 关闭当前聚焦通知,新增通知不抢焦点,支持减少动态效果偏好 Toast 是独立展示层;ToastProvider、useToast 和 ToastOptions 的参数表见 Toast 预览 -Loading 复现提供的 Infinity Path(48 × 24px、2 秒循环)与 Ring Sweep(40 × 40px、1 秒循环),支持 size、无障碍 label 和 decorative 属性,减少动态效果时显示静态图形 +Loading 提供 Infinity Path(默认 32 × 16px、2 秒循环)与 Ring Sweep(默认 20 × 20px、1 秒循环),支持 size、无障碍 label 和 decorative 属性,减少动态效果时显示静态图形 ScrollArea 加载时直接复用 Infinity Path,使用 24 × 12px 画布使路径与行内文字大小协调,仅显示图标;加载状态保留屏幕阅读器提示,颜色随明暗主题切换 EmptyState 默认显示 40px 圆形背景与 24px 空文件夹图标;icon 仅替换内部图标,传 null 隐藏图标区域,actions 接收任意数量的 Button,横排并在空间不足时换行 @@ -69,8 +81,11 @@ ResourcePageLayout 的 loading 状态在资源区域居中显示 Infinity Path DetailPageLayout 不包含 Sidebar,内容随容器宽度展开,保留 Header、Tabs、Runtime 与顶部光效 -LongRunningState 使用居中的双栏工作区,左侧通过 steps 和 currentStep 展示任务进度与步骤,当前步骤使用 Infinity Path,已完成步骤显示对勾并支持回看;selectedStep / onSelectedStepChange 可控制历史选择,查看历史不改变执行进度,可返回当前步骤继续跟随 -右侧复用 Drawer 的明暗主题玻璃材质与 ScrollArea 展示 details,detailsMaxHeight 默认 360px,代码日志复用 CodeBlock 高亮并可隐藏行号;切换时详情即时替换并在原位短暂淡入,始终只显示一层正文,左侧位置稳定,窄容器上下排列,支持减少动态效果、减少透明度与高对比度偏好 +LongRunningState 整体左对齐,从上到下依次为 14px 当前进度名称、快速循环滑动的加载条和无背景的 ScrollArea 内容区 +名称切换时先向上渐隐,下一名称从下方向上滑入;执行中加载条以 1.1 秒周期循环滑动,不将未知进度伪装为百分比,完成后停止并保留最后的内容 +detailsMaxHeight 默认 240px,代码日志可复用透明背景的 CodeBlock;步骤切换后内容滚回顶部,同一步骤追加内容时保留阅读位置,支持减少动态效果偏好 + +ConversationFlow 新追加的步骤先展开所需空间,再渐显内容;状态更新和展开历史详情不会重复进场,重试后重新追加的步骤会重新播放,减少动态效果时直接显示 ModalButton 的入口复用 Button,弹窗复用 ModalLayout,body 默认留空;提供遮罩、进出动效、焦点管理及 Esc 关闭,异步确认可使用 closeOnConfirm=false 与受控 open Drawer 是距屏幕上下与右侧各 16px 的浮动卡片,默认宽 480px,正文复用 ScrollArea;surface 默认 glass,深色使用烟灰玻璃,浅色使用白色磨砂玻璃,真实模糊后方内容并显示柔和高光,也可设为 solid;两者的层级低于 Menu、Select 和 Toast diff --git a/frontend/src/components-preview/api/ComponentApi.tsx b/frontend/src/components-preview/api/ComponentApi.tsx index 4edce4d04..30f6ef29a 100644 --- a/frontend/src/components-preview/api/ComponentApi.tsx +++ b/frontend/src/components-preview/api/ComponentApi.tsx @@ -66,7 +66,7 @@ const componentNotes: Record> = { }, LongRunningState: { steps: "按执行顺序排列:id 为稳定唯一标识,title 为步骤名称,details 支持文字或 React 内容", - currentStep: "当前执行步骤的 id,左侧此步骤显示 Loading,右侧展示对应详情;null 表示暂无执行步骤", + currentStep: "当前执行步骤的 id;名称向上淡出并滑入下一名称,加载条快速循环滑动,下方无背景 ScrollArea 展示对应详情;null 表示暂无执行步骤", }, ModalButton: { label: "触发按钮内容,点击后打开复用 ModalLayout 的弹窗", @@ -126,8 +126,8 @@ const componentNotes: Record> = { style: "外层容器样式,可调整宽度、内边距或最小高度", }, Loading: { - variant: "infinity 无限路径(默认 48 × 24px、2 秒循环);ring 圆环(默认 40 × 40px、1 秒循环)", - size: "图形宽度,单位 px;infinity 高度为宽度的一半,ring 宽高相同且描边按比例缩放", + variant: "infinity 无限路径(默认 32 × 16px、2 秒循环);ring 圆环(默认 20 × 20px、1 秒循环)", + size: "图形宽度,数字单位为 px,也支持 CSS 长度;infinity 高度为宽度的一半,ring 宽高相同且描边按比例缩放", label: "仅供屏幕阅读器读取的加载状态文案,不显示在图标旁", decorative: "设为 true 时隐藏无障碍状态,由按钮等外层提供名称和加载状态", }, @@ -152,11 +152,11 @@ const componentNotes: Record> = { }, Button: { loading: "默认 false;只显示居中的 Ring 图标并自动禁用按钮,设置 aria-busy,保留原宽高与无障碍名称", - variant: "primary 主按钮、secondary 次按钮、outline 半透明描边按钮,默认高度统一为 32px;ghost 透明按钮、link 链接样式按钮", - size: "default 保留各变体原始尺寸;large 为 36px,Header 的两个按钮共用;compact 仅用于 iconOnly,20 × 20px 且无内边距,Modal 关闭按钮复用", - iconOnly: "纯图标按钮,默认 28 × 28px,large 为 36 × 36px;图标通过 startIcon 传入,需提供 aria-label,可组合不同 variant", + variant: "primary 主按钮、secondary 次按钮、outline 半透明描边按钮、ghost 透明按钮、link 链接样式按钮、pill 胶囊按钮;各样式共享尺寸选项,文字按钮默认高度为 32px", + size: "compact / default / large 的文字按钮高度为 28 / 32 / 36px,字号为 12 / 14 / 16px,图标为 14 / 16 / 18px,各样式共用;iconOnly 高度为 20 / 28 / 36px", + iconOnly: "纯图标按钮,compact / default / large 为 20 / 28 / 36px 方形,统一 6px 圆角;图标通过 startIcon 传入,需提供 aria-label,可组合不同 variant", hoverEffect: "ghost 纯图标按钮支持 background(默认,悬停显示背景)与 icon(仅高亮图标,悬停和按下均保持透明背景),两种使用相同尺寸与明暗主题 tokens", - endIcon: "右侧图标;link 样式下尺寸跟随字号,悬停或键盘聚焦时与文字下方虚线一起淡入,移开淡出", + endIcon: "右侧图标,尺寸跟随字号;link 样式下在悬停或键盘聚焦时与文字下方虚线一起淡入,移开淡出", }, Radio: { name: "同一组 Radio 使用相同 name,只能选中一项", @@ -246,7 +246,7 @@ const componentNotes: Record> = { const commonNative = new Set(["children", "className", "style", "id", "value", "defaultValue", "checked", "defaultChecked", "disabled", "readOnly", "onChange", "onClick", "name", "placeholder", "required", "maxLength", "type", "htmlFor", "aria-label"]); -export function ComponentApi({ names }: { names: string[] }) { +export function ComponentApi({ names }: { names: readonly string[] }) { return
{names.map(name => { const doc = docs[name]; diff --git a/frontend/src/components-preview/components-preview.css b/frontend/src/components-preview/components-preview.css index 12479b877..362c17603 100644 --- a/frontend/src/components-preview/components-preview.css +++ b/frontend/src/components-preview/components-preview.css @@ -50,7 +50,7 @@ body { margin: 0; } border-right: 1px solid var(--studio-border-subtle, #2b2b30); } -.components-preview-sidebar h1 { +.components-preview-brand p { margin: 0 12px 12px; font-size: 15px; line-height: 24px; @@ -63,7 +63,7 @@ body { margin: 0; } gap: 24px; } -.components-preview-sidebar h2 { +.components-preview-group-label { margin: 0; height: 32px; padding: 5px 10px; @@ -171,3 +171,137 @@ body { margin: 0; } @media (prefers-reduced-motion: reduce) { .components-preview-theme button { transition: none; } } + +.components-preview-page { + flex: 1; + display: grid; + grid-template-columns: minmax(0, 1fr) 176px; + min-width: 0; + min-height: 0; +} + +.components-preview-page > .components-preview-canvas { + grid-column: 1; + grid-row: 1; +} + +.components-preview-page-title { + margin: 0 0 36px; + color: var(--studio-text-primary); + font-size: 21px; + line-height: 28px; + font-weight: 600; +} + +.components-preview-examples, +.components-preview-variants { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 40px; + min-width: 0; +} +.components-preview-examples > *, .components-preview-variants > * { min-width: 0; } + +.components-preview-examples [data-preview-heading], +.components-preview-api > h2 { + margin: 0 0 20px; + color: var(--studio-text-primary); + font-family: inherit; + font-size: 17px; + line-height: 26px; + font-weight: 500; + scroll-margin-top: 24px; +} + +.components-preview-examples :is(.button-preview-examples, .date-picker-preview__examples, .label-preview-examples, .menu-preview__examples, .empty-state-preview__examples, .loading-preview-examples, .file-upload-preview__examples, .sidebar-preview-examples):has(> section > [data-preview-heading]) { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 40px; +} + +.components-preview-api { margin-top: 48px; min-width: 0; } +.components-preview-api > .component-api { margin-top: 0; } +.components-preview-api > .component-api + .component-api { margin-top: 24px; } +.components-preview-variant + .components-preview-variant { margin-top: 40px; } +.components-preview-variant { min-width: 0; } +.components-preview-examples .form-field-preview-required-title, +.components-preview-examples .studio-slider + .components-preview-variant { margin-top: 40px; } +.components-preview-example-row { display: flex; flex-wrap: wrap; align-items: center; gap: 24px; } + +.components-preview-contents { + grid-column: 2; + grid-row: 1; + min-width: 0; + min-height: 0; + padding: 36px 16px 32px 0; +} + +.components-preview-contents-label { + margin: 0 0 12px; + color: var(--studio-text-primary); + font-size: 13px; + font-weight: 500; +} + +.components-preview-contents nav, +.components-preview-contents-mobile nav { display: grid; gap: 4px; } +.components-preview-contents a, +.components-preview-contents-mobile a { + display: block; + padding: 6px 10px; + border-left: 1px solid var(--studio-border-subtle); + color: var(--studio-text-secondary); + font-size: 13px; + line-height: 20px; + text-decoration: none; + overflow-wrap: anywhere; +} +.components-preview-contents a:hover, +.components-preview-contents-mobile a:hover { color: var(--studio-text-primary); } +.components-preview-contents a[aria-current], +.components-preview-contents-mobile a[aria-current] { + color: var(--studio-text-primary); + border-left-color: var(--studio-text-primary); +} +.components-preview-contents a:focus-visible, +.components-preview-contents-mobile a:focus-visible, +.components-preview-contents-mobile summary:focus-visible, +.components-preview-navigation-toggle:focus-visible { + outline: 2px solid var(--studio-focus-ring); + outline-offset: 2px; +} +.components-preview-contents-mobile, +.components-preview-navigation-toggle { display: none; } + +@media (max-width: 1100px) { + .components-preview-page { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); } + .components-preview-page > .components-preview-canvas { grid-row: 2; } + .components-preview-contents { display: none; } + .components-preview-contents-mobile { + display: block; + grid-column: 1; + grid-row: 1; + max-height: 32dvh; + overflow-y: auto; + padding: 12px 32px; + border-bottom: 1px solid var(--studio-border-subtle); + } + .components-preview-contents-mobile summary { padding: 4px 0; color: var(--studio-text-secondary); font-size: 13px; cursor: pointer; } + .components-preview-contents-mobile nav { padding: 12px 0 4px; } +} + +@media (max-width: 760px) { + .components-preview { flex-direction: column; } + .components-preview-sidebar { flex: none; width: 100%; max-height: 52dvh; padding: 12px 16px; border-right: 0; border-bottom: 1px solid var(--studio-border-subtle); } + .components-preview-brand { display: flex; align-items: center; justify-content: space-between; gap: 12px; } + .components-preview-brand p { margin: 0; } + .components-preview-navigation-toggle { display: inline-flex; align-items: center; gap: 6px; padding: 6px 0 6px 8px; border: 0; background: transparent; color: var(--studio-text-primary); font: inherit; font-size: 13px; cursor: pointer; } + .components-preview-navigation-toggle[aria-expanded="true"] svg { transform: rotate(180deg); } + .components-preview-sidebar[data-navigation-open="false"] .components-preview-navigation { display: none; } + .components-preview-navigation { padding-top: 16px; } + .components-preview-theme { margin: 0 0 16px; } + .components-preview-page { min-height: 0; } + .components-preview-contents-mobile { padding: 8px 16px; } + .components-preview-canvas { padding: 24px 16px; } + .components-preview-page-title { margin-bottom: 28px; } +} diff --git a/frontend/src/components-preview/examples/AgentNodePreview.tsx b/frontend/src/components-preview/examples/AgentNodePreview.tsx index b458a3045..b684ea9c4 100644 --- a/frontend/src/components-preview/examples/AgentNodePreview.tsx +++ b/frontend/src/components-preview/examples/AgentNodePreview.tsx @@ -1,11 +1,10 @@ -import { ComponentApi } from "../api/ComponentApi"; import { AgentNode } from "../../components/nodes/AgentNode"; import "./AgentNodePreview.css"; export function AgentNodePreview() { - return

Agent node

-

Selected

-

Default

-
+ return
+

Selected

+

Default

+
; } diff --git a/frontend/src/components-preview/examples/AppLayoutPreview.tsx b/frontend/src/components-preview/examples/AppLayoutPreview.tsx index 638396a97..8651f0bf3 100644 --- a/frontend/src/components-preview/examples/AppLayoutPreview.tsx +++ b/frontend/src/components-preview/examples/AppLayoutPreview.tsx @@ -12,7 +12,7 @@ import { Item } from "../../components/composites/Item"; import { PromptInput } from "../../components/ai-app/PromptInput"; import blocksIcon from "../../components/layouts/IndexLayout/assets/blocks.svg"; import codepenIcon from "../../components/layouts/IndexLayout/assets/codepen.svg"; -import { ComponentApi } from "../api/ComponentApi"; + import { promptPlaceholders } from "./promptInputExamples"; import { useSidebarPreviewState } from "./sidebarPreviewState"; import "./AppLayoutPreview.css"; @@ -27,7 +27,7 @@ function AppLayoutExample({ fullscreen = false }: { fullscreen?: boolean }) { return
-

App layout

+ {fullscreen ?

App Layout

:

Default

}
@@ -53,7 +53,6 @@ function AppLayoutExample({ fullscreen = false }: { fullscreen?: boolean }) { } /> - {!fullscreen && }
; } diff --git a/frontend/src/components-preview/examples/BasicNodePreview.tsx b/frontend/src/components-preview/examples/BasicNodePreview.tsx index f9332bcda..d1e2da90f 100644 --- a/frontend/src/components-preview/examples/BasicNodePreview.tsx +++ b/frontend/src/components-preview/examples/BasicNodePreview.tsx @@ -1,15 +1,12 @@ -import { ComponentApi } from "../api/ComponentApi"; -import { useId } from "react"; import { BasicNode } from "../../components/nodes/BasicNode"; import "./BasicNodePreview.css"; export function BasicNodePreview() { - const id = useId(); + const id = "preview-basic-node"; return (
-

Basic node

+

Default

-
); } diff --git a/frontend/src/components-preview/examples/ButtonPreview.css b/frontend/src/components-preview/examples/ButtonPreview.css index 62d24fabf..012fd991c 100644 --- a/frontend/src/components-preview/examples/ButtonPreview.css +++ b/frontend/src/components-preview/examples/ButtonPreview.css @@ -14,11 +14,6 @@ font-weight: 400; } -/* Exact frame width of the Figma specimen 631:246580 */ -.button-preview-create { - width: 138px; -} - .button-preview-icon-section, .button-preview-loading-section { margin-top: 32px; @@ -38,3 +33,13 @@ align-items: center; gap: 4px; } + +.button-preview-sizes { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + gap: 20px 24px; +} +.button-preview-size-example { display: grid; justify-items: start; gap: 8px; } +.button-preview-size-label { display: grid; color: var(--studio-text-secondary); font-size: 12px; line-height: 20px; white-space: nowrap; } +.button-preview-icon-section > .button-preview-sizes { margin-bottom: 28px; } diff --git a/frontend/src/components-preview/examples/ButtonPreview.tsx b/frontend/src/components-preview/examples/ButtonPreview.tsx index 8f10bcc87..577899b18 100644 --- a/frontend/src/components-preview/examples/ButtonPreview.tsx +++ b/frontend/src/components-preview/examples/ButtonPreview.tsx @@ -1,8 +1,54 @@ -import { useEffect, useState } from "react"; -import { ComponentApi } from "../api/ComponentApi"; +import { useEffect, useLayoutEffect, useRef, useState } from "react"; + import { Button, PlusIcon, PlayIcon, BackIcon, ExternalLinkIcon } from "../../components"; +import type { ButtonProps } from "../../components/primitives/Button"; import "./ButtonPreview.css"; +const sizes = [ + { value: "compact", label: "Compact" }, + { value: "default", label: "Default" }, + { value: "large", label: "Large" }, +] as const; + +function ButtonSizeExample({ label, size, ...props }: ButtonProps & { label: string }) { + const buttonRef = useRef(null); + const [metrics, setMetrics] = useState<{ height: string; detail: string } | null>(null); + const { iconOnly } = props; + + useLayoutEffect(() => { + const button = buttonRef.current; + if (!button) return; + const icon = button.querySelector(".studio-button__icon"); + const updateMetrics = () => { + const buttonStyle = getComputedStyle(button); + const next = { + height: buttonStyle.height, + detail: iconOnly && icon ? `图标 ${getComputedStyle(icon).width}` : `字号 ${buttonStyle.fontSize}`, + }; + setMetrics(current => current?.height === next.height && current.detail === next.detail ? current : next); + }; + updateMetrics(); + const observer = new ResizeObserver(updateMetrics); + observer.observe(button); + button.querySelectorAll(".studio-button__icon, .studio-button__label").forEach(element => observer.observe(element)); + return () => observer.disconnect(); + }, [size, iconOnly]); + + return
+
; +} + +function ButtonSizes(props: Omit) { + return
+ {sizes.map(size => )} +
; +} + export function ButtonPreview() { const [loading, setLoading] = useState(false); @@ -13,94 +59,87 @@ export function ButtonPreview() { }, [loading]); return ( -
-

Button

+
-

Primary

- +

Primary

+ Confirm
-

Primary with icon

- +

Primary with Icon

+ }>Create agent
-

Secondary

- +

Secondary

+ }>Test
-

Ghost

- +

Ghost

+ }>Back
-

Outline

- -
-
-

Large

-
- - -
+

Outline

+ Cancel
-

Pill

- +

Pill

+ Update
- - + + }>View details
-

Icon button

+

Icon Button

+ } aria-label="Add" />
-

Hover background

+

Hover background

-

Hover icon highlight

+

Hover icon highlight

-

Group

+

Group

-

Compact

+

Compact

-

Disabled

+

Disabled

-

Loading

+

Loading

-

Primary

+

Primary

-

Secondary

+

Secondary

-

Ghost

+

Ghost

-

Icon button

+

Icon button

-

点击体验

+

点击体验

-
); } diff --git a/frontend/src/components-preview/examples/ButtonsPreview.tsx b/frontend/src/components-preview/examples/ButtonsPreview.tsx index 1adedf42a..47fcce318 100644 --- a/frontend/src/components-preview/examples/ButtonsPreview.tsx +++ b/frontend/src/components-preview/examples/ButtonsPreview.tsx @@ -1,11 +1,9 @@ import { ButtonPreview } from "./ButtonPreview"; import { GlassIconButtonPreview } from "./GlassIconButtonPreview"; -import { GlassIconButtonGroupPreview } from "./GlassIconButtonGroupPreview"; export function ButtonsPreview() { return
-
; } diff --git a/frontend/src/components-preview/examples/CanvasBackgroundPreview.tsx b/frontend/src/components-preview/examples/CanvasBackgroundPreview.tsx index f1cb224cd..d5310c7e3 100644 --- a/frontend/src/components-preview/examples/CanvasBackgroundPreview.tsx +++ b/frontend/src/components-preview/examples/CanvasBackgroundPreview.tsx @@ -1,10 +1,11 @@ -import { ComponentApi } from "../api/ComponentApi"; import { CanvasBackground } from "../../components/nodes/CanvasBackground"; +import { ScrollArea } from "../../components/primitives/ScrollArea"; export function CanvasBackgroundPreview() { return
-

画布背景

- - +

Default

+ + +
; } diff --git a/frontend/src/components-preview/examples/CardLayoutPreview.tsx b/frontend/src/components-preview/examples/CardLayoutPreview.tsx index c6ae3ed7d..355522793 100644 --- a/frontend/src/components-preview/examples/CardLayoutPreview.tsx +++ b/frontend/src/components-preview/examples/CardLayoutPreview.tsx @@ -1,4 +1,3 @@ -import { ComponentApi } from "../api/ComponentApi"; import { useState } from "react"; import { CardLayout } from "../../components/layouts/CardLayout"; import { Button } from "../../components/primitives/Button"; @@ -6,8 +5,7 @@ import { Button } from "../../components/primitives/Button"; export function CardLayoutPreview() { const [open, setOpen] = useState(true); return
-

Card layout

+

Default

{open ? setOpen(false)} /> : } -
; } diff --git a/frontend/src/components-preview/examples/CardsPreview.tsx b/frontend/src/components-preview/examples/CardsPreview.tsx index df61afa83..7f4194fe0 100644 --- a/frontend/src/components-preview/examples/CardsPreview.tsx +++ b/frontend/src/components-preview/examples/CardsPreview.tsx @@ -1,13 +1,11 @@ import { PromoCardPreview } from "./PromoCardPreview"; import { ResourceCardPreview } from "./ResourceCardPreview"; import { InfoCardPreview } from "./InfoCardPreview"; -import { CardLayoutPreview } from "./CardLayoutPreview"; export function CardsPreview() { return
-
; } diff --git a/frontend/src/components-preview/examples/CheckboxPreview.tsx b/frontend/src/components-preview/examples/CheckboxPreview.tsx index 9483785bd..aef4ac0ca 100644 --- a/frontend/src/components-preview/examples/CheckboxPreview.tsx +++ b/frontend/src/components-preview/examples/CheckboxPreview.tsx @@ -1,13 +1,11 @@ -import { ComponentApi } from "../api/ComponentApi"; import { Checkbox } from "../../components/primitives/Checkbox"; import "./CheckboxPreview.css"; export function CheckboxPreview() { return (
-

Checkbox

+

Default

-
); } diff --git a/frontend/src/components-preview/examples/CodeBlockPreview.tsx b/frontend/src/components-preview/examples/CodeBlockPreview.tsx index 06f13a9a0..530dcb872 100644 --- a/frontend/src/components-preview/examples/CodeBlockPreview.tsx +++ b/frontend/src/components-preview/examples/CodeBlockPreview.tsx @@ -1,11 +1,9 @@ -import { ComponentApi } from "../api/ComponentApi"; import { CodeBlock } from "../../components/composites/CodeBlock"; import { codeBlockSample } from "./codeBlockSample"; export function CodeBlockPreview() { return
-

Code block

+

Default

-
; } diff --git a/frontend/src/components-preview/examples/ConversationFlowPreview.css b/frontend/src/components-preview/examples/ConversationFlowPreview.css index f86cc77fd..e949b0790 100644 --- a/frontend/src/components-preview/examples/ConversationFlowPreview.css +++ b/frontend/src/components-preview/examples/ConversationFlowPreview.css @@ -12,7 +12,7 @@ margin-bottom: 24px; } -.conversation-flow-preview__panel { padding-top: 24px; min-width: 0; } +.conversation-flow-preview__panel { min-width: 0; } .conversation-flow-preview__choice { margin: 8px 0 0; diff --git a/frontend/src/components-preview/examples/ConversationFlowPreview.tsx b/frontend/src/components-preview/examples/ConversationFlowPreview.tsx index fcb52097b..04e38a6db 100644 --- a/frontend/src/components-preview/examples/ConversationFlowPreview.tsx +++ b/frontend/src/components-preview/examples/ConversationFlowPreview.tsx @@ -2,9 +2,8 @@ import { useCallback, useEffect, useRef, useState, type ComponentProps } from "r import { ConversationFlow, type ConversationAssistantMessage, type ConversationBlock, type ConversationFeedback, type ConversationMessage, type ConversationStatus } from "../../components/ai-app/ConversationFlow"; import { ConversationSurface } from "../../components/ai-app/ConversationFlow/ConversationSurface"; import { Button } from "../../components/primitives/Button"; -import { UnderlineTabs } from "../../components/primitives/UnderlineTabs"; import { FileExplorer, type FileExplorerEntry } from "../../components/composites/FileExplorer"; -import { ComponentApi } from "../api/ComponentApi"; + import chartImage from "./assets/conversation-media/chart.png"; import trendVideo from "./assets/conversation-media/trend.mp4"; import summaryAudio from "./assets/conversation-media/summary.m4a?url"; @@ -347,13 +346,12 @@ const scenes = [ ] as const; export function ConversationFlowPreview() { - const [scene, setScene] = useState("analysis"); - return
-

Conversation Flow

- -
- {scene === "analysis" ? : scene === "rich" ? : scene === "multimodal" ? : scene === "multimodal-response" ? : } -
- -
; + return
+ {scenes.map(scene =>
+

{scene.label}

+
+ {scene.value === "analysis" ? : scene.value === "rich" ? : scene.value === "multimodal" ? : scene.value === "multimodal-response" ? : } +
+
)} +
; } diff --git a/frontend/src/components-preview/examples/DashedZonePreview.tsx b/frontend/src/components-preview/examples/DashedZonePreview.tsx index 78e2feda7..db2bb863d 100644 --- a/frontend/src/components-preview/examples/DashedZonePreview.tsx +++ b/frontend/src/components-preview/examples/DashedZonePreview.tsx @@ -1,17 +1,14 @@ -import { ComponentApi } from "../api/ComponentApi"; -import { useId } from "react"; import { DashedZone } from "../../components/composites/DashedZone"; import "./DashedZonePreview.css"; export function DashedZonePreview() { - const id = useId(); + const id = "preview-dashed-zone"; return (
-

Dashed zone

+

Default

-
); } diff --git a/frontend/src/components-preview/examples/DatePickerPreview.tsx b/frontend/src/components-preview/examples/DatePickerPreview.tsx index bf2c2df91..932982be1 100644 --- a/frontend/src/components-preview/examples/DatePickerPreview.tsx +++ b/frontend/src/components-preview/examples/DatePickerPreview.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { today } from "@internationalized/date"; -import { ComponentApi } from "../api/ComponentApi"; + import { DatePicker } from "../../components/primitives/DatePicker"; import { FormField } from "../../components/composites/FormField"; import { Select } from "../../components/primitives/Select"; @@ -11,17 +11,16 @@ export function DatePickerPreview() { const [date, setDate] = useState(""); const [runAt, setRunAt] = useState(() => `${today("Asia/Shanghai").add({ days: 1 })}T09:00`); const start = today(zone); - return
-

Date Picker

+ return
-

日期

+

日期

-

定时任务 · 单次执行

+

定时任务 · 单次执行

@@ -36,18 +35,17 @@ export function DatePickerPreview() {

{runAt ? `${runAt.replace("T", " ")} · ${zone}` : "请选择执行时间"}

-

日期范围限制

+

日期范围限制

-

状态

+

状态

-
; } diff --git a/frontend/src/components-preview/examples/DetailPageLayoutPreview.tsx b/frontend/src/components-preview/examples/DetailPageLayoutPreview.tsx index 47879d5b7..cfbfe841a 100644 --- a/frontend/src/components-preview/examples/DetailPageLayoutPreview.tsx +++ b/frontend/src/components-preview/examples/DetailPageLayoutPreview.tsx @@ -1,4 +1,3 @@ -import { ComponentApi } from "../api/ComponentApi"; import { DetailPageLayout } from "../../components/layouts/DetailPageLayout"; import { Header } from "../../components/composites/Header"; import { InfoCard, InfoCardBody } from "../../components/composites/InfoCard"; @@ -22,7 +21,7 @@ function DetailPerformanceChart() { export function DetailPageLayoutPreview() { return
-

Detail page layout

+

Default

Back} header={
} @@ -33,6 +32,5 @@ export function DetailPageLayoutPreview() {

PERFORMANCE OVERVIEW

Title

- ; } diff --git a/frontend/src/components-preview/examples/DividerPreview.tsx b/frontend/src/components-preview/examples/DividerPreview.tsx index 75af9a486..3efa91e05 100644 --- a/frontend/src/components-preview/examples/DividerPreview.tsx +++ b/frontend/src/components-preview/examples/DividerPreview.tsx @@ -1,10 +1,8 @@ -import { ComponentApi } from "../api/ComponentApi"; import { Divider } from "../../components/primitives/Divider"; export function DividerPreview() { return
-

Divider

+

Default

-
; } diff --git a/frontend/src/components-preview/examples/DrawerPreview.tsx b/frontend/src/components-preview/examples/DrawerPreview.tsx index 74eb7e868..8f06deb86 100644 --- a/frontend/src/components-preview/examples/DrawerPreview.tsx +++ b/frontend/src/components-preview/examples/DrawerPreview.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { Drawer } from "../../components/composites/Drawer"; import { Button } from "../../components/primitives/Button"; -import { ComponentApi } from "../api/ComponentApi"; + import "./DrawerPreview.css"; const sections = [ @@ -17,7 +17,7 @@ export function DrawerPreview() { const [open, setOpen] = useState(false); return (
-

Drawer

+

Default

-
); } diff --git a/frontend/src/components-preview/examples/DropdownPreview.tsx b/frontend/src/components-preview/examples/DropdownPreview.tsx index 8b7e2c5f1..3e8423c78 100644 --- a/frontend/src/components-preview/examples/DropdownPreview.tsx +++ b/frontend/src/components-preview/examples/DropdownPreview.tsx @@ -1,12 +1,10 @@ -import { ComponentApi } from "../api/ComponentApi"; import { Dropdown } from "../../components/primitives/Dropdown"; export function DropdownPreview() { return
- +
-
; } diff --git a/frontend/src/components-preview/examples/EmptyStatePreview.tsx b/frontend/src/components-preview/examples/EmptyStatePreview.tsx index 3f1c5ee3f..279957cb9 100644 --- a/frontend/src/components-preview/examples/EmptyStatePreview.tsx +++ b/frontend/src/components-preview/examples/EmptyStatePreview.tsx @@ -1,6 +1,6 @@ import { Button } from "../../components/primitives/Button"; import { EmptyState } from "../../components/primitives/EmptyState"; -import { ComponentApi } from "../api/ComponentApi"; + import "./EmptyStatePreview.css"; function SearchEmptyIcon() { @@ -14,11 +14,10 @@ function SearchEmptyIcon() { export function EmptyStatePreview() { return ( -
-

Empty State

+
-

基础空状态

+

基础空状态

-

自定义图标

+

自定义图标

} @@ -39,7 +38,6 @@ export function EmptyStatePreview() {
-
); } diff --git a/frontend/src/components-preview/examples/ErrorStatePreview.tsx b/frontend/src/components-preview/examples/ErrorStatePreview.tsx index d86cdd374..8a3a0118c 100644 --- a/frontend/src/components-preview/examples/ErrorStatePreview.tsx +++ b/frontend/src/components-preview/examples/ErrorStatePreview.tsx @@ -1,18 +1,17 @@ import { ErrorState } from "../../components/primitives/ErrorState"; -import { ComponentApi } from "../api/ComponentApi"; + import "./ErrorStatePreview.css"; export function ErrorStatePreview() { return (
-

Error State

+

Default

-
); } diff --git a/frontend/src/components-preview/examples/FileExplorerPreview.tsx b/frontend/src/components-preview/examples/FileExplorerPreview.tsx index f12da7d49..06139c277 100644 --- a/frontend/src/components-preview/examples/FileExplorerPreview.tsx +++ b/frontend/src/components-preview/examples/FileExplorerPreview.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { FileExplorer, type FileExplorerEntry } from "../../components/composites/FileExplorer"; -import { ComponentApi } from "../api/ComponentApi"; + import "./FileExplorerPreview.css"; const demoFiles: readonly FileExplorerEntry[] = [ @@ -208,15 +208,14 @@ export function FileExplorerPreview() { ]); const [editStatus, setEditStatus] = useState(""); return ( -
-

File Explorer

-

只读 · 自动格式化与换行

+
+

只读 · 自动格式化与换行

-

编辑与保存

+

编辑与保存

{editStatus}

-
); } diff --git a/frontend/src/components-preview/examples/FileUploadPreview.tsx b/frontend/src/components-preview/examples/FileUploadPreview.tsx index 719e0fc01..6a7d34331 100644 --- a/frontend/src/components-preview/examples/FileUploadPreview.tsx +++ b/frontend/src/components-preview/examples/FileUploadPreview.tsx @@ -1,26 +1,24 @@ import { useState } from "react"; import { FileUpload } from "../../components/composites/FileUpload"; -import { ComponentApi } from "../api/ComponentApi"; + import "./FileUploadPreview.css"; export function FileUploadPreview() { const [files, setFiles] = useState([]); - return
-

File upload

+ return
-

多文件选择

+

多文件选择

-

单文件选择

+

单文件选择

-

禁用状态

+

禁用状态

-
; } diff --git a/frontend/src/components-preview/examples/FilterTabsPreview.tsx b/frontend/src/components-preview/examples/FilterTabsPreview.tsx index 160e03333..a2fef60a3 100644 --- a/frontend/src/components-preview/examples/FilterTabsPreview.tsx +++ b/frontend/src/components-preview/examples/FilterTabsPreview.tsx @@ -1,4 +1,3 @@ -import { ComponentApi } from "../api/ComponentApi"; import { useState } from "react"; import { FilterTabs } from "../../components/primitives/FilterTabs"; import "./FilterTabsPreview.css"; @@ -7,10 +6,9 @@ export function FilterTabsPreview() { const [value, setValue] = useState("all"); return (
-

Filter tabs

+

Filter

-
); } diff --git a/frontend/src/components-preview/examples/FormFieldPreview.tsx b/frontend/src/components-preview/examples/FormFieldPreview.tsx index ed2b94a33..0a19d6f4e 100644 --- a/frontend/src/components-preview/examples/FormFieldPreview.tsx +++ b/frontend/src/components-preview/examples/FormFieldPreview.tsx @@ -1,4 +1,3 @@ -import { ComponentApi } from "../api/ComponentApi"; import { useId, useState } from "react"; import { FormField } from "../../components/composites/FormField"; import { InputWithTailIcon } from "../../components/primitives/InputWithTailIcon"; @@ -31,7 +30,7 @@ export function FormFieldPreview() { return (
-

Form field

+

Default

- +

Required

+