diff --git a/design-system/README.md b/design-system/README.md index 6e704ff9e3..04f2767ce2 100644 --- a/design-system/README.md +++ b/design-system/README.md @@ -33,6 +33,13 @@ The same entry publishes `ChatComposer`, the shared context-band and compact/expanded input anatomy used by product composers without importing their stores, localized menus, editor, model, voice, or host actions. +`SetupWizard` publishes the guided configuration pattern used by agent creation: +a split header, tinted explanatory pane, one current-step indicator, form/editor +viewport and footer actions. It supports any consumer-controlled sequence and +stacks by available container width. Design Lab includes a working three-stage +example; the [UI package guide](packages/ui/README.md#guided-configuration) +documents validation, localized guidance, dialog labelling and scroll ownership. + FlowChat tool-card frameworks are published from `@openbitfun/ui/flow-chat` as `AmbientToolCard` and `ProminentToolCard`. The names describe whether a tool result should stay in the conversational background or receive deliberate user diff --git a/design-system/apps/design-lab/src/i18n/componentMetadata.ts b/design-system/apps/design-lab/src/i18n/componentMetadata.ts index 910845529d..49a43ec9a9 100644 --- a/design-system/apps/design-lab/src/i18n/componentMetadata.ts +++ b/design-system/apps/design-lab/src/i18n/componentMetadata.ts @@ -25,6 +25,7 @@ const descriptionKeys: Readonly> = { MobileTag: "component.MobileTag.description", MobileToolbar: "component.MobileToolbar.description", SplitView: "component.SplitView.description", + SetupWizard: "component.SetupWizard.description", ListModelsToolCard: "component.ListModelsToolCard.description", ControlHubToolCard: "component.ControlHubToolCard.description", ThinkingBlock: "component.ThinkingBlock.description", diff --git a/design-system/apps/design-lab/src/i18n/messages.ts b/design-system/apps/design-lab/src/i18n/messages.ts index 479aba17e9..66ad15905a 100644 --- a/design-system/apps/design-lab/src/i18n/messages.ts +++ b/design-system/apps/design-lab/src/i18n/messages.ts @@ -6,6 +6,7 @@ import { flowChatEn, flowChatZhCN, flowChatZhTW } from "./flowChatMessages"; import { subagentMotionEn, subagentMotionZhCN, subagentMotionZhTW } from "./subagentMotionMessages"; import { thinkingIndicatorEn, thinkingIndicatorZhCN, thinkingIndicatorZhTW } from "./thinkingIndicatorMessages"; import { splitViewEn, splitViewZhCN, splitViewZhTW } from "./splitViewMessages"; +import { setupWizardEn, setupWizardZhCN, setupWizardZhTW } from "./setupWizardMessages"; import type { DesignLabLocale } from "./core.mjs"; export const enUSMessages = { @@ -16,6 +17,7 @@ export const enUSMessages = { ...subagentMotionEn, ...thinkingIndicatorEn, ...splitViewEn, + ...setupWizardEn, ...flowChatEn, "component.OpenBitFunSolidMark.description": "The original silver solid mark on a transparent canvas, preserving its material and shading.", "component.OpenBitFunAppIcon.description": "The application icon used by the installed product: silver mark, black rounded-square tile, and assets for different display sizes.", @@ -838,6 +840,7 @@ export const zhCNMessages = { ...subagentMotionZhCN, ...thinkingIndicatorZhCN, ...splitViewZhCN, + ...setupWizardZhCN, ...flowChatZhCN, "component.OpenBitFunSolidMark.description": "透明底的银色实体标志,保留原稿的材质、光泽与立体明暗。", "component.OpenBitFunAppIcon.description": "实际应用使用的图标:银色实体标志、黑色圆角底板,以及适配不同显示尺寸的资源。", @@ -1658,6 +1661,7 @@ export const zhTWMessages = { ...subagentMotionZhTW, ...thinkingIndicatorZhTW, ...splitViewZhTW, + ...setupWizardZhTW, ...flowChatZhTW, "patterns.description": "查看元件在常見介面中的組合與互動。", "patterns.title": "組合範例", diff --git a/design-system/apps/design-lab/src/i18n/setupWizardMessages.ts b/design-system/apps/design-lab/src/i18n/setupWizardMessages.ts new file mode 100644 index 0000000000..c9e1e0e8bd --- /dev/null +++ b/design-system/apps/design-lab/src/i18n/setupWizardMessages.ts @@ -0,0 +1,83 @@ +export const setupWizardEn = { + "component.SetupWizard.description": "Guided configuration with a tinted explanation pane, one current-step indicator, a form or editor viewport, and independent actions.", + "setupWizard.title": "Configure workflow", + "setupWizard.guide": "Configuration guidance", + "setupWizard.progress": "{current} / {total}", + "setupWizard.basics": "Basic information", + "setupWizard.basicsTitle": "Make the workflow easy to find", + "setupWizard.basicsDescription": "A recognizable name helps collaborators choose the right workflow when starting a task.", + "setupWizard.rules": "Working rules", + "setupWizard.rulesTitle": "Keep results consistent", + "setupWizard.rulesDescription": "These rules apply to every run. Define completion criteria so repeated tasks produce comparable results.", + "setupWizard.access": "Available capabilities", + "setupWizard.accessTitle": "Set the scope of execution", + "setupWizard.accessDescription": "Enable only the capabilities this workflow needs. Every operation remains subject to the current permission policy.", + "setupWizard.name": "Workflow name", + "setupWizard.namePlaceholder": "For example, weekly research", + "setupWizard.rulesPlaceholder": "Enter the workflow's working rules", + "setupWizard.network": "Allow network access", + "setupWizard.back": "Back", + "setupWizard.next": "Next", + "setupWizard.finish": "Finish preview", + "setupWizard.reset": "Reset", + "setupWizard.complete": "Preview complete. No configuration was saved.", + "setupWizard.help": "Try all three stages. Drafts survive navigation. The contained state delegates scrolling to the editor; the narrow state stacks the guide above the form.", + "setupWizard.state.form": "Form", + "setupWizard.state.contained": "Contained editor", +} as const; + +export const setupWizardZhCN = { + "component.SetupWizard.description": "分步配置布局,包含带底色的引导区、单个当前步骤指示、表单或编辑器区域,以及独立操作区。", + "setupWizard.title": "配置工作流", + "setupWizard.guide": "配置引导", + "setupWizard.progress": "{current} / {total}", + "setupWizard.basics": "基础信息", + "setupWizard.basicsTitle": "方便发现与选择", + "setupWizard.basicsDescription": "清晰的名称能帮助协作者在开始任务时,找到适合的工作流。", + "setupWizard.rules": "工作规则", + "setupWizard.rulesTitle": "让结果保持一致", + "setupWizard.rulesDescription": "规则会应用于每次执行。明确完成条件,让重复任务的结果更容易比较。", + "setupWizard.access": "可用能力", + "setupWizard.accessTitle": "界定执行范围", + "setupWizard.accessDescription": "仅开放工作流需要的能力。每次操作仍受当前权限策略约束。", + "setupWizard.name": "工作流名称", + "setupWizard.namePlaceholder": "例如:每周调研", + "setupWizard.rulesPlaceholder": "请输入工作流的执行规则", + "setupWizard.network": "允许访问网络", + "setupWizard.back": "上一步", + "setupWizard.next": "下一步", + "setupWizard.finish": "完成预览", + "setupWizard.reset": "重置", + "setupWizard.complete": "预览已完成,未保存配置。", + "setupWizard.help": "可体验三个阶段,返回时保留输入。编辑器模式由内部编辑区域负责滚动;窄屏模式将引导区放到表单上方。", + "setupWizard.state.form": "表单", + "setupWizard.state.contained": "沉浸式编辑器", +} satisfies Record; + +export const setupWizardZhTW = { + "component.SetupWizard.description": "分步設定版面,包含帶底色的引導區、單個目前步驟指示、表單或編輯器區域,以及獨立操作區。", + "setupWizard.title": "設定工作流程", + "setupWizard.guide": "設定引導", + "setupWizard.progress": "{current} / {total}", + "setupWizard.basics": "基本資訊", + "setupWizard.basicsTitle": "方便發現與選擇", + "setupWizard.basicsDescription": "清楚的名稱能協助協作者在開始任務時,找到適合的工作流程。", + "setupWizard.rules": "工作規則", + "setupWizard.rulesTitle": "讓結果保持一致", + "setupWizard.rulesDescription": "規則會套用至每次執行。明確的完成條件,讓重複任務的結果更容易比較。", + "setupWizard.access": "可用能力", + "setupWizard.accessTitle": "界定執行範圍", + "setupWizard.accessDescription": "僅開放工作流程需要的能力。每次操作仍受目前權限策略約束。", + "setupWizard.name": "工作流程名稱", + "setupWizard.namePlaceholder": "例如:每週調研", + "setupWizard.rulesPlaceholder": "請輸入工作流程的執行規則", + "setupWizard.network": "允許存取網路", + "setupWizard.back": "上一步", + "setupWizard.next": "下一步", + "setupWizard.finish": "完成預覽", + "setupWizard.reset": "重設", + "setupWizard.complete": "預覽已完成,未儲存設定。", + "setupWizard.help": "可體驗三個階段,返回時保留輸入。編輯器模式由內部編輯區域負責捲動;窄螢幕模式將引導區放到表單上方。", + "setupWizard.state.form": "表單", + "setupWizard.state.contained": "沉浸式編輯器", +} satisfies Record; diff --git a/design-system/apps/design-lab/src/pages/ComponentDetailPage.tsx b/design-system/apps/design-lab/src/pages/ComponentDetailPage.tsx index cea8fe175a..3f076df40e 100644 --- a/design-system/apps/design-lab/src/pages/ComponentDetailPage.tsx +++ b/design-system/apps/design-lab/src/pages/ComponentDetailPage.tsx @@ -143,6 +143,7 @@ import { IconCompositionPreview } from "../preview/IconCompositionPreview"; import { RollingTextPreview } from "../preview/RollingTextPreview"; import { ThinkingIndicatorPreview } from "../preview/ThinkingIndicatorPreview"; import { SplitViewPreview } from "../preview/SplitViewPreview"; +import { SetupWizardPreview } from "../preview/SetupWizardPreview"; import { NestedMenuPattern } from "./ReferencePatterns"; import { useI18n, type MessageKey } from "../i18n"; import { @@ -224,6 +225,8 @@ const iconSizes = ["2xs", "xs", "sm", "md", "lg"] as const; const iconTones = ["inherit", "primary", "secondary", "muted", "disabled", "info", "success", "warning", "danger"] as const; const optionLabelKeys: Readonly> = { + form: "setupWizard.state.form", + contained: "setupWizard.state.contained", ...Object.fromEntries(Object.keys(previewEn).filter(key => key.startsWith("preview.state.")).map(key => [key.slice("preview.state.".length), key as MessageKey])), collapsed: "detail.option.collapsed", streaming: "detail.option.streaming", @@ -493,6 +496,9 @@ export function ComponentDetailPage({ const inspectorStates = states; const codeSample = useMemo(() => { + if (component.name === "SetupWizard") { + return 'import { SetupWizard } from "@openbitfun/ui";\n\n// The host owns step state, drafts, validation, and saving.\n}\n guideLabel={guideLabel}\n step={{ ...steps[current], progress: formatProgress(current + 1, steps.length) }}\n contentMode={steps[current].hasEditor ? "contained" : "scroll"}\n footer={}\n>\n \n\n\n// For a modal, connect Dialog aria-labelledby to the same titleId.\n'; + } if (component.name === "SplitView") { return `import { SplitView } from "@openbitfun/ui";\n\n}\n secondary={}\n mode={mode}\n secondarySide={contentSide}\n rightSize={rightSize}\n onRightSizeChange={setRightSize}\n dividerLabel="${t('splitView.resize')}"\n dividerActions={}\n/>`; } @@ -972,6 +978,7 @@ export function ComponentDetailPage({ return ; } if (component.name === "SplitView") return ; + if (component.name === "SetupWizard") return ; if (component.name === "MobileButton") { return {t("components.preview.actionCardTitle")}; diff --git a/design-system/apps/design-lab/src/preview/SetupWizardPreview.css b/design-system/apps/design-lab/src/preview/SetupWizardPreview.css new file mode 100644 index 0000000000..cb3cc76a9c --- /dev/null +++ b/design-system/apps/design-lab/src/preview/SetupWizardPreview.css @@ -0,0 +1,35 @@ +.setup-wizard-preview { inline-size: 100%; min-inline-size: 0; } +.setup-wizard-preview[data-narrow="true"] { max-inline-size: 420px; } +.setup-wizard-preview__surface { + block-size: 600px; + border-radius: var(--openbitfun-overlay-dialog-surface-radius); + box-shadow: var(--openbitfun-shadow-sm); +} +.setup-wizard-preview__form { + display: flex; + min-inline-size: 0; + flex-direction: column; + gap: var(--openbitfun-space-5); +} +.setup-wizard-preview__form[data-contained="true"] { min-block-size: 0; flex: 1; } +.setup-wizard-preview__editor { + min-block-size: 0; + flex: 1; + inline-size: 100%; + border: 0; + padding: 0; + resize: none; + color: var(--openbitfun-color-content-primary); + background: transparent; + font: inherit; +} +.setup-wizard-preview__editor:focus-visible { outline: var(--openbitfun-border-width-default) solid var(--openbitfun-color-focus-ring); } +.setup-wizard-preview__note { + margin-block: var(--openbitfun-space-4) 0; + color: var(--openbitfun-color-content-muted); + font-family: var(--openbitfun-type-body-sm-font-family); + font-size: var(--openbitfun-type-body-sm-font-size); + font-weight: var(--openbitfun-type-body-sm-font-weight); + line-height: var(--openbitfun-type-body-sm-line-height); + letter-spacing: var(--openbitfun-type-body-sm-letter-spacing); +} diff --git a/design-system/apps/design-lab/src/preview/SetupWizardPreview.tsx b/design-system/apps/design-lab/src/preview/SetupWizardPreview.tsx new file mode 100644 index 0000000000..31fa1db3a4 --- /dev/null +++ b/design-system/apps/design-lab/src/preview/SetupWizardPreview.tsx @@ -0,0 +1,51 @@ +import { useEffect, useId, useState } from "react"; +import { Button, Field, Input, SetupWizard, Switch } from "@openbitfun/ui"; +import { useI18n } from "../i18n"; +import "./SetupWizardPreview.css"; + +export function SetupWizardPreview({ state }: { state: string }) { + const { t } = useI18n(); + const formId = useId(); + const [step, setStep] = useState<0 | 1 | 2>(state === "contained" ? 1 : 0); + const [name, setName] = useState(""); + const [rules, setRules] = useState(""); + const [network, setNetwork] = useState(false); + const [complete, setComplete] = useState(false); + useEffect(() => { setStep(state === "contained" ? 1 : 0); setComplete(false); }, [state]); + const steps = [ + { id: "basics", label: t("setupWizard.basics"), title: t("setupWizard.basicsTitle"), description: t("setupWizard.basicsDescription") }, + { id: "rules", label: t("setupWizard.rules"), title: t("setupWizard.rulesTitle"), description: t("setupWizard.rulesDescription") }, + { id: "access", label: t("setupWizard.access"), title: t("setupWizard.accessTitle"), description: t("setupWizard.accessDescription") }, + ] as const; + const reset = () => { setStep(0); setName(""); setRules(""); setNetwork(false); setComplete(false); }; + + return
+ {t("setupWizard.reset")}} + guideLabel={t("setupWizard.guide")} + step={{ ...steps[step], progress: t("setupWizard.progress", { current: step + 1, total: steps.length }) }} + contentMode={step === 1 ? "contained" : "scroll"} + footer={<> + {step > 0 && } + + } + > +
{ + event.preventDefault(); + if (step < 2) setStep(step === 0 ? 1 : 2); + else setComplete(true); + }}> + {step === 0 && setName(event.target.value)} />} + {step === 1 && <> + +