@cueloop/client 0.1.0-alpha.7 → 0.1.0-alpha.70
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/native/build-pty.sh +51 -0
- package/native/build.sh +82 -0
- package/native/darwin-arm64/libcuelooppty.dylib +0 -0
- package/native/darwin-arm64/libcueloopvt.dylib +0 -0
- package/native/src/pty.zig +193 -0
- package/native/src/shim.zig +114 -0
- package/package.json +39 -32
- package/src/App.appearance.test.tsx +53 -0
- package/src/App.autoclose.test.tsx +76 -50
- package/src/App.diff.test.tsx +156 -42
- package/src/App.export.test.tsx +58 -40
- package/src/App.plan-review.test.tsx +363 -0
- package/src/App.prototype-markdown.test.tsx +92 -0
- package/src/App.prototype.test.tsx +176 -0
- package/src/App.readonly.test.tsx +128 -67
- package/src/App.rendering.test.tsx +61 -35
- package/src/App.submit-confirm.test.tsx +191 -0
- package/src/App.test.tsx +310 -114
- package/src/App.toast.test.tsx +89 -0
- package/src/App.tsx +1039 -799
- package/src/App.walk.test.tsx +298 -0
- package/src/App.workbench.test.tsx +461 -0
- package/src/__snapshots__/view-plan.regression.test.ts.snap +100 -0
- package/src/annotation-palette.ts +61 -0
- package/src/app-key-state.ts +61 -0
- package/src/app-screens.tsx +432 -0
- package/src/app-view-model.ts +159 -0
- package/src/attribution.test.ts +31 -0
- package/src/attribution.ts +42 -0
- package/src/clipboard.test.ts +23 -0
- package/src/clipboard.ts +33 -0
- package/src/components/AnnotatableFileView.stories.tsx +42 -0
- package/src/components/AnnotatableFileView.test.tsx +102 -0
- package/src/components/AnnotatableFileView.tsx +126 -0
- 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 +286 -0
- package/src/components/AppShell.zoom.test.tsx +94 -0
- package/src/components/BareWorkbenchFileView.stories.tsx +27 -0
- package/src/components/BareWorkbenchFileView.tsx +64 -0
- package/src/components/Breadcrumb.stories.tsx +19 -0
- package/src/components/Breadcrumb.tsx +45 -0
- package/src/components/ChangesColumn.stories.tsx +39 -0
- package/src/components/ChangesColumn.tsx +171 -0
- package/src/components/CodeBlock.stories.tsx +42 -0
- package/src/components/CodeBlock.tsx +84 -0
- package/src/components/CompletionOverlay.stories.tsx +23 -0
- package/src/components/CompletionOverlay.tsx +60 -0
- package/src/components/ConfirmCard.stories.tsx +35 -0
- package/src/components/ConfirmCard.tsx +128 -0
- package/src/components/ConfirmDialog.stories.tsx +21 -0
- package/src/components/ConfirmDialog.tsx +66 -0
- package/src/components/DiffContentView.scroll.test.tsx +121 -0
- package/src/components/DiffContentView.split.test.tsx +140 -0
- package/src/components/DiffContentView.stories.tsx +80 -0
- package/src/components/DiffContentView.syntax.test.tsx +82 -0
- package/src/components/DiffContentView.tsx +859 -0
- 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 +118 -0
- package/src/components/EditorGrid.tsx +368 -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 +98 -0
- package/src/components/KeybindsDialog.stories.tsx +31 -0
- package/src/components/KeybindsDialog.tsx +103 -0
- package/src/components/MenuBar.stories.tsx +28 -0
- package/src/components/MenuBar.tsx +44 -0
- package/src/components/OverlayScrollbar.stories.tsx +33 -0
- package/src/components/OverlayScrollbar.test.tsx +139 -0
- package/src/components/OverlayScrollbar.tsx +129 -0
- 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 +83 -0
- package/src/components/PromptDialog.stories.tsx +36 -0
- package/src/components/PromptDialog.tsx +87 -0
- package/src/components/PrototypeContentView.stories.tsx +19 -0
- package/src/components/PrototypeContentView.tsx +446 -0
- package/src/components/RootOverlay.stories.tsx +44 -0
- package/src/components/RootOverlay.tsx +35 -0
- package/src/components/ScrollArea.stories.tsx +20 -0
- package/src/components/ScrollArea.tsx +44 -0
- package/src/components/SettingsDialog.stories.tsx +117 -0
- package/src/components/SettingsDialog.tsx +195 -0
- package/src/components/SettingsRows.stories.tsx +18 -0
- package/src/components/SettingsRows.tsx +112 -0
- package/src/components/ShellHeader.stories.tsx +50 -0
- package/src/components/ShellHeader.tsx +137 -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/ThreadTree.stories.tsx +46 -0
- package/src/components/ThreadTree.tsx +219 -0
- package/src/components/ThreadView.selection.test.tsx +812 -0
- package/src/components/ThreadView.slash.test.tsx +158 -0
- package/src/components/ThreadView.stories.tsx +26 -0
- package/src/components/ThreadView.tsx +330 -0
- package/src/components/ThreadsSidebar.stories.tsx +36 -0
- package/src/components/ThreadsSidebar.tsx +71 -0
- package/src/components/Toast.stories.tsx +12 -0
- package/src/components/Toast.tsx +45 -0
- package/src/components/Tooltip.stories.tsx +26 -0
- package/src/components/Tooltip.tsx +72 -0
- package/src/components/TreePane.stories.tsx +106 -0
- package/src/components/TreePane.tsx +157 -0
- package/src/components/WalkWizard.stories.tsx +100 -0
- package/src/components/WalkWizard.tsx +152 -0
- package/src/components/WelcomePlayground.stories.tsx +12 -0
- package/src/components/WelcomePlayground.test.tsx +47 -0
- package/src/components/WelcomePlayground.tsx +140 -0
- package/src/components/__snapshots__/stories.test.tsx.snap +1845 -0
- package/src/components/agent-launcher.stories.tsx +26 -0
- package/src/components/agent-launcher.test.tsx +50 -0
- package/src/components/agent-launcher.tsx +149 -0
- package/src/components/diff-content-view-layout.test.ts +198 -0
- package/src/components/diff-content-view-layout.ts +84 -0
- 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 +32 -0
- package/src/components/primitives/Button.tsx +52 -0
- package/src/components/primitives/Card.stories.tsx +25 -0
- package/src/components/primitives/Card.tsx +60 -0
- package/src/components/primitives/Dialog.stories.tsx +24 -0
- package/src/components/primitives/Dialog.tsx +66 -0
- package/src/components/primitives/IconButton.stories.tsx +36 -0
- package/src/components/primitives/IconButton.tsx +62 -0
- package/src/components/primitives/StatusBar.stories.tsx +11 -0
- package/src/components/primitives/StatusBar.tsx +23 -0
- package/src/components/primitives/Tabs.stories.tsx +29 -0
- package/src/components/primitives/Tabs.tsx +102 -0
- package/src/components/primitives/Toolbar.stories.tsx +17 -0
- package/src/components/primitives/Toolbar.tsx +14 -0
- package/src/components/primitives/Tree.stories.tsx +124 -0
- package/src/components/primitives/Tree.tsx +96 -0
- package/src/components/primitives/frame.test.ts +12 -0
- package/src/components/primitives/frame.ts +14 -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 +54 -0
- package/src/components/quick-actions-editor.test.tsx +49 -0
- package/src/components/quick-actions-editor.tsx +150 -0
- package/src/components/session-tree.test.ts +134 -0
- package/src/components/session-tree.ts +108 -0
- package/src/components/stories-app.tsx +184 -0
- package/src/components/stories.test.tsx +66 -0
- package/src/components/story-fixtures.ts +102 -0
- package/src/components/story-tree.test.ts +100 -0
- package/src/components/story.ts +142 -0
- package/src/components/syntax-highlight.ts +110 -0
- package/src/components/terminal-pane.test.tsx +40 -0
- package/src/components/terminal-pane.ts +199 -0
- package/src/components/theme-context.stories.tsx +46 -0
- package/src/components/theme-context.tsx +37 -0
- package/src/components/truncate-text.ts +7 -0
- package/src/config.test.ts +461 -31
- package/src/config.ts +389 -57
- package/src/diff-fold.test.ts +78 -0
- package/src/diff-fold.ts +126 -0
- package/src/diff-hunk-curate.test.ts +315 -0
- package/src/diff-hunk-curate.ts +153 -0
- package/src/diff-intraline.test.ts +165 -0
- package/src/diff-intraline.ts +146 -0
- package/src/diff-syntax.test.ts +112 -0
- package/src/diff-syntax.ts +179 -0
- package/src/discussions.ts +79 -0
- package/src/editor.test.ts +149 -0
- package/src/editor.ts +139 -9
- package/src/ghostty-terminal.test.ts +85 -0
- package/src/ghostty-terminal.ts +199 -0
- package/src/herdr.test.ts +9 -34
- package/src/herdr.ts +25 -21
- package/src/index.ts +11 -0
- package/src/integrations.test.ts +41 -0
- package/src/integrations.ts +32 -0
- package/src/intent-dispatch.test.ts +698 -0
- package/src/intent-dispatch.ts +700 -0
- package/src/key-bindings.test.ts +156 -0
- package/src/key-bindings.ts +465 -0
- package/src/keymap.test.ts +611 -0
- package/src/keymap.ts +410 -0
- package/src/kitty-image.test.ts +66 -0
- package/src/kitty-image.ts +138 -0
- package/src/mark-runs.ts +120 -0
- package/src/prototype-browser.test.ts +56 -0
- package/src/prototype-browser.ts +307 -0
- package/src/prototype-pixels.tsx +35 -0
- package/src/pty.test.ts +81 -0
- package/src/pty.ts +245 -0
- package/src/ready-signal.test.ts +85 -0
- package/src/ready-signal.ts +52 -0
- package/src/run.ts +30 -9
- package/src/serve.test.ts +24 -12
- package/src/serve.ts +20 -9
- package/src/share.ts +215 -0
- package/src/skills.test.ts +74 -0
- package/src/skills.ts +68 -0
- package/src/slash-palette.test.ts +54 -0
- package/src/slash-palette.ts +104 -0
- package/src/split-diff.test.ts +110 -0
- package/src/split-diff.ts +149 -0
- package/src/test-support.test.ts +264 -0
- package/src/test-support.ts +355 -0
- package/src/theme-presets.test.ts +51 -0
- package/src/theme-presets.ts +214 -0
- package/src/theme.ts +84 -19
- package/src/thread-chords.test.ts +170 -0
- package/src/thread-chords.ts +169 -0
- package/src/thread-controller.curate.test.ts +438 -0
- package/src/thread-controller.live-diff.test.ts +199 -0
- package/src/thread-controller.share.test.ts +472 -0
- package/src/thread-controller.tree.test.ts +255 -0
- package/src/thread-controller.ts +1509 -0
- package/src/thread-selection.test.ts +210 -0
- package/src/thread-selection.ts +243 -0
- package/src/tree-view.test.ts +128 -0
- package/src/tree-view.ts +148 -0
- package/src/use-annotation-surface.tsx +851 -0
- package/src/use-changes-workbench.ts +151 -0
- package/src/use-frame-measure.ts +38 -0
- package/src/use-settings-dialog.tsx +274 -0
- package/src/use-terminal-virtualizer.ts +233 -0
- package/src/version.ts +5 -0
- package/src/view-diff.test.ts +66 -9
- package/src/view-diff.ts +231 -23
- package/src/view-plan.regression.test.ts +60 -0
- package/src/view-plan.test.ts +406 -0
- package/src/view-plan.ts +570 -0
- package/src/walk.test.ts +121 -0
- package/src/walk.ts +74 -0
- package/src/syntax.test.ts +0 -41
- package/src/syntax.ts +0 -88
- package/src/view.test.ts +0 -157
- package/src/view.ts +0 -312
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "../story";
|
|
4
|
+
import { Card } from "./Card";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Primitives/Card" };
|
|
7
|
+
|
|
8
|
+
export const Titled: Story = {
|
|
9
|
+
render: () => (
|
|
10
|
+
<Card title=" a titled card " contentRows={2}>
|
|
11
|
+
<text>first content row</text>
|
|
12
|
+
<text>second content row</text>
|
|
13
|
+
</Card>
|
|
14
|
+
),
|
|
15
|
+
expectedColors: [DARK.border, DARK.elevated],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const AccentBorder: Story = {
|
|
19
|
+
render: () => (
|
|
20
|
+
<Card title=" accented " contentRows={1} borderColor={DARK.accent}>
|
|
21
|
+
<text>content</text>
|
|
22
|
+
</Card>
|
|
23
|
+
),
|
|
24
|
+
expectedColors: [DARK.accent],
|
|
25
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bordered rounded box with an optional title. The card owns the
|
|
3
|
+
* height-derivation rule: its height is always the declared content row
|
|
4
|
+
* count plus the two border rows, so layout math and the mounted box can
|
|
5
|
+
* never drift apart - border-collapse bugs die here.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from "react";
|
|
9
|
+
import type { Theme } from "../../theme";
|
|
10
|
+
import { useComponentTheme } from "../theme-context";
|
|
11
|
+
import { FRAME_BORDER_STYLE } from "./frame";
|
|
12
|
+
|
|
13
|
+
export interface CardProps {
|
|
14
|
+
title?: string;
|
|
15
|
+
/** Number of content rows inside the border; the box adds the two border rows. */
|
|
16
|
+
contentRows: number;
|
|
17
|
+
borderColor?: string;
|
|
18
|
+
backgroundColor?: string;
|
|
19
|
+
marginLeft?: number;
|
|
20
|
+
marginRight?: number;
|
|
21
|
+
theme?: Theme;
|
|
22
|
+
children: React.ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The one height rule: content rows plus the top and bottom border rows. */
|
|
26
|
+
export function cardHeight(contentRows: number): number {
|
|
27
|
+
return contentRows + 2;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function Card({
|
|
31
|
+
title,
|
|
32
|
+
contentRows,
|
|
33
|
+
borderColor,
|
|
34
|
+
backgroundColor,
|
|
35
|
+
marginLeft,
|
|
36
|
+
marginRight,
|
|
37
|
+
theme,
|
|
38
|
+
children,
|
|
39
|
+
}: CardProps): React.ReactNode {
|
|
40
|
+
const tokens = useComponentTheme(theme);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<box
|
|
44
|
+
style={{
|
|
45
|
+
height: cardHeight(contentRows),
|
|
46
|
+
marginLeft,
|
|
47
|
+
marginRight,
|
|
48
|
+
border: true,
|
|
49
|
+
borderStyle: FRAME_BORDER_STYLE,
|
|
50
|
+
borderColor: borderColor ?? tokens.border,
|
|
51
|
+
backgroundColor: backgroundColor ?? tokens.elevated,
|
|
52
|
+
flexDirection: "column",
|
|
53
|
+
paddingLeft: 1,
|
|
54
|
+
}}
|
|
55
|
+
title={title}
|
|
56
|
+
>
|
|
57
|
+
{children}
|
|
58
|
+
</box>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "../story";
|
|
4
|
+
import { Dialog } from "./Dialog";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Primitives/Dialog" };
|
|
7
|
+
|
|
8
|
+
export const Open: Story = {
|
|
9
|
+
render: () => (
|
|
10
|
+
<Dialog isOpen title=" a modal " width={40} height={8}>
|
|
11
|
+
<text>dialog body content</text>
|
|
12
|
+
</Dialog>
|
|
13
|
+
),
|
|
14
|
+
expectedColors: [DARK.accent],
|
|
15
|
+
size: { width: 60, height: 16 },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const Closed: Story = {
|
|
19
|
+
render: () => (
|
|
20
|
+
<Dialog isOpen={false} title=" hidden " width={40} height={8}>
|
|
21
|
+
<text>never rendered</text>
|
|
22
|
+
</Dialog>
|
|
23
|
+
),
|
|
24
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centered modal: a full-screen layer (backdrop token, transparent by
|
|
3
|
+
* default) centers a bordered, unpainted panel over the session - the frame
|
|
4
|
+
* delineates the dialog, the content behind stays visible.
|
|
5
|
+
* `isOpen` keeps the mount decision with the caller-visible prop vocabulary.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from "react";
|
|
9
|
+
import type { Theme } from "../../theme";
|
|
10
|
+
import { useComponentTheme } from "../theme-context";
|
|
11
|
+
import { FRAME_BORDER_STYLE } from "./frame";
|
|
12
|
+
|
|
13
|
+
export interface DialogProps {
|
|
14
|
+
isOpen: boolean;
|
|
15
|
+
title?: string;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
/** Solid panel fill for content-heavy dialogs; default transparent. */
|
|
19
|
+
background?: string;
|
|
20
|
+
theme?: Theme;
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function Dialog({
|
|
25
|
+
isOpen,
|
|
26
|
+
title,
|
|
27
|
+
width,
|
|
28
|
+
height,
|
|
29
|
+
background,
|
|
30
|
+
theme,
|
|
31
|
+
children,
|
|
32
|
+
}: DialogProps): React.ReactNode {
|
|
33
|
+
const tokens = useComponentTheme(theme);
|
|
34
|
+
|
|
35
|
+
if (!isOpen) return null;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<box
|
|
39
|
+
style={{
|
|
40
|
+
position: "absolute",
|
|
41
|
+
left: 0,
|
|
42
|
+
top: 0,
|
|
43
|
+
width: "100%",
|
|
44
|
+
height: "100%",
|
|
45
|
+
backgroundColor: tokens.backdrop,
|
|
46
|
+
alignItems: "center",
|
|
47
|
+
justifyContent: "center",
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<box
|
|
51
|
+
style={{
|
|
52
|
+
width,
|
|
53
|
+
height,
|
|
54
|
+
border: true,
|
|
55
|
+
borderStyle: FRAME_BORDER_STYLE,
|
|
56
|
+
borderColor: tokens.accent,
|
|
57
|
+
backgroundColor: background,
|
|
58
|
+
flexDirection: "column",
|
|
59
|
+
}}
|
|
60
|
+
title={title}
|
|
61
|
+
>
|
|
62
|
+
{children}
|
|
63
|
+
</box>
|
|
64
|
+
</box>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "../story";
|
|
4
|
+
import { IconButton } from "./IconButton";
|
|
5
|
+
import { NERD } from "./icons";
|
|
6
|
+
|
|
7
|
+
export const meta: StoryMeta = { title: "Primitives/IconButton" };
|
|
8
|
+
|
|
9
|
+
function Row({ children }: { children: React.ReactNode }): React.ReactNode {
|
|
10
|
+
return (
|
|
11
|
+
<box style={{ flexDirection: "row", padding: 1, backgroundColor: DARK.panel }}>{children}</box>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const States: Story = {
|
|
16
|
+
render: () => (
|
|
17
|
+
<Row>
|
|
18
|
+
<IconButton glyph={NERD.settings} onPress={() => {}} marginRight={3} />
|
|
19
|
+
<IconButton glyph={NERD.listTree} active onPress={() => {}} marginRight={3} />
|
|
20
|
+
<IconButton glyph={NERD.diff} disabled marginRight={3} />
|
|
21
|
+
<IconButton glyph={NERD.submit} onPress={() => {}} />
|
|
22
|
+
</Row>
|
|
23
|
+
),
|
|
24
|
+
expectedColors: [DARK.accent, DARK.textMuted, DARK.textDim],
|
|
25
|
+
size: { width: 40, height: 3 },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const Submit: Story = {
|
|
29
|
+
render: () => (
|
|
30
|
+
<Row>
|
|
31
|
+
<IconButton glyph={NERD.submit} active onPress={() => {}} />
|
|
32
|
+
</Row>
|
|
33
|
+
),
|
|
34
|
+
expectedColors: [DARK.accent],
|
|
35
|
+
size: { width: 20, height: 3 },
|
|
36
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// A clickable single-glyph button for header and footer controls: an icon in a
|
|
2
|
+
// box that calls onPress, painted in the brand accent when active. alignSelf
|
|
3
|
+
// center keeps it on the same row as sibling text or icons.
|
|
4
|
+
|
|
5
|
+
import React from "react";
|
|
6
|
+
import type { MouseEvent as TerminalMouseEvent } from "@opentui/core";
|
|
7
|
+
import { DARK, type Theme } from "../../theme";
|
|
8
|
+
import { useTooltip } from "../Tooltip";
|
|
9
|
+
|
|
10
|
+
export interface IconButtonProps {
|
|
11
|
+
glyph: string;
|
|
12
|
+
onPress?: () => void;
|
|
13
|
+
/** Paint the glyph in the brand accent to mark the current mode or view. */
|
|
14
|
+
active?: boolean;
|
|
15
|
+
/** Dim the glyph and ignore presses. */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
/** Override the resolved foreground (wins over active/disabled colouring). */
|
|
18
|
+
color?: string;
|
|
19
|
+
/** Label shown on hover, surfaced at the screen root so a tiny header cell cannot clip it. */
|
|
20
|
+
tip?: string;
|
|
21
|
+
marginLeft?: number;
|
|
22
|
+
marginRight?: number;
|
|
23
|
+
theme?: Theme;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function IconButton({
|
|
27
|
+
glyph,
|
|
28
|
+
onPress,
|
|
29
|
+
active,
|
|
30
|
+
disabled,
|
|
31
|
+
color,
|
|
32
|
+
tip,
|
|
33
|
+
marginLeft,
|
|
34
|
+
marginRight,
|
|
35
|
+
theme,
|
|
36
|
+
}: IconButtonProps): React.ReactNode {
|
|
37
|
+
const tokens = theme ?? DARK;
|
|
38
|
+
const resolved = color ?? (disabled ? tokens.textDim : active ? tokens.accent : tokens.textMuted);
|
|
39
|
+
const { showTooltip, hideTooltip } = useTooltip();
|
|
40
|
+
// a button press is its own action - never let it bubble to a clickable row behind it
|
|
41
|
+
const handleMouseUp = (event: TerminalMouseEvent): void => {
|
|
42
|
+
event.stopPropagation();
|
|
43
|
+
hideTooltip();
|
|
44
|
+
onPress?.();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<box
|
|
49
|
+
onMouseUp={disabled ? undefined : handleMouseUp}
|
|
50
|
+
onMouseOver={tip ? (event) => showTooltip(tip, event.x, event.y) : undefined}
|
|
51
|
+
onMouseOut={tip ? hideTooltip : undefined}
|
|
52
|
+
style={{
|
|
53
|
+
flexShrink: 0,
|
|
54
|
+
alignSelf: "center",
|
|
55
|
+
marginLeft,
|
|
56
|
+
marginRight,
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<text fg={resolved}>{glyph}</text>
|
|
60
|
+
</box>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "../story";
|
|
4
|
+
import { StatusBar } from "./StatusBar";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Primitives/StatusBar" };
|
|
7
|
+
|
|
8
|
+
export const HintLine: Story = {
|
|
9
|
+
render: () => <StatusBar>j/k move · v span · c comment · q quit</StatusBar>,
|
|
10
|
+
expectedColors: [DARK.textDim],
|
|
11
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-row panel bar for the status/hint line. Content renders as dim text;
|
|
3
|
+
* callers pass richer spans as children when a segment needs another tone.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import type { Theme } from "../../theme";
|
|
8
|
+
import { useComponentTheme } from "../theme-context";
|
|
9
|
+
|
|
10
|
+
export interface StatusBarProps {
|
|
11
|
+
theme?: Theme;
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function StatusBar({ theme, children }: StatusBarProps): React.ReactNode {
|
|
16
|
+
const tokens = useComponentTheme(theme);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<box style={{ height: 1, backgroundColor: tokens.panel, paddingLeft: 1 }}>
|
|
20
|
+
<text fg={tokens.textDim}>{children}</text>
|
|
21
|
+
</box>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "../story";
|
|
4
|
+
import { Tab, TabList, Tabs } from "./Tabs";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Primitives/Tabs" };
|
|
7
|
+
|
|
8
|
+
export const TwoTabs: Story = {
|
|
9
|
+
render: () => (
|
|
10
|
+
<Tabs selectedKey="review" onSelectionChange={() => {}}>
|
|
11
|
+
<TabList>
|
|
12
|
+
<Tab id="review">Review (2)</Tab>
|
|
13
|
+
<Tab id="agent">Agent</Tab>
|
|
14
|
+
</TabList>
|
|
15
|
+
</Tabs>
|
|
16
|
+
),
|
|
17
|
+
expectedColors: [DARK.accent, DARK.textDim],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const SecondSelected: Story = {
|
|
21
|
+
render: () => (
|
|
22
|
+
<Tabs selectedKey="agent" onSelectionChange={() => {}}>
|
|
23
|
+
<TabList>
|
|
24
|
+
<Tab id="review">Review (0)</Tab>
|
|
25
|
+
<Tab id="agent">Agent</Tab>
|
|
26
|
+
</TabList>
|
|
27
|
+
</Tabs>
|
|
28
|
+
),
|
|
29
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tabs / TabList / Tab: a transparent, full-width rounded strip of clickable
|
|
3
|
+
* text tabs. The composition follows the ARIA tabs pattern: Tabs owns the
|
|
4
|
+
* controlled selection (`selectedKey` + `onSelectionChange`), TabList renders
|
|
5
|
+
* the strip, Tab declares one item. Selection stays controlled from outside so
|
|
6
|
+
* the keyboard grammar remains the key owner; each tab click sets the key.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React, { createContext, useContext } from "react";
|
|
10
|
+
import type { Theme } from "../../theme";
|
|
11
|
+
import { useComponentTheme } from "../theme-context";
|
|
12
|
+
import { FRAME_BORDER_STYLE } from "./frame";
|
|
13
|
+
|
|
14
|
+
interface TabsContextValue {
|
|
15
|
+
selectedKey: string;
|
|
16
|
+
onSelectionChange: (key: string) => void;
|
|
17
|
+
/** Text color of the selected tab; defaults to the accent token. */
|
|
18
|
+
selectedColor?: string;
|
|
19
|
+
theme?: Theme;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const TabsContext = createContext<TabsContextValue | null>(null);
|
|
23
|
+
|
|
24
|
+
export interface TabsProps {
|
|
25
|
+
selectedKey: string;
|
|
26
|
+
onSelectionChange: (key: string) => void;
|
|
27
|
+
selectedColor?: string;
|
|
28
|
+
theme?: Theme;
|
|
29
|
+
children: React.ReactNode;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function Tabs({
|
|
33
|
+
selectedKey,
|
|
34
|
+
onSelectionChange,
|
|
35
|
+
selectedColor,
|
|
36
|
+
theme,
|
|
37
|
+
children,
|
|
38
|
+
}: TabsProps): React.ReactNode {
|
|
39
|
+
return (
|
|
40
|
+
<TabsContext.Provider value={{ selectedKey, onSelectionChange, selectedColor, theme }}>
|
|
41
|
+
{children}
|
|
42
|
+
</TabsContext.Provider>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface TabProps {
|
|
47
|
+
id: string;
|
|
48
|
+
children: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Declarative item: TabList collects Tab children into the native strip. */
|
|
52
|
+
export function Tab(_props: TabProps): React.ReactNode {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface TabListProps {
|
|
57
|
+
children: React.ReactNode;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function TabList({ children }: TabListProps): React.ReactNode {
|
|
61
|
+
const tabs = useContext(TabsContext);
|
|
62
|
+
|
|
63
|
+
if (!tabs) throw new Error("TabList must render inside Tabs");
|
|
64
|
+
const tokens = useComponentTheme(tabs.theme);
|
|
65
|
+
const items: { id: string; label: string }[] = [];
|
|
66
|
+
|
|
67
|
+
React.Children.forEach(children, (child) => {
|
|
68
|
+
if (React.isValidElement<TabProps>(child)) {
|
|
69
|
+
items.push({ id: child.props.id, label: child.props.children });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<box
|
|
75
|
+
style={{
|
|
76
|
+
width: "100%",
|
|
77
|
+
height: 3,
|
|
78
|
+
border: true,
|
|
79
|
+
borderStyle: FRAME_BORDER_STYLE,
|
|
80
|
+
borderColor: tokens.text,
|
|
81
|
+
flexDirection: "row",
|
|
82
|
+
paddingLeft: 1,
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
{items.map((item) => {
|
|
86
|
+
const selected = item.id === tabs.selectedKey;
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<box
|
|
90
|
+
key={item.id}
|
|
91
|
+
style={{ marginRight: 2 }}
|
|
92
|
+
onMouseUp={() => tabs.onSelectionChange(item.id)}
|
|
93
|
+
>
|
|
94
|
+
<text fg={selected ? (tabs.selectedColor ?? tokens.accent) : tokens.textDim}>
|
|
95
|
+
{item.label}
|
|
96
|
+
</text>
|
|
97
|
+
</box>
|
|
98
|
+
);
|
|
99
|
+
})}
|
|
100
|
+
</box>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Story, StoryMeta } from "../story";
|
|
3
|
+
import { Button } from "./Button";
|
|
4
|
+
import { Toolbar } from "./Toolbar";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Primitives/Toolbar" };
|
|
7
|
+
|
|
8
|
+
export const ActionRow: Story = {
|
|
9
|
+
render: () => (
|
|
10
|
+
<Toolbar>
|
|
11
|
+
<Button variant="solid" marginRight={2} onPress={() => {}}>
|
|
12
|
+
{" Submit "}
|
|
13
|
+
</Button>
|
|
14
|
+
<Button onPress={() => {}}>{" Cancel "}</Button>
|
|
15
|
+
</Toolbar>
|
|
16
|
+
),
|
|
17
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Horizontal action row: children flow left to right on one row. The classic
|
|
3
|
+
* home of Button groups (Save/Cancel, Submit/Cancel).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
|
|
8
|
+
export interface ToolbarProps {
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Toolbar({ children }: ToolbarProps): React.ReactNode {
|
|
13
|
+
return <box style={{ flexDirection: "row", height: 1 }}>{children}</box>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "../story";
|
|
4
|
+
import { Tree } from "./Tree";
|
|
5
|
+
import type { TreeNode } from "./tree-model";
|
|
6
|
+
|
|
7
|
+
export const meta: StoryMeta = { title: "Primitives/Tree" };
|
|
8
|
+
|
|
9
|
+
const FILES: TreeNode[] = [
|
|
10
|
+
{
|
|
11
|
+
id: "packages",
|
|
12
|
+
label: "packages",
|
|
13
|
+
children: [
|
|
14
|
+
{
|
|
15
|
+
id: "schema",
|
|
16
|
+
label: "schema",
|
|
17
|
+
children: [
|
|
18
|
+
{ id: "schema/types.ts", label: "types.ts", status: "modified" },
|
|
19
|
+
{ id: "schema/tree-model.ts", label: "tree-model.ts", status: "added" },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "daemon",
|
|
24
|
+
label: "daemon",
|
|
25
|
+
children: [{ id: "daemon/api.ts", label: "api.ts", status: "modified" }],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
{ id: "README.md", label: "README.md" },
|
|
30
|
+
{ id: "old.ts", label: "old.ts", status: "deleted" },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const DEEP: TreeNode[] = [
|
|
34
|
+
{
|
|
35
|
+
id: "src",
|
|
36
|
+
label: "src",
|
|
37
|
+
children: [
|
|
38
|
+
{
|
|
39
|
+
id: "src/components",
|
|
40
|
+
label: "components",
|
|
41
|
+
children: [
|
|
42
|
+
{
|
|
43
|
+
id: "src/components/primitives",
|
|
44
|
+
label: "primitives",
|
|
45
|
+
children: [
|
|
46
|
+
{ id: "src/components/primitives/Tree.tsx", label: "Tree.tsx", status: "added" },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const PROJECTS: TreeNode[] = [
|
|
56
|
+
{
|
|
57
|
+
id: "projects",
|
|
58
|
+
label: "Projects",
|
|
59
|
+
children: [
|
|
60
|
+
{
|
|
61
|
+
id: "cueloop",
|
|
62
|
+
label: "cueloop",
|
|
63
|
+
children: [{ id: "cueloop/read", label: "Read Cueloop Repository" }],
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: "threads",
|
|
69
|
+
label: "Threads",
|
|
70
|
+
children: [{ id: "threads/welcome", label: "Welcome to cueloop" }],
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
function Frame({ children }: { children: React.ReactNode }): React.ReactNode {
|
|
75
|
+
return (
|
|
76
|
+
<box style={{ width: 40, height: "100%", backgroundColor: DARK.panel, paddingTop: 1 }}>
|
|
77
|
+
{children}
|
|
78
|
+
</box>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const FileTree: Story = {
|
|
83
|
+
render: () => (
|
|
84
|
+
<Frame>
|
|
85
|
+
<Tree
|
|
86
|
+
nodes={FILES}
|
|
87
|
+
expandedIds={new Set(["packages", "schema", "daemon"])}
|
|
88
|
+
selectedId="schema/tree-model.ts"
|
|
89
|
+
showStatus
|
|
90
|
+
/>
|
|
91
|
+
</Frame>
|
|
92
|
+
),
|
|
93
|
+
expectedColors: [DARK.accent, DARK.green, DARK.red],
|
|
94
|
+
size: { width: 44, height: 16 },
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const FlattenedChain: Story = {
|
|
98
|
+
render: () => (
|
|
99
|
+
<Frame>
|
|
100
|
+
<Tree
|
|
101
|
+
nodes={DEEP}
|
|
102
|
+
expandedIds={new Set(["src", "src/components/primitives"])}
|
|
103
|
+
flattenEmptyDirectories
|
|
104
|
+
showStatus
|
|
105
|
+
/>
|
|
106
|
+
</Frame>
|
|
107
|
+
),
|
|
108
|
+
expectedColors: [DARK.green],
|
|
109
|
+
size: { width: 44, height: 10 },
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const ProjectsThreads: Story = {
|
|
113
|
+
render: () => (
|
|
114
|
+
<Frame>
|
|
115
|
+
<Tree
|
|
116
|
+
nodes={PROJECTS}
|
|
117
|
+
expandedIds={new Set(["projects", "cueloop", "threads"])}
|
|
118
|
+
selectedId="cueloop/read"
|
|
119
|
+
/>
|
|
120
|
+
</Frame>
|
|
121
|
+
),
|
|
122
|
+
expectedColors: [DARK.accent],
|
|
123
|
+
size: { width: 44, height: 12 },
|
|
124
|
+
};
|