@cueloop/client 0.1.0-alpha.9 → 0.1.0-alpha.90
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 +42 -34
- package/src/App.appearance.test.tsx +53 -0
- package/src/App.autoclose.test.tsx +77 -51
- package/src/App.diff.test.tsx +259 -45
- package/src/App.export.test.tsx +58 -41
- package/src/App.focus.test.ts +89 -0
- package/src/App.identity.test.tsx +78 -0
- package/src/App.plan-render.test.tsx +209 -0
- package/src/App.plan-review.test.tsx +330 -161
- package/src/App.prototype-markdown.test.tsx +128 -0
- package/src/App.prototype.test.tsx +176 -0
- package/src/App.readonly.test.tsx +129 -67
- package/src/App.regression.test.tsx +94 -0
- package/src/App.rendering.test.tsx +61 -35
- package/src/App.share-dialog.test.tsx +172 -0
- package/src/App.submit-confirm.test.tsx +264 -0
- package/src/App.test.tsx +402 -126
- package/src/App.toast.test.tsx +98 -0
- package/src/App.tsx +1402 -1060
- package/src/App.visual.test.tsx +57 -0
- package/src/App.walk.test.tsx +299 -0
- package/src/App.workbench.test.tsx +774 -0
- package/src/__snapshots__/view-plan.regression.test.ts.snap +100 -0
- package/src/annotation-palette.ts +62 -0
- package/src/ansi-screen.test.ts +28 -0
- package/src/ansi-screen.ts +72 -0
- package/src/app-key-state.ts +61 -0
- package/src/app-screens.tsx +577 -0
- package/src/app-view-model.ts +168 -0
- package/src/attribution.test.ts +31 -0
- package/src/attribution.ts +42 -0
- package/src/client-extension-registry.test.tsx +61 -0
- package/src/client-extension-registry.ts +159 -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 +134 -0
- package/src/components/AnnotationCards.stories.tsx +86 -0
- package/src/components/AnnotationCards.tsx +341 -0
- package/src/components/AnsiScreen.stories.tsx +12 -0
- package/src/components/AnsiScreen.tsx +28 -0
- package/src/components/AppHeader.stories.tsx +50 -0
- package/src/components/AppHeader.tsx +83 -0
- package/src/components/AppShell.brand.test.tsx +58 -0
- package/src/components/AppShell.extensions.test.tsx +191 -0
- package/src/components/AppShell.stories.tsx +89 -0
- package/src/components/AppShell.tsx +362 -0
- package/src/components/AppShell.zoom.test.tsx +135 -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 +44 -0
- package/src/components/ChangesColumn.tsx +199 -0
- package/src/components/ChangesFileTree.test.tsx +61 -0
- package/src/components/CodeBlock.stories.tsx +42 -0
- package/src/components/CodeBlock.tsx +84 -0
- package/src/components/CompletionOverlay.stories.tsx +37 -0
- package/src/components/CompletionOverlay.tsx +77 -0
- package/src/components/ConfirmCard.stories.tsx +37 -0
- package/src/components/ConfirmCard.tsx +150 -0
- package/src/components/ConfirmDialog.stories.tsx +21 -0
- package/src/components/ConfirmDialog.tsx +72 -0
- package/src/components/DiffContentView.scroll.test.tsx +149 -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 +906 -0
- package/src/components/DiscussionMarkerRail.stories.tsx +54 -0
- package/src/components/DiscussionMarkerRail.tsx +186 -0
- package/src/components/EditorGrid.stories.tsx +59 -0
- package/src/components/EditorGrid.tabs.test.tsx +118 -0
- package/src/components/EditorGrid.tsx +426 -0
- package/src/components/EditorGrid.zoom.test.tsx +68 -0
- package/src/components/ExtensionZone.stories.tsx +28 -0
- package/src/components/ExtensionZone.tsx +145 -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/GridTabContent.stories.tsx +59 -0
- package/src/components/GridTabContent.tsx +182 -0
- package/src/components/HandleChipsInput.stories.tsx +24 -0
- package/src/components/HandleChipsInput.test.tsx +35 -0
- package/src/components/HandleChipsInput.tsx +145 -0
- package/src/components/KeybindsDialog.stories.tsx +31 -0
- package/src/components/KeybindsDialog.tsx +103 -0
- package/src/components/MarkdownGridBlock.stories.tsx +33 -0
- package/src/components/MarkdownGridBlock.tsx +72 -0
- package/src/components/MarkdownThreadEditor.stories.tsx +15 -0
- package/src/components/MarkdownThreadEditor.test.tsx +98 -0
- package/src/components/MarkdownThreadEditor.tsx +162 -0
- package/src/components/MenuBar.stories.tsx +28 -0
- package/src/components/MenuBar.tsx +44 -0
- package/src/components/NavModeHint.stories.tsx +40 -0
- package/src/components/NavModeHint.tsx +31 -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 +98 -0
- package/src/components/ProjectTreeView.stories.tsx +25 -0
- package/src/components/ProjectTreeView.test.tsx +75 -0
- package/src/components/ProjectTreeView.tsx +125 -0
- package/src/components/PromptDialog.stories.tsx +36 -0
- package/src/components/PromptDialog.test.tsx +61 -0
- package/src/components/PromptDialog.tsx +99 -0
- package/src/components/PrototypeContentView.stories.tsx +19 -0
- package/src/components/PrototypeContentView.tsx +446 -0
- package/src/components/RootOverlay.stories.tsx +45 -0
- package/src/components/RootOverlay.tsx +48 -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 +186 -0
- package/src/components/SettingsRows.stories.tsx +18 -0
- package/src/components/SettingsRows.tsx +112 -0
- package/src/components/ShareDialog.stories.tsx +121 -0
- package/src/components/ShareDialog.tsx +552 -0
- package/src/components/ShellHeader.stories.tsx +50 -0
- package/src/components/ShellHeader.tsx +137 -0
- package/src/components/SlashComposer.stories.tsx +37 -0
- package/src/components/SlashComposer.tsx +87 -0
- package/src/components/ThreadFooter.stories.tsx +30 -0
- package/src/components/ThreadFooter.test.tsx +47 -0
- package/src/components/ThreadFooter.tsx +76 -0
- package/src/components/ThreadTree.stories.tsx +46 -0
- package/src/components/ThreadTree.tsx +255 -0
- package/src/components/ThreadView.selection.test.tsx +951 -0
- package/src/components/ThreadView.slash.test.tsx +146 -0
- package/src/components/ThreadView.stories.tsx +26 -0
- package/src/components/ThreadView.tsx +525 -0
- package/src/components/Toast.stories.tsx +19 -0
- package/src/components/Toast.tsx +52 -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 +150 -0
- package/src/components/WelcomePlayground.stories.tsx +12 -0
- package/src/components/WelcomePlayground.test.tsx +49 -0
- package/src/components/WelcomePlayground.tsx +139 -0
- package/src/components/WorkspacePanels.stories.tsx +36 -0
- package/src/components/WorkspacePanels.tsx +116 -0
- package/src/components/__snapshots__/stories.test.tsx.snap +2264 -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/file-tree.test.ts +60 -0
- package/src/components/file-tree.ts +112 -0
- package/src/components/menu-control.stories.tsx +35 -0
- package/src/components/menu-control.tsx +43 -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 +59 -0
- package/src/components/primitives/Card.stories.tsx +25 -0
- package/src/components/primitives/Card.tsx +63 -0
- package/src/components/primitives/Dialog.stories.tsx +24 -0
- package/src/components/primitives/Dialog.test.tsx +37 -0
- package/src/components/primitives/Dialog.tsx +71 -0
- package/src/components/primitives/DialogActions.stories.tsx +20 -0
- package/src/components/primitives/DialogActions.tsx +39 -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/Switch.stories.tsx +17 -0
- package/src/components/primitives/Switch.tsx +32 -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 +100 -0
- package/src/components/primitives/frame.test.ts +12 -0
- package/src/components/primitives/frame.ts +17 -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 +58 -0
- package/src/components/quick-actions-editor.test.tsx +117 -0
- package/src/components/quick-actions-editor.tsx +226 -0
- package/src/components/share-dialog-store.ts +58 -0
- package/src/components/stories-app.tsx +193 -0
- package/src/components/stories.test.tsx +74 -0
- package/src/components/story-fixtures.ts +102 -0
- package/src/components/story-tree.test.ts +108 -0
- package/src/components/story.ts +159 -0
- package/src/components/syntax-highlight.ts +133 -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/thread-tree.test.ts +134 -0
- package/src/components/thread-tree.ts +109 -0
- package/src/components/truncate-text.ts +7 -0
- package/src/components/truncate-title.test.ts +31 -0
- package/src/components/truncate-title.ts +15 -0
- package/src/config-identity.test.ts +78 -0
- package/src/config.test.ts +579 -34
- package/src/config.ts +533 -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 +238 -0
- package/src/diff-intraline.ts +236 -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/github-identity.test.ts +58 -0
- package/src/github-identity.ts +41 -0
- package/src/herdr.test.ts +9 -34
- package/src/herdr.ts +25 -21
- package/src/index.ts +28 -0
- package/src/integrations.test.ts +41 -0
- package/src/integrations.ts +32 -0
- package/src/intent-dispatch.test.ts +724 -0
- package/src/intent-dispatch.ts +690 -0
- package/src/key-bindings.test.ts +156 -0
- package/src/key-bindings.ts +459 -0
- package/src/keymap.test.ts +456 -115
- package/src/keymap.ts +315 -81
- package/src/kitty-image.test.ts +66 -0
- package/src/kitty-image.ts +138 -0
- package/src/launch-layout.test.ts +67 -0
- package/src/launch-layout.ts +47 -0
- package/src/mark-runs.ts +121 -0
- package/src/markdown-editor-syntax-style.ts +50 -0
- package/src/markdown-grid.test.ts +101 -0
- package/src/markdown-grid.ts +203 -0
- package/src/markdown-highlight.test.ts +61 -0
- package/src/markdown-highlight.ts +122 -0
- package/src/pasted-image.test.ts +67 -0
- package/src/pasted-image.ts +60 -0
- package/src/perf/otlp-trace.test.ts +116 -0
- package/src/perf/otlp-trace.ts +130 -0
- package/src/perf/perf-report.ts +17 -0
- package/src/perf/perf-timings.test.ts +18 -0
- package/src/perf/perf-timings.ts +38 -0
- package/src/prototype-browser.test.ts +81 -0
- package/src/prototype-browser.ts +329 -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 +85 -12
- package/src/serve.test.ts +24 -12
- package/src/serve.ts +52 -10
- package/src/share.ts +237 -0
- package/src/skills.test.ts +74 -0
- package/src/skills.ts +75 -0
- package/src/slash-palette.test.ts +103 -0
- package/src/slash-palette.ts +129 -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 +369 -0
- package/src/theme-presets.test.ts +51 -0
- package/src/theme-presets.ts +219 -0
- package/src/theme.ts +88 -19
- package/src/thread-chords.test.ts +79 -0
- package/src/thread-chords.ts +163 -0
- package/src/thread-code-syntax.ts +45 -0
- package/src/thread-controller.curate.test.ts +476 -0
- package/src/thread-controller.derivation.test.ts +62 -0
- package/src/thread-controller.live-diff.test.ts +322 -0
- package/src/thread-controller.optimistic.test.ts +69 -0
- package/src/thread-controller.share.test.ts +561 -0
- package/src/thread-controller.switch-cache.test.ts +176 -0
- package/src/thread-controller.tree.test.ts +261 -0
- package/src/thread-controller.ts +2050 -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 +153 -0
- package/src/use-annotation-surface.tsx +975 -0
- package/src/use-changes-workbench.ts +190 -0
- package/src/use-frame-measure.ts +41 -0
- package/src/use-remember-layout.ts +31 -0
- package/src/use-repo-changes.ts +87 -0
- package/src/use-settings-dialog.tsx +364 -0
- package/src/use-terminal-virtualizer.ts +259 -0
- package/src/use-thread-body-editing.tsx +58 -0
- package/src/version.ts +5 -0
- package/src/view-diff.test.ts +279 -9
- package/src/view-diff.ts +460 -24
- package/src/view-plan.regression.test.ts +60 -0
- package/src/view-plan.test.ts +545 -0
- package/src/view-plan.ts +1019 -0
- package/src/visual/contrast.test.ts +35 -0
- package/src/visual/contrast.ts +33 -0
- package/src/walk.test.ts +121 -0
- package/src/walk.ts +74 -0
- package/src/workbench-snapshot.test.ts +99 -0
- package/src/workbench-snapshot.ts +59 -0
- package/src/session-controller.ts +0 -398
- 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 -364
|
@@ -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,17 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "../story";
|
|
4
|
+
import { Switch } from "./Switch";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Primitives/Switch" };
|
|
7
|
+
|
|
8
|
+
export const On: Story = {
|
|
9
|
+
render: () => <Switch on onToggle={() => {}} theme={DARK} />,
|
|
10
|
+
expectedColors: [DARK.accent],
|
|
11
|
+
size: { width: 8, height: 1 },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const Off: Story = {
|
|
15
|
+
render: () => <Switch on={false} onToggle={() => {}} theme={DARK} />,
|
|
16
|
+
size: { width: 8, height: 1 },
|
|
17
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On/off switch: a pill with a knob that sits right (accent) when on and left
|
|
3
|
+
* (dim) when off. `onToggle` unifies a mouse click and keyboard activation, so
|
|
4
|
+
* the dialog grammar and a pointer both flip it.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import type { MouseEvent } from "@opentui/core";
|
|
9
|
+
import type { Theme } from "../../theme";
|
|
10
|
+
import { useComponentTheme } from "../theme-context";
|
|
11
|
+
|
|
12
|
+
export interface SwitchProps {
|
|
13
|
+
on: boolean;
|
|
14
|
+
onToggle: () => void;
|
|
15
|
+
theme?: Theme;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function Switch({ on, onToggle, theme }: SwitchProps): React.ReactNode {
|
|
19
|
+
const tokens = useComponentTheme(theme);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<box
|
|
23
|
+
onMouseUp={(event: MouseEvent) => {
|
|
24
|
+
event.stopPropagation();
|
|
25
|
+
onToggle();
|
|
26
|
+
}}
|
|
27
|
+
style={{ flexShrink: 0 }}
|
|
28
|
+
>
|
|
29
|
+
<text fg={on ? tokens.accent : tokens.textDim}>{on ? "( ●)" : "(● )"}</text>
|
|
30
|
+
</box>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// OpenTUI renderer for the headless tree-model; the caller owns expansion and selection.
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { DARK, type Theme } from "../../theme";
|
|
5
|
+
import { flattenTree, statusMeta, type TreeNode, type TreeTone } from "./tree-model";
|
|
6
|
+
import { NERD_TREE_ICONS, type TreeIcons } from "./icons";
|
|
7
|
+
|
|
8
|
+
export interface TreeProps {
|
|
9
|
+
nodes: readonly TreeNode[];
|
|
10
|
+
expandedIds: ReadonlySet<string>;
|
|
11
|
+
selectedId?: string;
|
|
12
|
+
flattenEmptyDirectories?: boolean;
|
|
13
|
+
showStatus?: boolean;
|
|
14
|
+
/** Drop the folder/file glyph prefix, for a plain text tree (e.g. the settings nav). */
|
|
15
|
+
hideIcons?: boolean;
|
|
16
|
+
/** The selected row's backdrop; defaults to `elevated` (invisible on an elevated dialog, use `border` there). */
|
|
17
|
+
selectedBackground?: string;
|
|
18
|
+
indentWidth?: number;
|
|
19
|
+
icons?: TreeIcons;
|
|
20
|
+
onSelect?: (id: string) => void;
|
|
21
|
+
onToggle?: (id: string) => void;
|
|
22
|
+
theme?: Theme;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function rowGlyph(isFolder: boolean, expanded: boolean, icons: TreeIcons): string {
|
|
26
|
+
if (!isFolder) return icons.leaf;
|
|
27
|
+
|
|
28
|
+
return expanded ? icons.expanded : icons.collapsed;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function toneColor(tone: TreeTone, theme: Theme): string {
|
|
32
|
+
if (tone === "green") return theme.green;
|
|
33
|
+
if (tone === "blue") return theme.blue;
|
|
34
|
+
if (tone === "red") return theme.red;
|
|
35
|
+
|
|
36
|
+
return theme.textDim;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function Tree({
|
|
40
|
+
nodes,
|
|
41
|
+
expandedIds,
|
|
42
|
+
selectedId,
|
|
43
|
+
flattenEmptyDirectories,
|
|
44
|
+
showStatus,
|
|
45
|
+
hideIcons,
|
|
46
|
+
selectedBackground,
|
|
47
|
+
indentWidth = 2,
|
|
48
|
+
icons = NERD_TREE_ICONS,
|
|
49
|
+
onSelect,
|
|
50
|
+
onToggle,
|
|
51
|
+
theme,
|
|
52
|
+
}: TreeProps): React.ReactNode {
|
|
53
|
+
const tokens = theme ?? DARK;
|
|
54
|
+
const selectedBackdrop = selectedBackground ?? tokens.elevated;
|
|
55
|
+
const rows = flattenTree(nodes, { expandedIds, flattenEmptyDirectories });
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<box style={{ flexDirection: "column" }}>
|
|
59
|
+
{rows.map((row) => {
|
|
60
|
+
const selected = row.id === selectedId;
|
|
61
|
+
const status = showStatus && row.status !== undefined ? statusMeta(row.status) : null;
|
|
62
|
+
const labelColor = selected
|
|
63
|
+
? tokens.text
|
|
64
|
+
: status
|
|
65
|
+
? toneColor(status.tone, tokens)
|
|
66
|
+
: row.isFolder
|
|
67
|
+
? tokens.text
|
|
68
|
+
: tokens.textMuted;
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<box
|
|
72
|
+
key={row.id}
|
|
73
|
+
id={`tree-row-${row.id}`}
|
|
74
|
+
style={{
|
|
75
|
+
flexDirection: "row",
|
|
76
|
+
paddingLeft: 1 + row.depth * indentWidth,
|
|
77
|
+
paddingRight: 1,
|
|
78
|
+
backgroundColor: selected ? selectedBackdrop : undefined,
|
|
79
|
+
}}
|
|
80
|
+
onMouseUp={() => (row.isFolder ? onToggle?.(row.id) : onSelect?.(row.id))}
|
|
81
|
+
>
|
|
82
|
+
{hideIcons ? null : (
|
|
83
|
+
<text fg={row.isFolder ? tokens.blue : tokens.textDim}>
|
|
84
|
+
{row.icon ?? rowGlyph(row.isFolder, row.expanded, icons)}{" "}
|
|
85
|
+
</text>
|
|
86
|
+
)}
|
|
87
|
+
<box style={{ flexShrink: 1, minWidth: 0 }}>
|
|
88
|
+
<text fg={selected ? tokens.accent : labelColor} truncate>
|
|
89
|
+
{row.label}
|
|
90
|
+
</text>
|
|
91
|
+
</box>
|
|
92
|
+
<box style={{ flexGrow: 1 }} />
|
|
93
|
+
{row.badge !== undefined ? <text fg={tokens.textDim}>{row.badge}</text> : null}
|
|
94
|
+
{status ? <text fg={toneColor(status.tone, tokens)}> {status.letter}</text> : null}
|
|
95
|
+
</box>
|
|
96
|
+
);
|
|
97
|
+
})}
|
|
98
|
+
</box>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locks the single frame border rule: every bordered frame reads this token, so
|
|
3
|
+
* a drift back to rounded (or any other style) fails here first, before the
|
|
4
|
+
* slower VRT snapshots catch it.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { expect, test } from "bun:test";
|
|
8
|
+
import { FRAME_BORDER_STYLE } from "./frame";
|
|
9
|
+
|
|
10
|
+
test("frames use the square border style", () => {
|
|
11
|
+
expect(FRAME_BORDER_STYLE).toBe("single");
|
|
12
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one frame border rule for the design system. Every bordered frame -
|
|
3
|
+
* cards, dialogs, and the stories gallery chrome - reads its corner style
|
|
4
|
+
* from FRAME_BORDER_STYLE, so the corner decision lives in a single place and
|
|
5
|
+
* can never drift between frames. Buttons stay text-first and borderless: the
|
|
6
|
+
* frame they sit in carries the border, not the button.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { BorderStyle } from "@opentui/core";
|
|
10
|
+
|
|
11
|
+
/** Corner style shared by every bordered frame in the app: square, not rounded. */
|
|
12
|
+
export const FRAME_BORDER_STYLE: BorderStyle = "single";
|
|
13
|
+
|
|
14
|
+
/** Every frame's border title renders lowercase, so the convention holds without each caller minding case. */
|
|
15
|
+
export function frameTitle(title: string | undefined): string | undefined {
|
|
16
|
+
return title?.toLowerCase();
|
|
17
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Nerd Font glyphs (Private Use Area / codicons) plus the plus-minus diff mark; render as icons under a Nerd Font, tofu without one.
|
|
2
|
+
|
|
3
|
+
export const NERD = {
|
|
4
|
+
folderClosed: "",
|
|
5
|
+
folderOpen: "",
|
|
6
|
+
file: "",
|
|
7
|
+
chevronRight: "",
|
|
8
|
+
chevronDown: "",
|
|
9
|
+
settings: "",
|
|
10
|
+
search: "",
|
|
11
|
+
expand: "",
|
|
12
|
+
diff: "",
|
|
13
|
+
copy: "",
|
|
14
|
+
unfold: "",
|
|
15
|
+
kebab: "⋮",
|
|
16
|
+
star: "★",
|
|
17
|
+
close: "✕",
|
|
18
|
+
listTree: "",
|
|
19
|
+
sidebarLeft: "",
|
|
20
|
+
sidebarLeftOff: "",
|
|
21
|
+
sidebarRight: "",
|
|
22
|
+
sidebarRightOff: "",
|
|
23
|
+
submit: "",
|
|
24
|
+
zoom: "⛶",
|
|
25
|
+
split: "",
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
// The thin rule under each pane header: an overline sits at the top edge of its cell so it hugs the
|
|
29
|
+
// header text (unlike the default light-horizontal, which floats mid-cell); painted in divider gray upstream.
|
|
30
|
+
export const HEADER_UNDERLINE_CHARS = {
|
|
31
|
+
topLeft: "\u250c",
|
|
32
|
+
topRight: "\u2510",
|
|
33
|
+
bottomLeft: "\u2514",
|
|
34
|
+
bottomRight: "\u2518",
|
|
35
|
+
horizontal: "\u203e",
|
|
36
|
+
vertical: "\u2502",
|
|
37
|
+
topT: "\u252c",
|
|
38
|
+
bottomT: "\u2534",
|
|
39
|
+
leftT: "\u251c",
|
|
40
|
+
rightT: "\u2524",
|
|
41
|
+
cross: "\u253c",
|
|
42
|
+
} as const;
|
|
43
|
+
|
|
44
|
+
export interface TreeIcons {
|
|
45
|
+
collapsed: string;
|
|
46
|
+
expanded: string;
|
|
47
|
+
leaf: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const NERD_TREE_ICONS: TreeIcons = {
|
|
51
|
+
collapsed: NERD.folderClosed,
|
|
52
|
+
expanded: NERD.folderOpen,
|
|
53
|
+
leaf: NERD.file,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Fallback for terminals without a Nerd Font.
|
|
57
|
+
export const ASCII_TREE_ICONS: TreeIcons = {
|
|
58
|
+
collapsed: "▸",
|
|
59
|
+
expanded: "▾",
|
|
60
|
+
leaf: " ",
|
|
61
|
+
};
|