claudeup 4.35.1 → 4.37.0
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 +4 -4
- package/src/__tests__/catalog-cache-store.test.ts +271 -0
- package/src/__tests__/catalog-notice.test.ts +155 -0
- package/src/__tests__/github-budget.test.ts +200 -0
- package/src/__tests__/plugin-manager-fallback.test.ts +200 -8
- package/src/__tests__/scope-squares.test.tsx +165 -0
- package/src/__tests__/theme-adaptive-colors.test.ts +307 -0
- package/src/__tests__/uppercase-keybindings.test.ts +101 -0
- package/src/main.tsx +21 -5
- package/src/opentui.d.ts +21 -12
- package/src/services/catalog-cache-store.ts +218 -0
- package/src/services/github-budget.ts +274 -0
- package/src/services/marketplace-catalog-git.ts +170 -0
- package/src/services/marketplace-catalog.ts +95 -0
- package/src/services/marketplace-fetcher.ts +310 -87
- package/src/services/plugin-manager.ts +103 -92
- package/src/ui/App.tsx +19 -12
- package/src/ui/adapters/catalogNotice.ts +122 -0
- package/src/ui/adapters/pluginsAdapter.ts +174 -168
- package/src/ui/adapters/settingsAdapter.ts +119 -116
- package/src/ui/adapters/skillsAdapter.ts +203 -196
- package/src/ui/components/CategoryHeader.tsx +9 -8
- package/src/ui/components/EmptyFilterState.tsx +10 -5
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/ScopeIndicator.tsx +10 -6
- package/src/ui/components/ScrollableList.tsx +3 -2
- package/src/ui/components/SearchInput.tsx +2 -1
- package/src/ui/components/StyledText.tsx +5 -4
- package/src/ui/components/TabBar.tsx +4 -3
- package/src/ui/components/layout/FooterHints.tsx +37 -30
- package/src/ui/components/layout/Panel.tsx +6 -5
- package/src/ui/components/layout/ProgressBar.tsx +7 -6
- package/src/ui/components/layout/ScopeTabs.tsx +6 -5
- package/src/ui/components/layout/ScreenLayout.tsx +46 -26
- package/src/ui/components/layout/index.ts +3 -3
- package/src/ui/components/modals/ConfirmModal.tsx +12 -11
- package/src/ui/components/modals/InputModal.tsx +14 -6
- package/src/ui/components/modals/LoadingModal.tsx +6 -5
- package/src/ui/components/modals/MessageModal.tsx +9 -8
- package/src/ui/components/modals/SelectModal.tsx +11 -7
- package/src/ui/components/modals/VersionMismatchModal.tsx +14 -16
- package/src/ui/components/primitives/ActionHints.tsx +26 -26
- package/src/ui/components/primitives/DetailSection.tsx +13 -12
- package/src/ui/components/primitives/KeyValueLine.tsx +9 -8
- package/src/ui/components/primitives/ListCategoryRow.tsx +25 -27
- package/src/ui/components/primitives/MetaText.tsx +3 -3
- package/src/ui/components/primitives/ScopeDetail.tsx +48 -48
- package/src/ui/components/primitives/ScopeSquares.tsx +47 -22
- package/src/ui/components/primitives/SelectableRow.tsx +22 -16
- package/src/ui/hooks/useGitignoreModal.ts +78 -74
- package/src/ui/registry.ts +11 -11
- package/src/ui/renderers/cliToolRenderers.tsx +260 -203
- package/src/ui/renderers/gitignoreRenderers.tsx +43 -42
- package/src/ui/renderers/mcpRenderers.tsx +121 -117
- package/src/ui/renderers/pluginRenderers.tsx +566 -471
- package/src/ui/renderers/profileRenderers.tsx +346 -300
- package/src/ui/renderers/settingsRenderers.tsx +183 -176
- package/src/ui/renderers/skillRenderers.tsx +410 -326
- package/src/ui/screens/AliasScreen.tsx +1336 -1309
- package/src/ui/screens/CliToolsScreen.tsx +92 -40
- package/src/ui/screens/EnvVarsScreen.tsx +19 -13
- package/src/ui/screens/GitignoreScreen.tsx +510 -493
- package/src/ui/screens/McpRegistryScreen.tsx +28 -21
- package/src/ui/screens/McpScreen.tsx +12 -3
- package/src/ui/screens/PluginsScreen.tsx +152 -33
- package/src/ui/screens/ProfilesScreen.tsx +39 -23
- package/src/ui/screens/SkillsScreen.tsx +832 -688
- package/src/ui/state/reducer.ts +11 -2
- package/src/ui/state/types.ts +16 -1
- package/src/ui/theme-mode.ts +73 -0
- package/src/ui/theme.ts +147 -53
- package/src/utils/config-dir.ts +47 -0
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import { theme } from "../../theme.js";
|
|
2
3
|
|
|
3
4
|
/** One footer hint: a key (or key group) and the action it performs. */
|
|
4
5
|
export interface FooterHint {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
/** Key(s) for this action, e.g. ["↑", "↓"] or ["U"]. Joined for display. */
|
|
7
|
+
keys: string[];
|
|
8
|
+
label: string;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
// The shared chip palette, not a local one. This file used to define its own
|
|
12
|
+
// (`theme.colors.dim` block + terminal-foreground ink), which is why the footer
|
|
13
|
+
// stayed a low-contrast grey-on-grey strip after the palette was made vivid —
|
|
14
|
+
// the new colours simply never reached it. Anything chip-shaped uses theme.hints.
|
|
15
|
+
const KEY_BG = theme.hints.defaultBg;
|
|
16
|
+
const KEY_FG = theme.hints.fg;
|
|
17
|
+
const LABEL_FG = theme.colors.muted;
|
|
13
18
|
const GAP = " ";
|
|
14
19
|
|
|
15
20
|
/**
|
|
@@ -20,33 +25,35 @@ const GAP = " ";
|
|
|
20
25
|
* Each key is shown in a chip so it reads as a pressable key, not prose.
|
|
21
26
|
* Shared across screens so every footer looks the same.
|
|
22
27
|
*/
|
|
23
|
-
export function FooterHints({
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
28
|
+
export function FooterHints({
|
|
29
|
+
hints,
|
|
30
|
+
}: { hints: FooterHint[] }): React.ReactNode {
|
|
31
|
+
const nodes: React.ReactNode[] = [];
|
|
32
|
+
hints.forEach((hint, i) => {
|
|
33
|
+
if (i > 0) {
|
|
34
|
+
nodes.push(
|
|
35
|
+
<span key={`gap-${i}`} fg={LABEL_FG}>
|
|
36
|
+
{GAP}
|
|
37
|
+
</span>,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
nodes.push(
|
|
41
|
+
<span key={`k-${i}`} bg={KEY_BG} fg={KEY_FG}>
|
|
42
|
+
{` ${hint.keys.join("")} `}
|
|
43
|
+
</span>,
|
|
44
|
+
);
|
|
45
|
+
nodes.push(
|
|
46
|
+
<span key={`l-${i}`} fg={LABEL_FG}>
|
|
47
|
+
{` ${hint.label}`}
|
|
48
|
+
</span>,
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
return <text fg={theme.colors.text}>{nodes}</text>;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
/** Convenience for callers that already have a node: same as <FooterHints>. */
|
|
48
55
|
export function renderFooterHints(hints: FooterHint[]): React.ReactNode {
|
|
49
|
-
|
|
56
|
+
return <FooterHints hints={hints} />;
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
export default FooterHints;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { theme, type UiColor } from "../../theme.js";
|
|
2
3
|
|
|
3
4
|
interface PanelProps {
|
|
4
5
|
/** Panel title */
|
|
@@ -6,9 +7,9 @@ interface PanelProps {
|
|
|
6
7
|
/** Panel content */
|
|
7
8
|
children: React.ReactNode;
|
|
8
9
|
/** Border color */
|
|
9
|
-
borderColor?:
|
|
10
|
+
borderColor?: UiColor;
|
|
10
11
|
/** Title color */
|
|
11
|
-
titleColor?:
|
|
12
|
+
titleColor?: UiColor;
|
|
12
13
|
/** Panel width - number or percentage like "50%" */
|
|
13
14
|
width?: number | `${number}%` | "auto";
|
|
14
15
|
/** Panel height - number or percentage like "50%" */
|
|
@@ -22,14 +23,14 @@ interface PanelProps {
|
|
|
22
23
|
export function Panel({
|
|
23
24
|
title,
|
|
24
25
|
children,
|
|
25
|
-
borderColor =
|
|
26
|
-
titleColor =
|
|
26
|
+
borderColor = theme.colors.accent,
|
|
27
|
+
titleColor = theme.colors.accent,
|
|
27
28
|
width,
|
|
28
29
|
height,
|
|
29
30
|
flexGrow = 1,
|
|
30
31
|
focused = false,
|
|
31
32
|
}: PanelProps) {
|
|
32
|
-
const activeColor = focused ?
|
|
33
|
+
const activeColor = focused ? theme.colors.accent : borderColor;
|
|
33
34
|
|
|
34
35
|
return (
|
|
35
36
|
<box
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { theme } from "../../theme.js";
|
|
2
3
|
|
|
3
4
|
interface ProgressBarProps {
|
|
4
5
|
/** Progress message */
|
|
@@ -21,9 +22,9 @@ export function ProgressBar({ message, current, total }: ProgressBarProps) {
|
|
|
21
22
|
|
|
22
23
|
return (
|
|
23
24
|
<box>
|
|
24
|
-
<text fg=
|
|
25
|
-
<text> {message} </text>
|
|
26
|
-
<text fg=
|
|
25
|
+
<text fg={theme.colors.info}>⟳</text>
|
|
26
|
+
<text fg={theme.colors.text}> {message} </text>
|
|
27
|
+
<text fg={theme.colors.info}>
|
|
27
28
|
[{bar}] {current}/{total}
|
|
28
29
|
</text>
|
|
29
30
|
</box>
|
|
@@ -33,9 +34,9 @@ export function ProgressBar({ message, current, total }: ProgressBarProps) {
|
|
|
33
34
|
// Indeterminate progress
|
|
34
35
|
return (
|
|
35
36
|
<box>
|
|
36
|
-
<text fg=
|
|
37
|
-
<text> {message}</text>
|
|
38
|
-
<text fg=
|
|
37
|
+
<text fg={theme.colors.info}>⟳</text>
|
|
38
|
+
<text fg={theme.colors.text}> {message}</text>
|
|
39
|
+
<text fg={theme.colors.muted}> ...</text>
|
|
39
40
|
</box>
|
|
40
41
|
);
|
|
41
42
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { theme } from "../../theme.js";
|
|
2
3
|
|
|
3
4
|
interface ScopeTabsProps {
|
|
4
5
|
/** Current scope */
|
|
@@ -21,29 +22,29 @@ export function ScopeTabs({
|
|
|
21
22
|
{/* Project tab */}
|
|
22
23
|
<box>
|
|
23
24
|
{isProject ? (
|
|
24
|
-
<text bg=
|
|
25
|
+
<text bg={theme.colors.info} fg={theme.hints.fg}>
|
|
25
26
|
<strong> ◆ Project </strong>
|
|
26
27
|
</text>
|
|
27
28
|
) : (
|
|
28
|
-
<text fg=
|
|
29
|
+
<text fg={theme.colors.muted}> ○ Project </text>
|
|
29
30
|
)}
|
|
30
31
|
</box>
|
|
31
32
|
|
|
32
33
|
{/* Global tab */}
|
|
33
34
|
<box>
|
|
34
35
|
{!isProject ? (
|
|
35
|
-
<text bg=
|
|
36
|
+
<text bg={theme.colors.accent} fg={theme.hints.fg}>
|
|
36
37
|
<strong> ◆ Global </strong>
|
|
37
38
|
</text>
|
|
38
39
|
) : (
|
|
39
|
-
<text fg=
|
|
40
|
+
<text fg={theme.colors.muted}> ○ Global </text>
|
|
40
41
|
)}
|
|
41
42
|
</box>
|
|
42
43
|
|
|
43
44
|
{/* Toggle hint */}
|
|
44
45
|
{toggleHint && (
|
|
45
46
|
<box marginLeft={2}>
|
|
46
|
-
<text fg=
|
|
47
|
+
<text fg={theme.colors.muted}>({toggleHint})</text>
|
|
47
48
|
</box>
|
|
48
49
|
)}
|
|
49
50
|
</box>
|
|
@@ -3,6 +3,7 @@ import { useDimensions } from "../../state/DimensionsContext.js";
|
|
|
3
3
|
import { TabBar } from "../TabBar.js";
|
|
4
4
|
import type { Screen } from "../../state/types.js";
|
|
5
5
|
import { FooterHints, type FooterHint } from "./FooterHints.js";
|
|
6
|
+
import { theme } from "../../theme.js";
|
|
6
7
|
|
|
7
8
|
interface ScreenLayoutProps {
|
|
8
9
|
/** Screen title (e.g., "claudeup Plugins") */
|
|
@@ -22,6 +23,15 @@ interface ScreenLayoutProps {
|
|
|
22
23
|
};
|
|
23
24
|
/** Status line content (for screens without search) - shown in second row */
|
|
24
25
|
statusLine?: React.ReactNode;
|
|
26
|
+
/**
|
|
27
|
+
* One-line warning shown under the header, above the content.
|
|
28
|
+
*
|
|
29
|
+
* For conditions that make the data on screen untrustworthy — not for
|
|
30
|
+
* transient progress. It sits in the fixed chrome rather than in a panel so it
|
|
31
|
+
* cannot be scrolled out of view, and it takes a row from the panels only
|
|
32
|
+
* while it is present.
|
|
33
|
+
*/
|
|
34
|
+
notice?: React.ReactNode;
|
|
25
35
|
/** Footer hints (left side). Pass a FooterHint[] to render key badges
|
|
26
36
|
* (the standard look). A string or ReactNode is still accepted for
|
|
27
37
|
* free-form footers. */
|
|
@@ -32,7 +42,7 @@ interface ScreenLayoutProps {
|
|
|
32
42
|
detailPanel: React.ReactNode;
|
|
33
43
|
}
|
|
34
44
|
|
|
35
|
-
const HEADER_COLOR =
|
|
45
|
+
const HEADER_COLOR = theme.colors.accent;
|
|
36
46
|
|
|
37
47
|
export function ScreenLayout({
|
|
38
48
|
title,
|
|
@@ -40,6 +50,7 @@ export function ScreenLayout({
|
|
|
40
50
|
currentScreen,
|
|
41
51
|
search,
|
|
42
52
|
statusLine,
|
|
53
|
+
notice,
|
|
43
54
|
footerHints,
|
|
44
55
|
listPanel,
|
|
45
56
|
detailPanel,
|
|
@@ -49,8 +60,8 @@ export function ScreenLayout({
|
|
|
49
60
|
const hasSearchBar = search && (search.isActive || search.query);
|
|
50
61
|
|
|
51
62
|
// Fixed chrome: top line + tabs + line + header + separator + footer = 6
|
|
52
|
-
// Search bar adds 1 when active
|
|
53
|
-
const fixedHeight = 6 + (hasSearchBar ? 1 : 0);
|
|
63
|
+
// Search bar adds 1 when active; the notice adds 1 while present.
|
|
64
|
+
const fixedHeight = 6 + (hasSearchBar ? 1 : 0) + (notice ? 1 : 0);
|
|
54
65
|
const panelHeight = Math.max(5, dimensions.contentHeight - fixedHeight);
|
|
55
66
|
const lineWidth = Math.max(10, dimensions.terminalWidth - 4);
|
|
56
67
|
|
|
@@ -58,7 +69,7 @@ export function ScreenLayout({
|
|
|
58
69
|
<box flexDirection="column" height={dimensions.contentHeight}>
|
|
59
70
|
{/* Line above tabs */}
|
|
60
71
|
<box height={1} paddingLeft={1} paddingRight={1}>
|
|
61
|
-
<text fg=
|
|
72
|
+
<text fg={theme.colors.dim}>{"─".repeat(lineWidth)}</text>
|
|
62
73
|
</box>
|
|
63
74
|
|
|
64
75
|
{/* Tab bar */}
|
|
@@ -68,15 +79,21 @@ export function ScreenLayout({
|
|
|
68
79
|
|
|
69
80
|
{/* Line below tabs */}
|
|
70
81
|
<box height={1} paddingLeft={1} paddingRight={1}>
|
|
71
|
-
<text fg=
|
|
82
|
+
<text fg={theme.colors.dim}>{"─".repeat(lineWidth)}</text>
|
|
72
83
|
</box>
|
|
73
84
|
|
|
74
85
|
{/* Header — title on left, subtitle/status on right */}
|
|
75
|
-
<box
|
|
86
|
+
<box
|
|
87
|
+
height={1}
|
|
88
|
+
paddingLeft={1}
|
|
89
|
+
paddingRight={1}
|
|
90
|
+
flexDirection="row"
|
|
91
|
+
justifyContent="space-between"
|
|
92
|
+
>
|
|
76
93
|
<text fg={HEADER_COLOR}>
|
|
77
94
|
<strong>{title}</strong>
|
|
78
95
|
</text>
|
|
79
|
-
{subtitle && <text fg=
|
|
96
|
+
{subtitle && <text fg={theme.colors.muted}>{subtitle}</text>}
|
|
80
97
|
{!subtitle && statusLine ? statusLine : null}
|
|
81
98
|
</box>
|
|
82
99
|
|
|
@@ -84,24 +101,33 @@ export function ScreenLayout({
|
|
|
84
101
|
{hasSearchBar && (
|
|
85
102
|
<box height={1} paddingLeft={1} paddingRight={1}>
|
|
86
103
|
{search.isActive ? (
|
|
87
|
-
<text>
|
|
88
|
-
<span fg=
|
|
89
|
-
<span fg=
|
|
90
|
-
<span bg=
|
|
104
|
+
<text fg={theme.colors.text}>
|
|
105
|
+
<span fg={theme.colors.success}>{"Filter: "}</span>
|
|
106
|
+
<span fg={theme.colors.text}>{search.query}</span>
|
|
107
|
+
<span bg={theme.colors.text} fg={theme.hints.fg}>
|
|
108
|
+
{" "}
|
|
109
|
+
</span>
|
|
91
110
|
</text>
|
|
92
111
|
) : (
|
|
93
|
-
<text>
|
|
94
|
-
<span fg=
|
|
95
|
-
<span fg=
|
|
96
|
-
<span fg=
|
|
112
|
+
<text fg={theme.colors.text}>
|
|
113
|
+
<span fg={theme.colors.success}>{"Filter: "}</span>
|
|
114
|
+
<span fg={theme.colors.warning}>{search.query}</span>
|
|
115
|
+
<span fg={theme.colors.muted}> (Esc to clear)</span>
|
|
97
116
|
</text>
|
|
98
117
|
)}
|
|
99
118
|
</box>
|
|
100
119
|
)}
|
|
101
120
|
|
|
121
|
+
{/* Warning line — only when the data on screen cannot be trusted */}
|
|
122
|
+
{notice && (
|
|
123
|
+
<box height={1} paddingLeft={1} paddingRight={1}>
|
|
124
|
+
{notice}
|
|
125
|
+
</box>
|
|
126
|
+
)}
|
|
127
|
+
|
|
102
128
|
{/* Separator below header */}
|
|
103
129
|
<box height={1} paddingLeft={1} paddingRight={1}>
|
|
104
|
-
<text fg=
|
|
130
|
+
<text fg={theme.colors.border}>{"─".repeat(lineWidth)}</text>
|
|
105
131
|
</box>
|
|
106
132
|
|
|
107
133
|
{/* Main content area */}
|
|
@@ -118,19 +144,13 @@ export function ScreenLayout({
|
|
|
118
144
|
|
|
119
145
|
{/* Vertical separator */}
|
|
120
146
|
<box flexDirection="column" width={1} height={panelHeight}>
|
|
121
|
-
<text fg=
|
|
147
|
+
<text fg={theme.colors.border}>{"│".repeat(panelHeight)}</text>
|
|
122
148
|
</box>
|
|
123
149
|
|
|
124
150
|
{/* Detail panel — scrollable */}
|
|
125
151
|
<box width="50%" height={panelHeight} paddingLeft={1}>
|
|
126
|
-
<scrollbox
|
|
127
|
-
|
|
128
|
-
scrollY={true}
|
|
129
|
-
scrollX={false}
|
|
130
|
-
>
|
|
131
|
-
<box flexDirection="column">
|
|
132
|
-
{detailPanel}
|
|
133
|
-
</box>
|
|
152
|
+
<scrollbox height={panelHeight} scrollY={true} scrollX={false}>
|
|
153
|
+
<box flexDirection="column">{detailPanel}</box>
|
|
134
154
|
</scrollbox>
|
|
135
155
|
</box>
|
|
136
156
|
</box>
|
|
@@ -140,7 +160,7 @@ export function ScreenLayout({
|
|
|
140
160
|
{Array.isArray(footerHints) ? (
|
|
141
161
|
<FooterHints hints={footerHints as FooterHint[]} />
|
|
142
162
|
) : typeof footerHints === "string" ? (
|
|
143
|
-
<text fg=
|
|
163
|
+
<text fg={theme.colors.muted}>{footerHints}</text>
|
|
144
164
|
) : (
|
|
145
165
|
footerHints
|
|
146
166
|
)}
|
|
@@ -3,7 +3,7 @@ export { ScopeTabs } from "./ScopeTabs.js";
|
|
|
3
3
|
export { ProgressBar } from "./ProgressBar.js";
|
|
4
4
|
export { ScreenLayout } from "./ScreenLayout.js";
|
|
5
5
|
export {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
FooterHints,
|
|
7
|
+
renderFooterHints,
|
|
8
|
+
type FooterHint,
|
|
9
9
|
} from "./FooterHints.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { useKeyboard } from "../../hooks/useKeyboard.js";
|
|
3
|
+
import { theme } from "../../theme.js";
|
|
3
4
|
|
|
4
5
|
interface ConfirmModalProps {
|
|
5
6
|
/** Modal title */
|
|
@@ -19,9 +20,9 @@ export function ConfirmModal({
|
|
|
19
20
|
onCancel,
|
|
20
21
|
}: ConfirmModalProps) {
|
|
21
22
|
useKeyboard((key) => {
|
|
22
|
-
if (key.name === "y"
|
|
23
|
+
if (key.name === "y") {
|
|
23
24
|
onConfirm();
|
|
24
|
-
} else if (key.name === "n" || key.name === "
|
|
25
|
+
} else if (key.name === "n" || key.name === "escape") {
|
|
25
26
|
onCancel();
|
|
26
27
|
}
|
|
27
28
|
});
|
|
@@ -31,8 +32,8 @@ export function ConfirmModal({
|
|
|
31
32
|
flexDirection="column"
|
|
32
33
|
border
|
|
33
34
|
borderStyle="rounded"
|
|
34
|
-
borderColor=
|
|
35
|
-
backgroundColor=
|
|
35
|
+
borderColor={theme.colors.dim}
|
|
36
|
+
backgroundColor={theme.surface.bg}
|
|
36
37
|
paddingLeft={3}
|
|
37
38
|
paddingRight={3}
|
|
38
39
|
paddingTop={1}
|
|
@@ -40,19 +41,19 @@ export function ConfirmModal({
|
|
|
40
41
|
width={60}
|
|
41
42
|
>
|
|
42
43
|
<box marginBottom={1}>
|
|
43
|
-
<text fg=
|
|
44
|
+
<text fg={theme.colors.text}>
|
|
44
45
|
<strong>{title}</strong>
|
|
45
46
|
</text>
|
|
46
47
|
</box>
|
|
47
48
|
<box marginBottom={1}>
|
|
48
|
-
<text fg=
|
|
49
|
+
<text fg={theme.colors.muted}>{message}</text>
|
|
49
50
|
</box>
|
|
50
51
|
<box marginTop={1} flexDirection="row">
|
|
51
|
-
<text fg=
|
|
52
|
-
<text fg=
|
|
53
|
-
<text fg=
|
|
54
|
-
<text fg=
|
|
55
|
-
<text fg=
|
|
52
|
+
<text fg={theme.colors.muted}>Press </text>
|
|
53
|
+
<text fg={theme.colors.text}>Y</text>
|
|
54
|
+
<text fg={theme.colors.muted}> to confirm • </text>
|
|
55
|
+
<text fg={theme.colors.text}>N</text>
|
|
56
|
+
<text fg={theme.colors.muted}> to cancel</text>
|
|
56
57
|
</box>
|
|
57
58
|
</box>
|
|
58
59
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { theme } from "../../theme.js";
|
|
2
3
|
|
|
3
4
|
interface InputModalProps {
|
|
4
5
|
/** Modal title */
|
|
@@ -24,8 +25,8 @@ export function InputModal({
|
|
|
24
25
|
flexDirection="column"
|
|
25
26
|
border
|
|
26
27
|
borderStyle="rounded"
|
|
27
|
-
borderColor=
|
|
28
|
-
backgroundColor=
|
|
28
|
+
borderColor={theme.colors.dim}
|
|
29
|
+
backgroundColor={theme.surface.bg}
|
|
29
30
|
paddingLeft={3}
|
|
30
31
|
paddingRight={3}
|
|
31
32
|
paddingTop={1}
|
|
@@ -33,16 +34,23 @@ export function InputModal({
|
|
|
33
34
|
width={60}
|
|
34
35
|
>
|
|
35
36
|
<box marginBottom={1}>
|
|
36
|
-
<text fg=
|
|
37
|
+
<text fg={theme.colors.text}>
|
|
37
38
|
<strong>{title}</strong>
|
|
38
39
|
</text>
|
|
39
40
|
</box>
|
|
40
41
|
|
|
41
42
|
<box marginBottom={1}>
|
|
42
|
-
<text fg=
|
|
43
|
+
<text fg={theme.colors.muted}>{label}</text>
|
|
43
44
|
</box>
|
|
44
45
|
|
|
45
|
-
<box
|
|
46
|
+
<box
|
|
47
|
+
border
|
|
48
|
+
borderStyle="rounded"
|
|
49
|
+
borderColor={theme.colors.dim}
|
|
50
|
+
paddingLeft={1}
|
|
51
|
+
paddingRight={1}
|
|
52
|
+
width={54}
|
|
53
|
+
>
|
|
46
54
|
<input
|
|
47
55
|
value={defaultValue}
|
|
48
56
|
onSubmit={onSubmit as any}
|
|
@@ -52,7 +60,7 @@ export function InputModal({
|
|
|
52
60
|
</box>
|
|
53
61
|
|
|
54
62
|
<box marginTop={1}>
|
|
55
|
-
<text fg=
|
|
63
|
+
<text fg={theme.colors.muted}>↵ to confirm • Esc to cancel</text>
|
|
56
64
|
</box>
|
|
57
65
|
</box>
|
|
58
66
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { theme } from "../../theme.js";
|
|
2
3
|
|
|
3
4
|
interface LoadingModalProps {
|
|
4
5
|
/** Loading message — supports multiline (first line = status, subsequent = detail) */
|
|
@@ -27,20 +28,20 @@ export function LoadingModal({ message }: LoadingModalProps) {
|
|
|
27
28
|
flexDirection="column"
|
|
28
29
|
border
|
|
29
30
|
borderStyle="rounded"
|
|
30
|
-
borderColor=
|
|
31
|
-
backgroundColor=
|
|
31
|
+
borderColor={theme.colors.dim}
|
|
32
|
+
backgroundColor={theme.surface.bg}
|
|
32
33
|
paddingLeft={3}
|
|
33
34
|
paddingRight={3}
|
|
34
35
|
paddingTop={1}
|
|
35
36
|
paddingBottom={1}
|
|
36
37
|
>
|
|
37
38
|
<box flexDirection="row">
|
|
38
|
-
<text fg=
|
|
39
|
-
<text fg=
|
|
39
|
+
<text fg={theme.colors.muted}>{SPINNER_FRAMES[frame]}</text>
|
|
40
|
+
<text fg={theme.colors.text}> {mainMessage}</text>
|
|
40
41
|
</box>
|
|
41
42
|
{detail ? (
|
|
42
43
|
<box marginTop={0} paddingLeft={2}>
|
|
43
|
-
<text fg=
|
|
44
|
+
<text fg={theme.colors.dim}>{detail}</text>
|
|
44
45
|
</box>
|
|
45
46
|
) : null}
|
|
46
47
|
</box>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { useKeyboard } from "../../hooks/useKeyboard.js";
|
|
3
|
+
import { theme } from "../../theme.js";
|
|
3
4
|
|
|
4
5
|
interface MessageModalProps {
|
|
5
6
|
/** Modal title */
|
|
@@ -13,9 +14,9 @@ interface MessageModalProps {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
const variantConfig = {
|
|
16
|
-
info: { icon: "ℹ", color:
|
|
17
|
-
success: { icon: "✓", color:
|
|
18
|
-
error: { icon: "✗", color:
|
|
17
|
+
info: { icon: "ℹ", color: theme.colors.link },
|
|
18
|
+
success: { icon: "✓", color: theme.colors.success },
|
|
19
|
+
error: { icon: "✗", color: theme.colors.danger },
|
|
19
20
|
} as const;
|
|
20
21
|
|
|
21
22
|
export function MessageModal({
|
|
@@ -36,8 +37,8 @@ export function MessageModal({
|
|
|
36
37
|
flexDirection="column"
|
|
37
38
|
border
|
|
38
39
|
borderStyle="rounded"
|
|
39
|
-
borderColor=
|
|
40
|
-
backgroundColor=
|
|
40
|
+
borderColor={theme.colors.dim}
|
|
41
|
+
backgroundColor={theme.surface.bg}
|
|
41
42
|
paddingLeft={3}
|
|
42
43
|
paddingRight={3}
|
|
43
44
|
paddingTop={1}
|
|
@@ -46,17 +47,17 @@ export function MessageModal({
|
|
|
46
47
|
>
|
|
47
48
|
<box marginBottom={1}>
|
|
48
49
|
<text fg={config.color}>{config.icon} </text>
|
|
49
|
-
<text fg=
|
|
50
|
+
<text fg={theme.colors.text}>
|
|
50
51
|
<strong>{title}</strong>
|
|
51
52
|
</text>
|
|
52
53
|
</box>
|
|
53
54
|
|
|
54
55
|
<box marginBottom={1}>
|
|
55
|
-
<text fg=
|
|
56
|
+
<text fg={theme.colors.muted}>{message}</text>
|
|
56
57
|
</box>
|
|
57
58
|
|
|
58
59
|
<box marginTop={1}>
|
|
59
|
-
<text fg=
|
|
60
|
+
<text fg={theme.colors.muted}>Press any key to continue</text>
|
|
60
61
|
</box>
|
|
61
62
|
</box>
|
|
62
63
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { SelectOption } from "../../state/types.js";
|
|
3
3
|
import { useDimensions } from "../../state/DimensionsContext.js";
|
|
4
|
+
import { theme } from "../../theme.js";
|
|
4
5
|
|
|
5
6
|
interface SelectModalProps {
|
|
6
7
|
/** Modal title */
|
|
@@ -44,8 +45,8 @@ export function SelectModal({
|
|
|
44
45
|
flexDirection="column"
|
|
45
46
|
border
|
|
46
47
|
borderStyle="rounded"
|
|
47
|
-
borderColor=
|
|
48
|
-
backgroundColor=
|
|
48
|
+
borderColor={theme.colors.dim}
|
|
49
|
+
backgroundColor={theme.surface.bg}
|
|
49
50
|
paddingLeft={3}
|
|
50
51
|
paddingRight={3}
|
|
51
52
|
paddingTop={1}
|
|
@@ -53,21 +54,24 @@ export function SelectModal({
|
|
|
53
54
|
width={width}
|
|
54
55
|
>
|
|
55
56
|
<box marginBottom={1}>
|
|
56
|
-
<text fg=
|
|
57
|
+
<text fg={theme.colors.text}>
|
|
57
58
|
<strong>{title}</strong>
|
|
58
59
|
</text>
|
|
59
60
|
</box>
|
|
60
61
|
|
|
61
62
|
<box marginBottom={1}>
|
|
62
|
-
<text fg=
|
|
63
|
+
<text fg={theme.colors.muted}>{message}</text>
|
|
63
64
|
</box>
|
|
64
65
|
|
|
65
66
|
<box flexDirection="column" paddingLeft={1}>
|
|
66
67
|
{options.map((option, idx) => {
|
|
67
68
|
const isSelected = idx === selectedIndex;
|
|
68
69
|
return (
|
|
69
|
-
<text
|
|
70
|
-
|
|
70
|
+
<text
|
|
71
|
+
key={option.value}
|
|
72
|
+
fg={isSelected ? theme.colors.text : theme.colors.muted}
|
|
73
|
+
>
|
|
74
|
+
<span fg={isSelected ? theme.colors.text : theme.colors.muted}>
|
|
71
75
|
{isSelected ? "❯ " : " "}
|
|
72
76
|
</span>
|
|
73
77
|
{isSelected ? <strong>{option.label}</strong> : option.label}
|
|
@@ -77,7 +81,7 @@ export function SelectModal({
|
|
|
77
81
|
</box>
|
|
78
82
|
|
|
79
83
|
<box marginTop={1}>
|
|
80
|
-
<text fg=
|
|
84
|
+
<text fg={theme.colors.muted}>↑↓ Select • ↵ Confirm • Esc Cancel</text>
|
|
81
85
|
</box>
|
|
82
86
|
</box>
|
|
83
87
|
);
|