Skip to content

Commit 71bc498

Browse files
GCWingGCWing
andauthored
feat(flowchat): stabilize timeline and session pane layout (#3247)
* feat(flowchat): stabilize timeline and session pane layout * test(web-ui): align layout assertions with session pane behavior --------- Co-authored-by: GCWing <gcwing@foxmail.com>
1 parent 91b05f1 commit 71bc498

192 files changed

Lines changed: 7018 additions & 5232 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎design-system/apps/design-lab/src/i18n/componentMetadata.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const categoryKeys: Readonly<Record<string, MessageKey>> = {
1515
};
1616

1717
const descriptionKeys: Readonly<Record<string, MessageKey>> = {
18+
SplitView: "component.SplitView.description",
1819
ListModelsToolCard: "component.ListModelsToolCard.description",
1920
ControlHubToolCard: "component.ControlHubToolCard.description",
2021
ThinkingBlock: "component.ThinkingBlock.description",

‎design-system/apps/design-lab/src/i18n/messages.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { designEn, designZhCN, designZhTW } from "./designMessages";
55
import { flowChatEn, flowChatZhCN, flowChatZhTW } from "./flowChatMessages";
66
import { subagentMotionEn, subagentMotionZhCN, subagentMotionZhTW } from "./subagentMotionMessages";
77
import { thinkingIndicatorEn, thinkingIndicatorZhCN, thinkingIndicatorZhTW } from "./thinkingIndicatorMessages";
8+
import { splitViewEn, splitViewZhCN, splitViewZhTW } from "./splitViewMessages";
89
import type { DesignLabLocale } from "./core.mjs";
910

1011
export const enUSMessages = {
@@ -14,6 +15,7 @@ export const enUSMessages = {
1415
...brandAssetsEn,
1516
...subagentMotionEn,
1617
...thinkingIndicatorEn,
18+
...splitViewEn,
1719
...flowChatEn,
1820
"component.OpenBitFunSolidMark.description": "The original silver solid mark on a transparent canvas, preserving its material and shading.",
1921
"component.OpenBitFunAppIcon.description": "The application icon used by the installed product: silver mark, black rounded-square tile, and assets for different display sizes.",
@@ -818,6 +820,7 @@ export const zhCNMessages = {
818820
...brandAssetsZhCN,
819821
...subagentMotionZhCN,
820822
...thinkingIndicatorZhCN,
823+
...splitViewZhCN,
821824
...flowChatZhCN,
822825
"component.OpenBitFunSolidMark.description": "透明底的银色实体标志,保留原稿的材质、光泽与立体明暗。",
823826
"component.OpenBitFunAppIcon.description": "实际应用使用的图标:银色实体标志、黑色圆角底板,以及适配不同显示尺寸的资源。",
@@ -1620,6 +1623,7 @@ export const zhTWMessages = {
16201623
...brandAssetsZhTW,
16211624
...subagentMotionZhTW,
16221625
...thinkingIndicatorZhTW,
1626+
...splitViewZhTW,
16231627
...flowChatZhTW,
16241628
"patterns.description": "查看元件在常見介面中的組合與互動。",
16251629
"patterns.title": "組合範例",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export const splitViewEn = {
2+
'component.SplitView.description': 'A resizable pair of panes. Content swaps without moving the divider or remounting either pane.',
3+
'splitView.split': 'Show both panes',
4+
'splitView.hide': 'Hide content',
5+
'splitView.maximize': 'Maximize content',
6+
'splitView.resize': 'Resize panes',
7+
'splitView.swap': 'Swap panes',
8+
'splitView.primary': 'Conversation',
9+
'splitView.secondary': 'Content',
10+
'splitView.draft': 'Write a draft',
11+
'splitView.edit': 'Edit content',
12+
'splitView.help': 'Hover over the divider to swap panes. Drag or use arrow keys to resize; F6 moves focus between panes. Your text survives every layout change.',
13+
} as const;
14+
15+
export const splitViewZhCN = {
16+
'component.SplitView.description': '可调整大小的双窗格。交换内容时保留分隔线位置,两个窗格持续挂载。',
17+
'splitView.split': '显示双窗格',
18+
'splitView.hide': '隐藏内容',
19+
'splitView.maximize': '内容全屏',
20+
'splitView.resize': '调整窗格宽度',
21+
'splitView.swap': '交换左右窗格',
22+
'splitView.primary': '聊天',
23+
'splitView.secondary': '内容',
24+
'splitView.draft': '输入草稿',
25+
'splitView.edit': '编辑内容',
26+
'splitView.help': '悬停分隔线可交换窗格,拖动或使用方向键调整宽度,F6 切换窗格焦点。布局切换会保留已输入的文字。',
27+
} satisfies Record<keyof typeof splitViewEn, string>;
28+
29+
export const splitViewZhTW = {
30+
'component.SplitView.description': '可調整大小的雙窗格。交換內容時保留分隔線位置,兩個窗格持續掛載。',
31+
'splitView.split': '顯示雙窗格',
32+
'splitView.hide': '隱藏內容',
33+
'splitView.maximize': '內容全螢幕',
34+
'splitView.resize': '調整窗格寬度',
35+
'splitView.swap': '交換左右窗格',
36+
'splitView.primary': '聊天',
37+
'splitView.secondary': '內容',
38+
'splitView.draft': '輸入草稿',
39+
'splitView.edit': '編輯內容',
40+
'splitView.help': '懸停分隔線可交換窗格,拖動或使用方向鍵調整寬度,F6 切換窗格焦點。佈局切換會保留已輸入的文字。',
41+
} satisfies Record<keyof typeof splitViewEn, string>;

‎design-system/apps/design-lab/src/pages/ComponentDetailPage.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ import previewImage from "../assets/design-system-hero.webp";
130130
import { IconCompositionPreview } from "../preview/IconCompositionPreview";
131131
import { RollingTextPreview } from "../preview/RollingTextPreview";
132132
import { ThinkingIndicatorPreview } from "../preview/ThinkingIndicatorPreview";
133+
import { SplitViewPreview } from "../preview/SplitViewPreview";
133134
import { NestedMenuPattern } from "./ReferencePatterns";
134135
import { useI18n, type MessageKey } from "../i18n";
135136
import {
@@ -443,6 +444,9 @@ export function ComponentDetailPage({
443444
const inspectorStates = states;
444445

445446
const codeSample = useMemo(() => {
447+
if (component.name === "SplitView") {
448+
return `import { SplitView } from "@openbitfun/ui";\n\n<SplitView\n primary={<Chat />}\n secondary={<Content />}\n mode={mode}\n secondarySide={contentSide}\n rightSize={rightSize}\n onRightSizeChange={setRightSize}\n dividerLabel="${t('splitView.resize')}"\n dividerActions={<SwapButton onClick={swapPanes} />}\n/>`;
449+
}
446450
if (component.name === "ThinkingIndicator") {
447451
return `import { ThinkingIndicator } from "@openbitfun/ui";\n\n<ThinkingIndicator active={thinking} size="sm" label="${t("thinkingIndicator.label")}" />\n`;
448452
}
@@ -903,6 +907,7 @@ export function ComponentDetailPage({
903907
if (component.name === "ThinkingIndicator") {
904908
return <ThinkingIndicatorPreview key={state} state={state} />;
905909
}
910+
if (component.name === "SplitView") return <SplitViewPreview state={state} />;
906911

907912
if (component.name === "MobileButton") {
908913
return <MobileButton data-openbitfun-preview-state={state} disabled={state === "disabled"} loading={state === "loading"}>{t("components.preview.actionCardTitle")}</MobileButton>;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
.split-view-preview { display: grid; inline-size: 100%; min-inline-size: 0; gap: var(--openbitfun-space-4); }
2+
.split-view-preview__controls { display: flex; flex-wrap: wrap; gap: var(--openbitfun-space-2); }
3+
.split-view-preview__viewport {
4+
block-size: 20rem;
5+
border: var(--openbitfun-border-width-default) solid var(--openbitfun-color-border-subtle);
6+
border-radius: var(--openbitfun-radius-md);
7+
overflow: hidden;
8+
}
9+
.split-view-preview__pane {
10+
display: flex;
11+
flex-direction: column;
12+
gap: var(--openbitfun-space-4);
13+
padding: var(--openbitfun-space-4);
14+
min-inline-size: 0;
15+
color: var(--openbitfun-color-content-primary);
16+
font-family: var(--openbitfun-type-body-sm-font-family);
17+
font-size: var(--openbitfun-type-body-sm-font-size);
18+
font-weight: var(--openbitfun-type-body-sm-font-weight);
19+
line-height: var(--openbitfun-type-body-sm-line-height);
20+
letter-spacing: var(--openbitfun-type-body-sm-letter-spacing);
21+
}
22+
.split-view-preview__note {
23+
margin: 0;
24+
color: var(--openbitfun-color-content-secondary);
25+
font-family: var(--openbitfun-type-support-font-family);
26+
font-size: var(--openbitfun-type-support-font-size);
27+
font-weight: var(--openbitfun-type-support-font-weight);
28+
line-height: var(--openbitfun-type-support-line-height);
29+
letter-spacing: var(--openbitfun-type-support-letter-spacing);
30+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useEffect, useState } from 'react';
2+
import { ArrowLeftRight } from 'lucide-react';
3+
import { Button, Icon, IconButton, Input, SplitView, Tooltip, type SplitViewMode } from '@openbitfun/ui';
4+
import { useI18n } from '../i18n';
5+
import './SplitViewPreview.css';
6+
7+
export function SplitViewPreview({ state }: { state: string }) {
8+
const { t } = useI18n();
9+
const [mode, setMode] = useState<SplitViewMode>('split');
10+
const [side, setSide] = useState<'left' | 'right'>('right');
11+
const [rightSize, setRightSize] = useState(280);
12+
useEffect(() => { setMode(state === 'primary' || state === 'secondary' ? state : 'split'); }, [state]);
13+
14+
return <div className="split-view-preview">
15+
<div className="split-view-preview__controls">
16+
<Button size="sm" onClick={() => setMode('split')}>{t('splitView.split')}</Button>
17+
<Button size="sm" onClick={() => setMode('primary')}>{t('splitView.hide')}</Button>
18+
<Button size="sm" onClick={() => setMode('secondary')}>{t('splitView.maximize')}</Button>
19+
</div>
20+
<div className="split-view-preview__viewport">
21+
<SplitView mode={mode} secondarySide={side} rightSize={rightSize} onRightSizeChange={setRightSize}
22+
minLeftSize={180} minRightSize={180} defaultRightSize={280} dividerLabel={t('splitView.resize')}
23+
dividerActions={<Tooltip content={t('splitView.swap')}><IconButton size="sm" aria-label={t('splitView.swap')}
24+
icon={<Icon glyph={ArrowLeftRight} size="sm" />} onClick={() => setSide(value => value === 'right' ? 'left' : 'right')} /></Tooltip>}
25+
primary={<div className="split-view-preview__pane"><strong>{t('splitView.primary')}</strong><Input aria-label={t('splitView.draft')} placeholder={t('splitView.draft')} /></div>}
26+
secondary={<div className="split-view-preview__pane"><strong>{t('splitView.secondary')}</strong><Input aria-label={t('splitView.edit')} placeholder={t('splitView.edit')} /></div>}
27+
/>
28+
</div>
29+
<p className="split-view-preview__note">{t('splitView.help')}</p>
30+
</div>;
31+
}

‎design-system/apps/design-lab/src/preview/componentPresentation.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const presentations: Record<string, ComponentPresentation> = {
1717
Dialog: "interactive", ConfirmDialog: "interactive", Sheet: "interactive",
1818
Combobox: "interactive", MultiSelect: "interactive", Select: "interactive", Tooltip: "interactive",
1919
Disclosure: "composition", Composer: "composition", Listbox: "composition", Menu: "composition",
20-
NavigationPanel: "composition", PageHeader: "composition", ScrollArea: "composition",
20+
NavigationPanel: "composition", PageHeader: "composition", ScrollArea: "composition", SplitView: "composition",
2121
SegmentedControl: "composition", TabGroup: "composition",
2222
Icon: "icons", RollingText: "motion", ShimmerText: "motion", VoiceCallPanel: "motion",
2323
ThinkingIndicator: "motion",

‎design-system/packages/theme-openbitfun/src/dark.tokens.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@
150150
}
151151
},
152152
"link": {
153-
"default": { "$type": "color", "$value": "{ref.color.blue.400}" },
154-
"hover": { "$type": "color", "$value": "{ref.color.blue.300}" }
153+
"default": { "$type": "color", "$value": "{ref.color.cyan.500}" },
154+
"hover": { "$type": "color", "$value": "{ref.color.cyan.400}" }
155155
},
156156
"border": {
157157
"subtle": { "$type": "color", "$value": "rgba(255, 255, 255, 0.12)" },

‎design-system/packages/theme-openbitfun/src/high-contrast-dark.tokens.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@
4242
"secondary": { "$type": "color", "$value": "{ref.color.gray.50}" },
4343
"muted": { "$type": "color", "$value": "{ref.color.gray.150}" }
4444
},
45+
"link": {
46+
"default": { "$type": "color", "$value": "{ref.color.cyan.300}" },
47+
"hover": { "$type": "color", "$value": "{ref.color.cyan.200}" }
48+
},
4549
"border": {
4650
"subtle": { "$type": "color", "$value": "{ref.color.gray.400}" },
4751
"default": { "$type": "color", "$value": "{ref.color.gray.250}" },

‎design-system/packages/theme-openbitfun/src/high-contrast-light.tokens.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@
4242
"secondary": { "$type": "color", "$value": "{ref.color.gray.800}" },
4343
"muted": { "$type": "color", "$value": "{ref.color.gray.700}" }
4444
},
45+
"link": {
46+
"default": { "$type": "color", "$value": "{ref.color.cyan.700}" },
47+
"hover": { "$type": "color", "$value": "{ref.color.cyan.800}" }
48+
},
4549
"border": {
4650
"subtle": { "$type": "color", "$value": "{ref.color.gray.500}" },
4751
"default": { "$type": "color", "$value": "{ref.color.gray.650}" },

0 commit comments

Comments
 (0)