@cueloop/client 0.1.0-alpha.65 → 0.1.0-alpha.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -5
- package/src/App.autoclose.test.tsx +2 -2
- package/src/App.diff.test.tsx +79 -112
- package/src/App.plan-review.test.tsx +23 -205
- package/src/App.prototype.test.tsx +1 -8
- package/src/App.readonly.test.tsx +9 -27
- package/src/App.submit-confirm.test.tsx +19 -72
- package/src/App.test.tsx +136 -31
- package/src/App.tsx +491 -322
- package/src/App.walk.test.tsx +15 -16
- package/src/App.workbench.test.tsx +390 -0
- package/src/__snapshots__/view-plan.regression.test.ts.snap +1 -1
- package/src/annotation-palette.ts +61 -0
- package/src/app-screens.tsx +197 -77
- package/src/app-view-model.ts +9 -69
- package/src/components/AnnotationCard.stories.tsx +1 -1
- package/src/components/AnnotationCard.tsx +1 -1
- package/src/components/AnnotationCards.stories.tsx +87 -0
- package/src/components/AnnotationCards.tsx +303 -0
- package/src/components/AppHeader.stories.tsx +50 -0
- package/src/components/AppHeader.tsx +83 -0
- package/src/components/AppShell.stories.tsx +89 -0
- package/src/components/AppShell.tsx +239 -0
- package/src/components/AppShell.zoom.test.tsx +94 -0
- package/src/components/Breadcrumb.stories.tsx +1 -1
- package/src/components/ChangesColumn.stories.tsx +39 -0
- package/src/components/ChangesColumn.tsx +170 -0
- package/src/components/CodeBlock.stories.tsx +1 -1
- package/src/components/CompletionOverlay.stories.tsx +1 -1
- package/src/components/ConfirmCard.stories.tsx +1 -1
- package/src/components/ConfirmCard.tsx +3 -4
- package/src/components/ConfirmDialog.stories.tsx +1 -1
- package/src/components/DiffSheet.scroll.test.tsx +38 -55
- package/src/components/DiffSheet.split.test.tsx +140 -0
- package/src/components/DiffSheet.stories.tsx +56 -54
- package/src/components/DiffSheet.syntax.test.tsx +24 -7
- package/src/components/DiffSheet.tsx +607 -276
- package/src/components/DiscussionMarkerRail.stories.tsx +54 -0
- package/src/components/DiscussionMarkerRail.tsx +181 -0
- package/src/components/EditorGrid.stories.tsx +59 -0
- package/src/components/EditorGrid.tabs.test.tsx +105 -0
- package/src/components/EditorGrid.tsx +338 -0
- package/src/components/EditorGrid.zoom.test.tsx +68 -0
- package/src/components/FileContentsView.stories.tsx +16 -0
- package/src/components/FileContentsView.test.tsx +29 -0
- package/src/components/FileContentsView.tsx +78 -0
- package/src/components/InboxList.stories.tsx +23 -8
- package/src/components/InboxList.tsx +188 -33
- package/src/components/KeybindsDialog.stories.tsx +1 -1
- package/src/components/MarkerPopover.stories.tsx +1 -1
- package/src/components/MenuBar.stories.tsx +3 -12
- package/src/components/MenuBar.tsx +25 -56
- package/src/components/PaneGrid.stories.tsx +84 -0
- package/src/components/PaneGrid.tsx +128 -0
- package/src/components/PanelColumn.stories.tsx +25 -0
- package/src/components/PanelColumn.tsx +95 -0
- package/src/components/ProjectTreeView.stories.tsx +25 -0
- package/src/components/ProjectTreeView.test.tsx +32 -0
- package/src/components/ProjectTreeView.tsx +82 -0
- package/src/components/PromptDialog.stories.tsx +1 -1
- package/src/components/PromptDialog.tsx +8 -1
- package/src/components/PrototypeSheet.stories.tsx +1 -1
- package/src/components/RootOverlay.stories.tsx +44 -0
- package/src/components/RootOverlay.tsx +35 -0
- package/src/components/SettingsDialog.stories.tsx +7 -1
- package/src/components/SettingsDialog.tsx +114 -67
- package/src/components/SettingsRows.stories.tsx +1 -1
- package/src/components/ShellHeader.stories.tsx +50 -0
- package/src/components/ShellHeader.tsx +137 -0
- package/src/components/SurfaceScrollbar.stories.tsx +34 -0
- package/src/components/SurfaceScrollbar.test.tsx +111 -0
- package/src/components/SurfaceScrollbar.tsx +93 -0
- package/src/components/ThreadFooter.stories.tsx +30 -0
- package/src/components/ThreadFooter.test.tsx +31 -0
- package/src/components/ThreadFooter.tsx +54 -0
- package/src/components/ThreadView.slash.test.tsx +113 -0
- package/src/components/ThreadView.stories.tsx +1 -1
- package/src/components/ThreadView.tsx +62 -1352
- package/src/components/ThreadsSidebar.stories.tsx +36 -0
- package/src/components/ThreadsSidebar.tsx +70 -0
- package/src/components/Toast.stories.tsx +1 -1
- package/src/components/Tooltip.stories.tsx +26 -0
- package/src/components/Tooltip.tsx +72 -0
- package/src/components/TreePane.stories.tsx +1 -1
- package/src/components/WalkWizard.stories.tsx +1 -1
- package/src/components/WelcomeSurface.stories.tsx +12 -0
- package/src/components/WelcomeSurface.tsx +52 -0
- package/src/components/__snapshots__/stories.test.tsx.snap +732 -572
- package/src/components/agent-launcher.stories.tsx +1 -1
- package/src/components/diff-sheet-layout.test.ts +125 -55
- package/src/components/diff-sheet-layout.ts +3 -103
- package/src/components/editor-grid.test.ts +86 -0
- package/src/components/editor-grid.ts +138 -0
- package/src/components/fade-title.test.ts +49 -0
- package/src/components/fade-title.ts +61 -0
- package/src/components/file-tree.test.ts +60 -0
- package/src/components/file-tree.ts +112 -0
- package/src/components/pages/Workspace.stories.tsx +181 -0
- package/src/components/primitives/Button.stories.tsx +1 -1
- package/src/components/primitives/Card.stories.tsx +1 -1
- package/src/components/primitives/Dialog.stories.tsx +1 -1
- package/src/components/primitives/IconButton.stories.tsx +36 -0
- package/src/components/primitives/IconButton.tsx +62 -0
- package/src/components/primitives/StatusBar.stories.tsx +1 -1
- package/src/components/primitives/Tabs.stories.tsx +1 -1
- package/src/components/primitives/Toolbar.stories.tsx +1 -1
- package/src/components/primitives/Tree.stories.tsx +124 -0
- package/src/components/primitives/Tree.tsx +96 -0
- package/src/components/primitives/icons.ts +61 -0
- package/src/components/primitives/tree-model.test.ts +99 -0
- package/src/components/primitives/tree-model.ts +137 -0
- package/src/components/quick-actions-editor.stories.tsx +1 -1
- package/src/components/session-tree.test.ts +134 -0
- package/src/components/session-tree.ts +108 -0
- package/src/components/stories-app.tsx +142 -82
- package/src/components/story-tree.test.ts +100 -0
- package/src/components/story.ts +55 -0
- package/src/components/theme-context.stories.tsx +1 -1
- package/src/config.test.ts +20 -49
- package/src/config.ts +26 -28
- package/src/diff-fold.test.ts +78 -0
- package/src/diff-fold.ts +126 -0
- package/src/diff-syntax.test.ts +16 -0
- package/src/diff-syntax.ts +6 -3
- package/src/discussions.ts +79 -0
- package/src/intent-dispatch.test.ts +31 -11
- package/src/intent-dispatch.ts +93 -48
- package/src/key-bindings.test.ts +1 -1
- package/src/keymap.test.ts +3 -25
- package/src/keymap.ts +17 -18
- package/src/mark-runs.ts +120 -0
- package/src/pty.ts +10 -2
- package/src/run.ts +17 -7
- package/src/serve.test.ts +4 -8
- package/src/session-controller.curate.test.ts +75 -0
- package/src/session-controller.share.test.ts +1 -0
- package/src/session-controller.tree.test.ts +26 -0
- package/src/session-controller.ts +160 -31
- package/src/slash-palette.ts +83 -0
- package/src/split-diff.test.ts +110 -0
- package/src/split-diff.ts +149 -0
- package/src/test-support.test.ts +3 -3
- package/src/test-support.ts +39 -8
- package/src/theme.ts +4 -4
- package/src/thread-chords.test.ts +25 -9
- package/src/thread-chords.ts +33 -8
- package/src/use-annotation-surface.tsx +837 -0
- package/src/use-changes-workbench.ts +151 -0
- package/src/use-frame-measure.ts +38 -0
- package/src/use-settings-dialog.tsx +23 -24
- package/src/use-terminal-virtualizer.ts +233 -0
- package/src/view-diff.ts +114 -0
- package/src/App.review-panel.test.tsx +0 -140
- package/src/App.tree.test.tsx +0 -224
- package/src/components/ReviewPanel.stories.tsx +0 -100
- package/src/components/ReviewPanel.tsx +0 -103
- package/src/components/ReviewRail.stories.tsx +0 -232
- package/src/components/ReviewRail.tsx +0 -327
- package/src/review-panel.test.ts +0 -113
- package/src/review-panel.ts +0 -83
|
@@ -1,61 +1,216 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The pending-review inbox: a
|
|
3
|
-
* the keyboard grammar
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* The pending-review inbox: a Pinned / Projects / Threads tree. Selection stays
|
|
3
|
+
* with the keyboard grammar - the cursor indexes the flat thread order and this
|
|
4
|
+
* component renders the snapshot. A hovered or selected thread reveals a kebab
|
|
5
|
+
* that opens an inline pin / rename / delete menu; long titles fade on the right
|
|
6
|
+
* instead of wrapping. App supplies the surrounding chrome.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import React from "react";
|
|
10
|
-
import type { ReviewSession } from "@cueloop/schema";
|
|
9
|
+
import React, { useState } from "react";
|
|
11
10
|
import type { Theme } from "../theme";
|
|
12
11
|
import { useComponentTheme } from "./theme-context";
|
|
13
|
-
import {
|
|
12
|
+
import { IconButton } from "./primitives/IconButton";
|
|
13
|
+
import { NERD } from "./primitives/icons";
|
|
14
|
+
import { fadeTitle } from "./fade-title";
|
|
15
|
+
import { threadTitle, type InboxRow } from "./session-tree";
|
|
14
16
|
|
|
15
17
|
export interface InboxListProps {
|
|
16
|
-
|
|
18
|
+
rows: InboxRow[];
|
|
17
19
|
cursor: number;
|
|
18
|
-
/**
|
|
20
|
+
/** The open thread's id; highlights it instead of the cursor (the left sidebar case). */
|
|
21
|
+
activeId?: string;
|
|
22
|
+
/** Ids of pinned threads; a pinned row carries the pin glyph and the menu offers Unpin. */
|
|
23
|
+
pinnedIds?: ReadonlySet<string>;
|
|
24
|
+
/** The column width, so titles fade to fit one line. */
|
|
25
|
+
width?: number;
|
|
26
|
+
/** Open a thread by clicking its row (the left sidebar case). */
|
|
27
|
+
onSelect?: (sessionId: string) => void;
|
|
28
|
+
/** Ask to delete a thread (the menu's Delete, wired to the confirm dialog). */
|
|
19
29
|
onRequestDelete?: (id: string, title: string) => void;
|
|
30
|
+
/** Toggle a thread's pinned state (the menu's Pin/Unpin). */
|
|
31
|
+
onPin?: (id: string) => void;
|
|
32
|
+
/** Rename a thread's title (the menu's Rename). */
|
|
33
|
+
onRename?: (id: string, title: string) => void;
|
|
20
34
|
theme?: Theme;
|
|
21
35
|
}
|
|
22
36
|
|
|
37
|
+
function ActionsMenu({
|
|
38
|
+
pinned,
|
|
39
|
+
onPin,
|
|
40
|
+
onRename,
|
|
41
|
+
onDelete,
|
|
42
|
+
tokens,
|
|
43
|
+
}: {
|
|
44
|
+
pinned: boolean;
|
|
45
|
+
onPin?: () => void;
|
|
46
|
+
onRename?: () => void;
|
|
47
|
+
onDelete?: () => void;
|
|
48
|
+
tokens: Theme;
|
|
49
|
+
}): React.ReactNode {
|
|
50
|
+
const item = (
|
|
51
|
+
glyph: string,
|
|
52
|
+
label: string,
|
|
53
|
+
color: string,
|
|
54
|
+
onPick?: () => void,
|
|
55
|
+
): React.ReactNode =>
|
|
56
|
+
onPick !== undefined ? (
|
|
57
|
+
<box onMouseUp={onPick} style={{ flexDirection: "row", paddingLeft: 4, paddingRight: 1 }}>
|
|
58
|
+
<text fg={color}>{`${glyph} ${label}`}</text>
|
|
59
|
+
</box>
|
|
60
|
+
) : null;
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<box
|
|
64
|
+
style={{
|
|
65
|
+
flexDirection: "column",
|
|
66
|
+
marginLeft: 3,
|
|
67
|
+
borderStyle: "single",
|
|
68
|
+
border: ["left"],
|
|
69
|
+
borderColor: tokens.border,
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
{item(NERD.pin, pinned ? "Unpin" : "Pin", tokens.text, onPin)}
|
|
73
|
+
{item(NERD.file, "Rename", tokens.text, onRename)}
|
|
74
|
+
{item(NERD.close, "Delete", tokens.red, onDelete)}
|
|
75
|
+
</box>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface ThreadRowProps {
|
|
80
|
+
title: string;
|
|
81
|
+
selected: boolean;
|
|
82
|
+
pinned: boolean;
|
|
83
|
+
titleWidth: number;
|
|
84
|
+
menuOpen: boolean;
|
|
85
|
+
onToggleMenu: () => void;
|
|
86
|
+
onSelect?: () => void;
|
|
87
|
+
onPin?: () => void;
|
|
88
|
+
onRename?: () => void;
|
|
89
|
+
onDelete?: () => void;
|
|
90
|
+
tokens: Theme;
|
|
91
|
+
theme?: Theme;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function ThreadRow(props: ThreadRowProps): React.ReactNode {
|
|
95
|
+
const { title, selected, pinned, titleWidth, menuOpen, onToggleMenu, tokens, theme } = props;
|
|
96
|
+
const [hovered, setHovered] = useState(false);
|
|
97
|
+
// the tail fades toward the row's own background, so it masks out instead of
|
|
98
|
+
// leaving a colored band over the selected (elevated) or hovered (panel) row
|
|
99
|
+
const rowBackground = selected ? tokens.elevated : hovered ? tokens.panel : tokens.background;
|
|
100
|
+
const segments = fadeTitle(
|
|
101
|
+
title,
|
|
102
|
+
titleWidth,
|
|
103
|
+
selected ? tokens.accent : tokens.textMuted,
|
|
104
|
+
rowBackground,
|
|
105
|
+
);
|
|
106
|
+
const hasActions =
|
|
107
|
+
props.onPin !== undefined || props.onRename !== undefined || props.onDelete !== undefined;
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<box style={{ flexDirection: "column" }} onMouseOut={() => setHovered(false)}>
|
|
111
|
+
<box
|
|
112
|
+
onMouseUp={props.onSelect}
|
|
113
|
+
onMouseOver={() => setHovered(true)}
|
|
114
|
+
style={{
|
|
115
|
+
flexDirection: "row",
|
|
116
|
+
backgroundColor: selected ? tokens.elevated : hovered ? tokens.panel : undefined,
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
<text>
|
|
120
|
+
<span fg={tokens.textDim}>{pinned ? ` ${NERD.pin} ` : " "}</span>
|
|
121
|
+
{segments.map((segment, index) => (
|
|
122
|
+
<span key={index} fg={segment.fg}>
|
|
123
|
+
{segment.text}
|
|
124
|
+
</span>
|
|
125
|
+
))}
|
|
126
|
+
</text>
|
|
127
|
+
<box style={{ flexGrow: 1 }} />
|
|
128
|
+
{(hovered || menuOpen) && hasActions ? (
|
|
129
|
+
<IconButton
|
|
130
|
+
glyph={NERD.kebab}
|
|
131
|
+
active={menuOpen}
|
|
132
|
+
onPress={onToggleMenu}
|
|
133
|
+
marginRight={1}
|
|
134
|
+
theme={theme}
|
|
135
|
+
/>
|
|
136
|
+
) : null}
|
|
137
|
+
</box>
|
|
138
|
+
{menuOpen ? (
|
|
139
|
+
<ActionsMenu
|
|
140
|
+
pinned={pinned}
|
|
141
|
+
onPin={props.onPin}
|
|
142
|
+
onRename={props.onRename}
|
|
143
|
+
onDelete={props.onDelete}
|
|
144
|
+
tokens={tokens}
|
|
145
|
+
/>
|
|
146
|
+
) : null}
|
|
147
|
+
</box>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
23
151
|
export function InboxList({
|
|
24
|
-
|
|
152
|
+
rows,
|
|
25
153
|
cursor,
|
|
154
|
+
activeId,
|
|
155
|
+
pinnedIds,
|
|
156
|
+
width = 30,
|
|
157
|
+
onSelect,
|
|
26
158
|
onRequestDelete,
|
|
159
|
+
onPin,
|
|
160
|
+
onRename,
|
|
27
161
|
theme,
|
|
28
162
|
}: InboxListProps): React.ReactNode {
|
|
29
163
|
const tokens = useComponentTheme(theme);
|
|
164
|
+
const [actionsForId, setActionsForId] = useState<string | null>(null);
|
|
165
|
+
// leave room for the 3-char prefix, the border, and the hover kebab so a row
|
|
166
|
+
// never wraps: the title fades to fit whatever is left
|
|
167
|
+
const titleWidth = Math.max(8, width - 9);
|
|
168
|
+
const closeMenu = (): void => setActionsForId(null);
|
|
30
169
|
|
|
31
170
|
return (
|
|
32
171
|
<box style={{ flexGrow: 1, flexDirection: "column", paddingLeft: 1, paddingTop: 1 }}>
|
|
33
|
-
{
|
|
172
|
+
{rows.length === 0 ? (
|
|
34
173
|
<text fg={tokens.textDim}>nothing waiting for review</text>
|
|
35
174
|
) : (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
175
|
+
rows.map((row) => {
|
|
176
|
+
if (row.kind === "section") {
|
|
177
|
+
return (
|
|
178
|
+
<text key={row.id} fg={tokens.textDim}>
|
|
179
|
+
{row.label}
|
|
180
|
+
</text>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
39
183
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
key={
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
backgroundColor: selected ? tokens.cursorBackground : undefined,
|
|
46
|
-
}}
|
|
47
|
-
>
|
|
48
|
-
<text fg={selected ? tokens.text : tokens.textMuted}>
|
|
49
|
-
{selected ? "> " : " "}
|
|
50
|
-
{title} · {session.workspace.branch} · {session.artifact.type}
|
|
184
|
+
if (row.kind === "project") {
|
|
185
|
+
return (
|
|
186
|
+
<text key={row.id}>
|
|
187
|
+
<span fg={tokens.blue}>{` ${NERD.folderOpen} `}</span>
|
|
188
|
+
<span fg={tokens.textMuted}>{row.label}</span>
|
|
51
189
|
</text>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const id = row.session.id;
|
|
194
|
+
const title = threadTitle(row.session);
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<ThreadRow
|
|
198
|
+
key={row.id}
|
|
199
|
+
title={title}
|
|
200
|
+
selected={activeId !== undefined ? activeId === id : row.selectionIndex === cursor}
|
|
201
|
+
pinned={pinnedIds?.has(id) ?? false}
|
|
202
|
+
titleWidth={titleWidth}
|
|
203
|
+
menuOpen={actionsForId === id}
|
|
204
|
+
onToggleMenu={() => setActionsForId(actionsForId === id ? null : id)}
|
|
205
|
+
onSelect={onSelect ? () => onSelect(id) : undefined}
|
|
206
|
+
onPin={onPin ? () => (onPin(id), closeMenu()) : undefined}
|
|
207
|
+
onRename={onRename ? () => (onRename(id, title), closeMenu()) : undefined}
|
|
208
|
+
onDelete={
|
|
209
|
+
onRequestDelete ? () => (onRequestDelete(id, title), closeMenu()) : undefined
|
|
210
|
+
}
|
|
211
|
+
tokens={tokens}
|
|
212
|
+
theme={theme}
|
|
213
|
+
/>
|
|
59
214
|
);
|
|
60
215
|
})
|
|
61
216
|
)}
|
|
@@ -3,7 +3,7 @@ import { DARK } from "../theme";
|
|
|
3
3
|
import type { Story, StoryMeta } from "./story";
|
|
4
4
|
import { KeybindsDialog } from "./KeybindsDialog";
|
|
5
5
|
|
|
6
|
-
export const meta: StoryMeta = { title: "KeybindsDialog" };
|
|
6
|
+
export const meta: StoryMeta = { title: "Overlays/KeybindsDialog" };
|
|
7
7
|
|
|
8
8
|
const SECTIONS = [
|
|
9
9
|
{
|
|
@@ -4,7 +4,7 @@ import { DEFAULT_QUICK_ACTIONS } from "../config";
|
|
|
4
4
|
import type { Story, StoryMeta } from "./story";
|
|
5
5
|
import { MarkerPopover } from "./MarkerPopover";
|
|
6
6
|
|
|
7
|
-
export const meta: StoryMeta = { title: "MarkerPopover" };
|
|
7
|
+
export const meta: StoryMeta = { title: "Overlays/MarkerPopover" };
|
|
8
8
|
|
|
9
9
|
const callbacks = {
|
|
10
10
|
onComment: () => {},
|
|
@@ -3,9 +3,9 @@ import { DARK } from "../theme";
|
|
|
3
3
|
import type { Story, StoryMeta } from "./story";
|
|
4
4
|
import { MenuBar } from "./MenuBar";
|
|
5
5
|
|
|
6
|
-
export const meta: StoryMeta = { title: "MenuBar" };
|
|
6
|
+
export const meta: StoryMeta = { title: "Chrome/MenuBar" };
|
|
7
7
|
|
|
8
|
-
const callbacks = {
|
|
8
|
+
const callbacks = { onSettings: () => {}, onKeybinds: () => {} };
|
|
9
9
|
|
|
10
10
|
function BottomFrame({ children }: { children: React.ReactNode }): React.ReactNode {
|
|
11
11
|
return (
|
|
@@ -17,19 +17,10 @@ function BottomFrame({ children }: { children: React.ReactNode }): React.ReactNo
|
|
|
17
17
|
);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export const Closed: Story = {
|
|
21
|
-
render: () => (
|
|
22
|
-
<BottomFrame>
|
|
23
|
-
<MenuBar open={false} version="0.1.0-alpha.32" {...callbacks} />
|
|
24
|
-
</BottomFrame>
|
|
25
|
-
),
|
|
26
|
-
size: { width: 60, height: 8 },
|
|
27
|
-
};
|
|
28
|
-
|
|
29
20
|
export const Open: Story = {
|
|
30
21
|
render: () => (
|
|
31
22
|
<BottomFrame>
|
|
32
|
-
<MenuBar open
|
|
23
|
+
<MenuBar open {...callbacks} />
|
|
33
24
|
</BottomFrame>
|
|
34
25
|
),
|
|
35
26
|
expectedColors: [DARK.text],
|
|
@@ -1,75 +1,44 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { useTerminalDimensions } from "@opentui/react";
|
|
3
2
|
import type { Theme } from "../theme";
|
|
4
3
|
import { useComponentTheme } from "./theme-context";
|
|
5
4
|
import { FRAME_BORDER_STYLE } from "./primitives/frame";
|
|
6
5
|
|
|
7
6
|
export interface MenuBarProps {
|
|
8
7
|
open: boolean;
|
|
9
|
-
version: string;
|
|
10
|
-
/** The transient status line, shown between the menu and the version. */
|
|
11
|
-
status?: string;
|
|
12
|
-
onToggle: () => void;
|
|
13
8
|
onSettings: () => void;
|
|
14
9
|
onKeybinds: () => void;
|
|
15
10
|
theme?: Theme;
|
|
16
11
|
}
|
|
17
12
|
|
|
18
|
-
const
|
|
13
|
+
const DROPDOWN_OPTION_ROWS = 2;
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
version,
|
|
23
|
-
status,
|
|
24
|
-
onToggle,
|
|
25
|
-
onSettings,
|
|
26
|
-
onKeybinds,
|
|
27
|
-
theme,
|
|
28
|
-
}: MenuBarProps): React.ReactNode {
|
|
15
|
+
/** The settings gear's drop-down: Settings and Keybinds, below the top-left gear. */
|
|
16
|
+
export function MenuBar({ open, onSettings, onKeybinds, theme }: MenuBarProps): React.ReactNode {
|
|
29
17
|
const tokens = useComponentTheme(theme);
|
|
30
|
-
const
|
|
31
|
-
|
|
18
|
+
const dropdownHeight = DROPDOWN_OPTION_ROWS + 2;
|
|
19
|
+
|
|
20
|
+
if (!open) return null;
|
|
32
21
|
|
|
33
22
|
return (
|
|
34
|
-
|
|
35
|
-
{
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<box onMouseUp={onKeybinds}>
|
|
53
|
-
<text fg={tokens.text}> Keybinds</text>
|
|
54
|
-
</box>
|
|
55
|
-
</box>
|
|
56
|
-
) : null}
|
|
57
|
-
<box
|
|
58
|
-
style={{
|
|
59
|
-
flexDirection: "row",
|
|
60
|
-
height: 1,
|
|
61
|
-
backgroundColor: tokens.panel,
|
|
62
|
-
paddingLeft: 1,
|
|
63
|
-
paddingRight: 1,
|
|
64
|
-
}}
|
|
65
|
-
>
|
|
66
|
-
<box onMouseUp={onToggle}>
|
|
67
|
-
<text fg={tokens.textDim}>menu</text>
|
|
68
|
-
</box>
|
|
69
|
-
{status ? <text fg={tokens.accent}>{` ${status}`}</text> : null}
|
|
70
|
-
<box style={{ flexGrow: 1 }} />
|
|
71
|
-
<text fg={tokens.textDim}>v{version}</text>
|
|
23
|
+
<box
|
|
24
|
+
style={{
|
|
25
|
+
position: "absolute",
|
|
26
|
+
left: 1,
|
|
27
|
+
top: 2,
|
|
28
|
+
width: 16,
|
|
29
|
+
height: dropdownHeight,
|
|
30
|
+
border: true,
|
|
31
|
+
borderStyle: FRAME_BORDER_STYLE,
|
|
32
|
+
borderColor: tokens.text,
|
|
33
|
+
flexDirection: "column",
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
<box onMouseUp={onSettings}>
|
|
37
|
+
<text fg={tokens.text}> Settings</text>
|
|
38
|
+
</box>
|
|
39
|
+
<box onMouseUp={onKeybinds}>
|
|
40
|
+
<text fg={tokens.text}> Keybinds</text>
|
|
72
41
|
</box>
|
|
73
|
-
|
|
42
|
+
</box>
|
|
74
43
|
);
|
|
75
44
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "./story";
|
|
4
|
+
import { PaneGrid } from "./PaneGrid";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Layout/PaneGrid" };
|
|
7
|
+
|
|
8
|
+
function Sidebar(): React.ReactNode {
|
|
9
|
+
return (
|
|
10
|
+
<box style={{ flexDirection: "column", paddingLeft: 1, paddingTop: 1 }}>
|
|
11
|
+
<text fg={DARK.textDim}>Projects</text>
|
|
12
|
+
<text fg={DARK.text}> cueloop</text>
|
|
13
|
+
<box style={{ backgroundColor: DARK.elevated }}>
|
|
14
|
+
<text fg={DARK.accent}> Read Cueloop Repository</text>
|
|
15
|
+
</box>
|
|
16
|
+
<text> </text>
|
|
17
|
+
<text fg={DARK.textDim}>Threads</text>
|
|
18
|
+
<text fg={DARK.textMuted}> Welcome to cueloop</text>
|
|
19
|
+
</box>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function Main(): React.ReactNode {
|
|
24
|
+
return (
|
|
25
|
+
<box style={{ flexDirection: "column", paddingLeft: 1, paddingTop: 1 }}>
|
|
26
|
+
<text fg={DARK.text}>read the repo</text>
|
|
27
|
+
<text> </text>
|
|
28
|
+
<text fg={DARK.textMuted}>Repository context loaded. main is clean.</text>
|
|
29
|
+
<text> </text>
|
|
30
|
+
<text fg={DARK.text}>Core architecture</text>
|
|
31
|
+
<text fg={DARK.textMuted}> - packages/schema is the dependency root</text>
|
|
32
|
+
<text fg={DARK.textMuted}> - packages/daemon owns session state</text>
|
|
33
|
+
</box>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function Inspector(): React.ReactNode {
|
|
38
|
+
return (
|
|
39
|
+
<box style={{ flexDirection: "column", paddingLeft: 1, paddingTop: 1 }}>
|
|
40
|
+
<text fg={DARK.text}>cueloop/main</text>
|
|
41
|
+
<text fg={DARK.textDim}> No changes</text>
|
|
42
|
+
</box>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function Header(): React.ReactNode {
|
|
47
|
+
return (
|
|
48
|
+
<box style={{ flexDirection: "row", paddingLeft: 1 }}>
|
|
49
|
+
<text fg={DARK.textMuted}>Read Cueloop Repository</text>
|
|
50
|
+
<box style={{ flexGrow: 1 }} />
|
|
51
|
+
<text fg={DARK.textDim}>markdown.test.ts</text>
|
|
52
|
+
</box>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function Footer(): React.ReactNode {
|
|
57
|
+
return (
|
|
58
|
+
<box style={{ flexDirection: "row", paddingLeft: 1 }}>
|
|
59
|
+
<text fg={DARK.textDim}>cueloop / main</text>
|
|
60
|
+
<box style={{ flexGrow: 1 }} />
|
|
61
|
+
<text fg={DARK.textDim}>Fable 5</text>
|
|
62
|
+
</box>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const ThreePane: Story = {
|
|
67
|
+
render: () => (
|
|
68
|
+
<PaneGrid
|
|
69
|
+
sidebar={<Sidebar />}
|
|
70
|
+
main={<Main />}
|
|
71
|
+
mainHeader={<Header />}
|
|
72
|
+
inspector={<Inspector />}
|
|
73
|
+
footer={<Footer />}
|
|
74
|
+
/>
|
|
75
|
+
),
|
|
76
|
+
expectedColors: [DARK.border, DARK.accent],
|
|
77
|
+
size: { width: 120, height: 28 },
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const SidebarAndMain: Story = {
|
|
81
|
+
render: () => <PaneGrid sidebar={<Sidebar />} main={<Main />} />,
|
|
82
|
+
expectedColors: [DARK.border],
|
|
83
|
+
size: { width: 90, height: 20 },
|
|
84
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// A generic pane grid: a row of columns, each stacking a header cell over its body, with the
|
|
2
|
+
// vertical rules run full height and every header cell's bottom rule joined into one horizontal
|
|
3
|
+
// line. Rules only - no enclosing frames. This is the layout primitive for dev surfaces (the
|
|
4
|
+
// storybook runner); the product's four-pane workbench is AppShell.
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { DARK, type Theme } from "../theme";
|
|
8
|
+
import type { BorderSides } from "@opentui/core";
|
|
9
|
+
|
|
10
|
+
export interface PaneGridProps {
|
|
11
|
+
headerHeight?: number;
|
|
12
|
+
sidebar?: React.ReactNode;
|
|
13
|
+
sidebarHeader?: React.ReactNode;
|
|
14
|
+
sidebarWidth?: number;
|
|
15
|
+
main: React.ReactNode;
|
|
16
|
+
mainHeader?: React.ReactNode;
|
|
17
|
+
inspector?: React.ReactNode;
|
|
18
|
+
inspectorHeader?: React.ReactNode;
|
|
19
|
+
inspectorWidth?: number;
|
|
20
|
+
footer?: React.ReactNode;
|
|
21
|
+
theme?: Theme;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface PaneColumnProps {
|
|
25
|
+
width?: number;
|
|
26
|
+
grow?: boolean;
|
|
27
|
+
side?: BorderSides;
|
|
28
|
+
header: React.ReactNode;
|
|
29
|
+
body: React.ReactNode;
|
|
30
|
+
headerHeight: number;
|
|
31
|
+
theme: Theme;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function PaneColumn({
|
|
35
|
+
width,
|
|
36
|
+
grow,
|
|
37
|
+
side,
|
|
38
|
+
header,
|
|
39
|
+
body,
|
|
40
|
+
headerHeight,
|
|
41
|
+
theme,
|
|
42
|
+
}: PaneColumnProps): React.ReactNode {
|
|
43
|
+
return (
|
|
44
|
+
<box
|
|
45
|
+
style={{
|
|
46
|
+
flexDirection: "column",
|
|
47
|
+
...(grow ? { flexGrow: 1 } : { width }),
|
|
48
|
+
borderStyle: "single",
|
|
49
|
+
border: side ? [side] : [],
|
|
50
|
+
borderColor: theme.border,
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<box
|
|
54
|
+
style={{
|
|
55
|
+
height: headerHeight,
|
|
56
|
+
borderStyle: "single",
|
|
57
|
+
border: ["bottom"],
|
|
58
|
+
borderColor: theme.border,
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
{header}
|
|
62
|
+
</box>
|
|
63
|
+
<box style={{ flexGrow: 1 }}>{body}</box>
|
|
64
|
+
</box>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function PaneGrid({
|
|
69
|
+
headerHeight = 3,
|
|
70
|
+
sidebar,
|
|
71
|
+
sidebarHeader,
|
|
72
|
+
sidebarWidth = 28,
|
|
73
|
+
main,
|
|
74
|
+
mainHeader,
|
|
75
|
+
inspector,
|
|
76
|
+
inspectorHeader,
|
|
77
|
+
inspectorWidth = 40,
|
|
78
|
+
footer,
|
|
79
|
+
theme,
|
|
80
|
+
}: PaneGridProps): React.ReactNode {
|
|
81
|
+
const tokens = theme ?? DARK;
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<box
|
|
85
|
+
style={{
|
|
86
|
+
flexDirection: "column",
|
|
87
|
+
width: "100%",
|
|
88
|
+
height: "100%",
|
|
89
|
+
backgroundColor: tokens.background,
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
<box style={{ flexDirection: "row", flexGrow: 1 }}>
|
|
93
|
+
{sidebar !== undefined ? (
|
|
94
|
+
<PaneColumn
|
|
95
|
+
width={sidebarWidth}
|
|
96
|
+
side="right"
|
|
97
|
+
header={sidebarHeader}
|
|
98
|
+
body={sidebar}
|
|
99
|
+
headerHeight={headerHeight}
|
|
100
|
+
theme={tokens}
|
|
101
|
+
/>
|
|
102
|
+
) : null}
|
|
103
|
+
<PaneColumn
|
|
104
|
+
grow
|
|
105
|
+
header={mainHeader}
|
|
106
|
+
body={main}
|
|
107
|
+
headerHeight={headerHeight}
|
|
108
|
+
theme={tokens}
|
|
109
|
+
/>
|
|
110
|
+
{inspector !== undefined ? (
|
|
111
|
+
<PaneColumn
|
|
112
|
+
width={inspectorWidth}
|
|
113
|
+
side="left"
|
|
114
|
+
header={inspectorHeader}
|
|
115
|
+
body={inspector}
|
|
116
|
+
headerHeight={headerHeight}
|
|
117
|
+
theme={tokens}
|
|
118
|
+
/>
|
|
119
|
+
) : null}
|
|
120
|
+
</box>
|
|
121
|
+
{footer !== undefined ? (
|
|
122
|
+
<box style={{ borderStyle: "single", border: ["top"], borderColor: tokens.border }}>
|
|
123
|
+
{footer}
|
|
124
|
+
</box>
|
|
125
|
+
) : null}
|
|
126
|
+
</box>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "./story";
|
|
4
|
+
import { PanelColumn, FileTab } from "./PanelColumn";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Surfaces/PanelColumn" };
|
|
7
|
+
|
|
8
|
+
export const WithFileTab: Story = {
|
|
9
|
+
render: () => (
|
|
10
|
+
<box style={{ width: "100%", height: "100%", flexDirection: "row" }}>
|
|
11
|
+
<PanelColumn
|
|
12
|
+
width={40}
|
|
13
|
+
border="left"
|
|
14
|
+
header={<FileTab label="Welcome" active onClose={() => {}} theme={DARK} />}
|
|
15
|
+
theme={DARK}
|
|
16
|
+
>
|
|
17
|
+
<box style={{ paddingLeft: 1, paddingTop: 1 }}>
|
|
18
|
+
<text fg={DARK.textDim}>panel body</text>
|
|
19
|
+
</box>
|
|
20
|
+
</PanelColumn>
|
|
21
|
+
</box>
|
|
22
|
+
),
|
|
23
|
+
expectedColors: [DARK.accent, DARK.border],
|
|
24
|
+
size: { width: 50, height: 8 },
|
|
25
|
+
};
|