sncommit 1.0.0 → 1.0.2
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/README.md +1 -1
- package/dist/index.js +93 -99
- package/package.json +5 -2
- package/bun.lock +0 -705
- package/eslint.config.mjs +0 -34
- package/src/components/App.tsx +0 -357
- package/src/components/CommitSuggestions.tsx +0 -157
- package/src/components/ConfigApp.tsx +0 -258
- package/src/components/CustomInputPrompt.tsx +0 -82
- package/src/components/StagedFiles.tsx +0 -52
- package/src/components/TuiDialog.tsx +0 -177
- package/src/index.tsx +0 -150
- package/src/services/git.ts +0 -128
- package/src/services/groq.ts +0 -360
- package/src/suppress-warnings.ts +0 -18
- package/src/types/index.ts +0 -36
- package/src/utils/config.ts +0 -66
- package/tsconfig.json +0 -19
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import React, { useState, useCallback } from "react";
|
|
2
|
-
import { Box, Text, useInput, useApp, Key } from "ink";
|
|
3
|
-
import { configManager } from "../utils/config";
|
|
4
|
-
import { Config } from "../types";
|
|
5
|
-
import { TuiDialog, DialogType } from "./TuiDialog";
|
|
6
|
-
|
|
7
|
-
interface ConfigAppProps {
|
|
8
|
-
onExit: (message?: string) => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const ConfigApp: React.FC<ConfigAppProps> = ({ onExit }) => {
|
|
12
|
-
const { exit } = useApp();
|
|
13
|
-
const [config, setConfig] = useState<Config>(configManager.getConfig());
|
|
14
|
-
|
|
15
|
-
const [activeDialog, setActiveDialog] = useState<{
|
|
16
|
-
key: keyof Config;
|
|
17
|
-
type: DialogType;
|
|
18
|
-
title: string;
|
|
19
|
-
options?: string[];
|
|
20
|
-
} | null>(null);
|
|
21
|
-
|
|
22
|
-
const [menuIndex, setMenuIndex] = useState(0);
|
|
23
|
-
|
|
24
|
-
const menuItems = [
|
|
25
|
-
{
|
|
26
|
-
key: "groqApiKey" as keyof Config,
|
|
27
|
-
label: "Groq API Key",
|
|
28
|
-
type: "password" as const,
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
key: "model" as keyof Config,
|
|
32
|
-
label: "AI Model",
|
|
33
|
-
type: "select" as const,
|
|
34
|
-
options: [
|
|
35
|
-
"llama-3.1-8b-instant",
|
|
36
|
-
"llama-3.3-70b-versatile",
|
|
37
|
-
"openai/gpt-oss-20b",
|
|
38
|
-
],
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
key: "commitStyle" as keyof Config,
|
|
42
|
-
label: "Commit Style",
|
|
43
|
-
type: "select" as const,
|
|
44
|
-
options: ["conventional", "simple", "detailed"],
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
key: "customPrompt" as keyof Config,
|
|
48
|
-
label: "Custom Prompt",
|
|
49
|
-
type: "textarea" as const,
|
|
50
|
-
},
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
const saveAndExit = () => {
|
|
54
|
-
const finalConfig = {
|
|
55
|
-
...config,
|
|
56
|
-
// model was hardcoded here previously, now we let user select it
|
|
57
|
-
// model: "llama-3.1-8b-instant",
|
|
58
|
-
maxHistoryCommits: 40,
|
|
59
|
-
language: "en",
|
|
60
|
-
};
|
|
61
|
-
configManager.updateConfig(finalConfig);
|
|
62
|
-
onExit("Configuration saved");
|
|
63
|
-
exit();
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const cancelAndExit = () => {
|
|
67
|
-
onExit("Configuration cancelled");
|
|
68
|
-
exit();
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const handleInput = useCallback(
|
|
72
|
-
(input: string, key: Key) => {
|
|
73
|
-
if (activeDialog) return;
|
|
74
|
-
|
|
75
|
-
if (key.upArrow) {
|
|
76
|
-
setMenuIndex((prev) => (prev > 0 ? prev - 1 : menuItems.length + 1));
|
|
77
|
-
} else if (key.downArrow) {
|
|
78
|
-
setMenuIndex((prev) => (prev < menuItems.length + 1 ? prev + 1 : 0));
|
|
79
|
-
} else if (key.return) {
|
|
80
|
-
if (menuIndex < menuItems.length) {
|
|
81
|
-
const item = menuItems[menuIndex];
|
|
82
|
-
setActiveDialog({
|
|
83
|
-
key: item.key,
|
|
84
|
-
type: item.type,
|
|
85
|
-
title: `Edit ${item.label}`,
|
|
86
|
-
options: item.options,
|
|
87
|
-
});
|
|
88
|
-
} else if (menuIndex === menuItems.length) {
|
|
89
|
-
saveAndExit();
|
|
90
|
-
} else {
|
|
91
|
-
cancelAndExit();
|
|
92
|
-
}
|
|
93
|
-
} else if (key.escape || (key.ctrl && input === "c")) {
|
|
94
|
-
cancelAndExit();
|
|
95
|
-
}
|
|
96
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
97
|
-
},
|
|
98
|
-
[activeDialog, menuIndex, menuItems],
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
useInput(handleInput, { isActive: !activeDialog });
|
|
102
|
-
|
|
103
|
-
const handleDialogSubmit = (value: string) => {
|
|
104
|
-
if (activeDialog) {
|
|
105
|
-
setConfig((prev) => ({ ...prev, [activeDialog.key]: value }));
|
|
106
|
-
setActiveDialog(null);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const handleDialogCancel = () => {
|
|
111
|
-
setActiveDialog(null);
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const renderValue = (isSelected: boolean, key: keyof Config) => {
|
|
115
|
-
const val = config[key];
|
|
116
|
-
const textColor = isSelected ? "#111827" : "#e5e7eb";
|
|
117
|
-
const bgColor = isSelected ? "#60a5fa" : undefined;
|
|
118
|
-
|
|
119
|
-
if (!val)
|
|
120
|
-
return (
|
|
121
|
-
<Text color={textColor} backgroundColor={bgColor}>
|
|
122
|
-
{" "}
|
|
123
|
-
(not set)
|
|
124
|
-
</Text>
|
|
125
|
-
);
|
|
126
|
-
if (key === "groqApiKey")
|
|
127
|
-
return (
|
|
128
|
-
<Text color={textColor} backgroundColor={bgColor}>
|
|
129
|
-
••••••••
|
|
130
|
-
</Text>
|
|
131
|
-
);
|
|
132
|
-
if (key === "customPrompt")
|
|
133
|
-
return (
|
|
134
|
-
<Text color={textColor} backgroundColor={bgColor}>
|
|
135
|
-
{(val as string).substring(0, 20) +
|
|
136
|
-
((val as string).length > 20 ? "..." : "")}
|
|
137
|
-
</Text>
|
|
138
|
-
);
|
|
139
|
-
return (
|
|
140
|
-
<Text color={textColor} backgroundColor={bgColor}>
|
|
141
|
-
{val}
|
|
142
|
-
</Text>
|
|
143
|
-
);
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
return (
|
|
147
|
-
<Box flexDirection="column" padding={1} flexGrow={1} width="100%">
|
|
148
|
-
{/* Dialog or Main Content */}
|
|
149
|
-
{activeDialog ? (
|
|
150
|
-
<Box
|
|
151
|
-
flexGrow={1}
|
|
152
|
-
justifyContent="center"
|
|
153
|
-
alignItems="center"
|
|
154
|
-
paddingX={2}
|
|
155
|
-
>
|
|
156
|
-
<TuiDialog
|
|
157
|
-
title={activeDialog.title}
|
|
158
|
-
type={activeDialog.type}
|
|
159
|
-
initialValue={String(config[activeDialog.key] || "")}
|
|
160
|
-
options={activeDialog.options}
|
|
161
|
-
onSubmit={handleDialogSubmit}
|
|
162
|
-
onCancel={handleDialogCancel}
|
|
163
|
-
/>
|
|
164
|
-
</Box>
|
|
165
|
-
) : (
|
|
166
|
-
<Box
|
|
167
|
-
flexDirection="column"
|
|
168
|
-
flexGrow={1}
|
|
169
|
-
borderStyle="round"
|
|
170
|
-
borderColor="#374151"
|
|
171
|
-
paddingX={1}
|
|
172
|
-
marginBottom={1}
|
|
173
|
-
>
|
|
174
|
-
{/* Menu Items */}
|
|
175
|
-
<Box flexDirection="column" marginBottom={2}>
|
|
176
|
-
{menuItems.map((item, index) => (
|
|
177
|
-
<Box
|
|
178
|
-
key={item.key}
|
|
179
|
-
flexDirection="row"
|
|
180
|
-
justifyContent="space-between"
|
|
181
|
-
paddingY={0.5}
|
|
182
|
-
paddingX={1}
|
|
183
|
-
>
|
|
184
|
-
<Box flexGrow={0.6}>
|
|
185
|
-
<Text
|
|
186
|
-
bold={index === menuIndex}
|
|
187
|
-
color={index === menuIndex ? "#111827" : "#e5e7eb"}
|
|
188
|
-
backgroundColor={
|
|
189
|
-
index === menuIndex ? "#60a5fa" : undefined
|
|
190
|
-
}
|
|
191
|
-
>
|
|
192
|
-
{index === menuIndex ? "› " : " "}
|
|
193
|
-
{item.label}
|
|
194
|
-
</Text>
|
|
195
|
-
</Box>
|
|
196
|
-
<Box flexGrow={0.4}>
|
|
197
|
-
<Text
|
|
198
|
-
color={index === menuIndex ? "#111827" : "#e5e7eb"}
|
|
199
|
-
backgroundColor={
|
|
200
|
-
index === menuIndex ? "#60a5fa" : undefined
|
|
201
|
-
}
|
|
202
|
-
>
|
|
203
|
-
{renderValue(index === menuIndex, item.key)}
|
|
204
|
-
</Text>
|
|
205
|
-
</Box>
|
|
206
|
-
</Box>
|
|
207
|
-
))}
|
|
208
|
-
</Box>
|
|
209
|
-
|
|
210
|
-
{/* Actions */}
|
|
211
|
-
<Box flexDirection="row" marginBottom={2}>
|
|
212
|
-
<Box marginRight={2}>
|
|
213
|
-
<Text
|
|
214
|
-
bold={menuIndex === menuItems.length}
|
|
215
|
-
color={menuIndex === menuItems.length ? "#111827" : "#e5e7eb"}
|
|
216
|
-
backgroundColor={
|
|
217
|
-
menuIndex === menuItems.length ? "#10b981" : undefined
|
|
218
|
-
}
|
|
219
|
-
>
|
|
220
|
-
{menuIndex === menuItems.length ? "› " : " "}Save & Exit
|
|
221
|
-
</Text>
|
|
222
|
-
</Box>
|
|
223
|
-
<Box>
|
|
224
|
-
<Text
|
|
225
|
-
bold={menuIndex === menuItems.length + 1}
|
|
226
|
-
color={
|
|
227
|
-
menuIndex === menuItems.length + 1 ? "#111827" : "#e5e7eb"
|
|
228
|
-
}
|
|
229
|
-
backgroundColor={
|
|
230
|
-
menuIndex === menuItems.length + 1 ? "#ef4444" : undefined
|
|
231
|
-
}
|
|
232
|
-
>
|
|
233
|
-
{menuIndex === menuItems.length + 1 ? "› " : " "}Cancel
|
|
234
|
-
</Text>
|
|
235
|
-
</Box>
|
|
236
|
-
</Box>
|
|
237
|
-
</Box>
|
|
238
|
-
)}
|
|
239
|
-
|
|
240
|
-
{/* Footer - only show when no dialog is open */}
|
|
241
|
-
{!activeDialog && (
|
|
242
|
-
<Box
|
|
243
|
-
borderTop={true}
|
|
244
|
-
borderStyle="single"
|
|
245
|
-
borderColor="#374151"
|
|
246
|
-
paddingTop={1}
|
|
247
|
-
paddingLeft={1}
|
|
248
|
-
paddingRight={1}
|
|
249
|
-
>
|
|
250
|
-
<Text color="#6b7280">
|
|
251
|
-
Use <Text color="#60a5fa">↑↓</Text> to navigate,{" "}
|
|
252
|
-
<Text color="#10b981">Enter</Text> to edit/select
|
|
253
|
-
</Text>
|
|
254
|
-
</Box>
|
|
255
|
-
)}
|
|
256
|
-
</Box>
|
|
257
|
-
);
|
|
258
|
-
};
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import { Box, Text, useInput } from "ink";
|
|
3
|
-
|
|
4
|
-
interface CustomInputPromptProps {
|
|
5
|
-
value: string;
|
|
6
|
-
onChange: (value: string) => void;
|
|
7
|
-
onSubmit: () => void;
|
|
8
|
-
onCancel: () => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const CustomInputPrompt: React.FC<CustomInputPromptProps> = ({
|
|
12
|
-
value,
|
|
13
|
-
onChange,
|
|
14
|
-
onSubmit,
|
|
15
|
-
onCancel,
|
|
16
|
-
}) => {
|
|
17
|
-
const [cursorPosition, setCursorPosition] = useState(value.length);
|
|
18
|
-
|
|
19
|
-
useInput((input, key) => {
|
|
20
|
-
if (key.return) {
|
|
21
|
-
onSubmit();
|
|
22
|
-
} else if (key.escape) {
|
|
23
|
-
onCancel();
|
|
24
|
-
} else if (key.backspace || key.delete) {
|
|
25
|
-
if (cursorPosition > 0) {
|
|
26
|
-
const before = value.slice(0, cursorPosition - 1);
|
|
27
|
-
const after = value.slice(cursorPosition);
|
|
28
|
-
onChange(before + after);
|
|
29
|
-
setCursorPosition(cursorPosition - 1);
|
|
30
|
-
}
|
|
31
|
-
} else if (key.leftArrow) {
|
|
32
|
-
setCursorPosition((prev: number) => Math.max(0, prev - 1));
|
|
33
|
-
} else if (key.rightArrow) {
|
|
34
|
-
setCursorPosition((prev: number) => Math.min(value.length, prev + 1));
|
|
35
|
-
} else if (input && !key.ctrl && !key.meta) {
|
|
36
|
-
const before = value.slice(0, cursorPosition);
|
|
37
|
-
const after = value.slice(cursorPosition);
|
|
38
|
-
onChange(before + input + after);
|
|
39
|
-
setCursorPosition(cursorPosition + input.length);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const displayValue = value || "";
|
|
44
|
-
const beforeCursor = displayValue.slice(0, cursorPosition);
|
|
45
|
-
const cursorChar = displayValue[cursorPosition] || " ";
|
|
46
|
-
const afterCursor = displayValue.slice(cursorPosition + 1);
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<Box
|
|
50
|
-
flexDirection="column"
|
|
51
|
-
borderStyle="round"
|
|
52
|
-
borderColor="#8b5cf6"
|
|
53
|
-
paddingX={1}
|
|
54
|
-
>
|
|
55
|
-
<Box>
|
|
56
|
-
<Text bold color="#8b5cf6">
|
|
57
|
-
Custom Message
|
|
58
|
-
</Text>
|
|
59
|
-
</Box>
|
|
60
|
-
|
|
61
|
-
<Box marginLeft={1}>
|
|
62
|
-
<Text>
|
|
63
|
-
<Text color="#e5e7eb">{beforeCursor}</Text>
|
|
64
|
-
<Text backgroundColor="#8b5cf6" color="#ffffff" bold>
|
|
65
|
-
{cursorChar}
|
|
66
|
-
</Text>
|
|
67
|
-
<Text color="#e5e7eb">{afterCursor}</Text>
|
|
68
|
-
{displayValue.length === 0 && (
|
|
69
|
-
<Text color="#6b7280"> type your commit message...</Text>
|
|
70
|
-
)}
|
|
71
|
-
</Text>
|
|
72
|
-
</Box>
|
|
73
|
-
|
|
74
|
-
<Box marginTop={1}>
|
|
75
|
-
<Text color="#6b7280">
|
|
76
|
-
<Text color="#10b981">Enter</Text> submit{" "}
|
|
77
|
-
<Text color="#ef4444">Esc</Text> cancel
|
|
78
|
-
</Text>
|
|
79
|
-
</Box>
|
|
80
|
-
</Box>
|
|
81
|
-
);
|
|
82
|
-
};
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import { GitFile } from "../types";
|
|
4
|
-
|
|
5
|
-
interface StagedFilesProps {
|
|
6
|
-
files: GitFile[];
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const StagedFiles: React.FC<StagedFilesProps> = ({ files }) => {
|
|
10
|
-
if (files.length === 0) {
|
|
11
|
-
return (
|
|
12
|
-
<Box
|
|
13
|
-
borderStyle="round"
|
|
14
|
-
borderColor="#ef4444"
|
|
15
|
-
paddingX={1}
|
|
16
|
-
marginBottom={1}
|
|
17
|
-
>
|
|
18
|
-
<Text color="#ef4444">No staged files found</Text>
|
|
19
|
-
</Box>
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<Box
|
|
25
|
-
flexDirection="column"
|
|
26
|
-
borderStyle="round"
|
|
27
|
-
borderColor="#374151"
|
|
28
|
-
paddingX={1}
|
|
29
|
-
marginBottom={1}
|
|
30
|
-
>
|
|
31
|
-
<Box>
|
|
32
|
-
<Text bold color="#8b5cf6">
|
|
33
|
-
Staged Files
|
|
34
|
-
</Text>
|
|
35
|
-
<Text color="#6b7280"> ({files.length})</Text>
|
|
36
|
-
</Box>
|
|
37
|
-
|
|
38
|
-
{files.slice(0, 6).map((file, index) => (
|
|
39
|
-
<Box key={index} marginLeft={1}>
|
|
40
|
-
<Text color="#10b981">•</Text>
|
|
41
|
-
<Text color="#e5e7eb"> {file.path}</Text>
|
|
42
|
-
</Box>
|
|
43
|
-
))}
|
|
44
|
-
|
|
45
|
-
{files.length > 6 && (
|
|
46
|
-
<Box marginLeft={1}>
|
|
47
|
-
<Text color="#6b7280">+{files.length - 6} more files</Text>
|
|
48
|
-
</Box>
|
|
49
|
-
)}
|
|
50
|
-
</Box>
|
|
51
|
-
);
|
|
52
|
-
};
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import { Box, Text, useInput } from "ink";
|
|
3
|
-
|
|
4
|
-
export type DialogType = "input" | "password" | "textarea" | "select";
|
|
5
|
-
|
|
6
|
-
export interface DialogProps {
|
|
7
|
-
title: string;
|
|
8
|
-
type: DialogType;
|
|
9
|
-
initialValue?: string;
|
|
10
|
-
options?: string[];
|
|
11
|
-
onSubmit: (value: string) => void;
|
|
12
|
-
onCancel: () => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const TuiDialog: React.FC<DialogProps> = ({
|
|
16
|
-
title,
|
|
17
|
-
type,
|
|
18
|
-
initialValue = "",
|
|
19
|
-
options = [],
|
|
20
|
-
onSubmit,
|
|
21
|
-
onCancel,
|
|
22
|
-
}) => {
|
|
23
|
-
const [value, setValue] = useState(initialValue);
|
|
24
|
-
const [selectedIndex, setSelectedIndex] = useState(() => {
|
|
25
|
-
if (type === "select" && initialValue) {
|
|
26
|
-
const index = options.indexOf(initialValue);
|
|
27
|
-
return index !== -1 ? index : 0;
|
|
28
|
-
}
|
|
29
|
-
return 0;
|
|
30
|
-
});
|
|
31
|
-
const [cursorPosition, setCursorPosition] = useState(initialValue.length);
|
|
32
|
-
|
|
33
|
-
useInput((input, key) => {
|
|
34
|
-
// Handle escape to cancel
|
|
35
|
-
if (key.escape) {
|
|
36
|
-
onCancel();
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (type === "select") {
|
|
41
|
-
if (key.upArrow) {
|
|
42
|
-
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : options.length - 1));
|
|
43
|
-
} else if (key.downArrow) {
|
|
44
|
-
setSelectedIndex((prev) => (prev < options.length - 1 ? prev + 1 : 0));
|
|
45
|
-
} else if (key.return) {
|
|
46
|
-
onSubmit(options[selectedIndex]);
|
|
47
|
-
}
|
|
48
|
-
} else {
|
|
49
|
-
// Input / Password / Textarea
|
|
50
|
-
if (key.tab && type === "textarea") {
|
|
51
|
-
// Tab: Submit for textarea
|
|
52
|
-
onSubmit(value.trim());
|
|
53
|
-
} else if (key.return) {
|
|
54
|
-
if (type === "textarea") {
|
|
55
|
-
// Enter: Insert newline for textarea
|
|
56
|
-
const before = value.slice(0, cursorPosition);
|
|
57
|
-
const after = value.slice(cursorPosition);
|
|
58
|
-
setValue(before + "\n" + after);
|
|
59
|
-
setCursorPosition(cursorPosition + 1);
|
|
60
|
-
} else {
|
|
61
|
-
// Enter: Submit for input/password
|
|
62
|
-
onSubmit(value.trim());
|
|
63
|
-
}
|
|
64
|
-
} else if (key.leftArrow) {
|
|
65
|
-
setCursorPosition((prev) => Math.max(0, prev - 1));
|
|
66
|
-
} else if (key.rightArrow) {
|
|
67
|
-
setCursorPosition((prev) => Math.min(value.length, prev + 1));
|
|
68
|
-
} else if (key.backspace || key.delete) {
|
|
69
|
-
if (cursorPosition > 0) {
|
|
70
|
-
const before = value.slice(0, cursorPosition - 1);
|
|
71
|
-
const after = value.slice(cursorPosition);
|
|
72
|
-
setValue(before + after);
|
|
73
|
-
setCursorPosition(cursorPosition - 1);
|
|
74
|
-
}
|
|
75
|
-
} else if (input && !key.ctrl && !key.meta) {
|
|
76
|
-
const before = value.slice(0, cursorPosition);
|
|
77
|
-
const after = value.slice(cursorPosition);
|
|
78
|
-
setValue(before + input + after);
|
|
79
|
-
setCursorPosition(cursorPosition + input.length);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
const renderInputField = () => {
|
|
85
|
-
const displayValue = type === "password" ? "•".repeat(value.length) : value;
|
|
86
|
-
const beforeCursor = displayValue.slice(0, cursorPosition);
|
|
87
|
-
const cursorChar = displayValue[cursorPosition] || " ";
|
|
88
|
-
const afterCursor = displayValue.slice(cursorPosition + 1);
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<Box borderStyle="round" borderColor="#6366f1" paddingX={1} flexGrow={1}>
|
|
92
|
-
<Text>
|
|
93
|
-
<Text color="#e5e7eb">{beforeCursor}</Text>
|
|
94
|
-
<Text backgroundColor="#8b5cf6" color="#ffffff" bold>
|
|
95
|
-
{cursorChar}
|
|
96
|
-
</Text>
|
|
97
|
-
<Text color="#e5e7eb">{afterCursor}</Text>
|
|
98
|
-
</Text>
|
|
99
|
-
</Box>
|
|
100
|
-
);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
return (
|
|
104
|
-
<Box
|
|
105
|
-
flexDirection="column"
|
|
106
|
-
flexGrow={1}
|
|
107
|
-
padding={2}
|
|
108
|
-
borderStyle="round"
|
|
109
|
-
borderColor="#6366f1"
|
|
110
|
-
>
|
|
111
|
-
{/* Header */}
|
|
112
|
-
<Box marginBottom={1}>
|
|
113
|
-
<Text bold color="#8b5cf6">
|
|
114
|
-
{title}
|
|
115
|
-
</Text>
|
|
116
|
-
</Box>
|
|
117
|
-
|
|
118
|
-
{/* Content */}
|
|
119
|
-
{type === "select" ? (
|
|
120
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
121
|
-
{options.map((option, index) => (
|
|
122
|
-
<Box key={option} paddingX={1}>
|
|
123
|
-
<Text
|
|
124
|
-
bold={index === selectedIndex}
|
|
125
|
-
inverse={index === selectedIndex}
|
|
126
|
-
>
|
|
127
|
-
{index === selectedIndex ? "› " : " "}
|
|
128
|
-
{option}
|
|
129
|
-
</Text>
|
|
130
|
-
</Box>
|
|
131
|
-
))}
|
|
132
|
-
</Box>
|
|
133
|
-
) : (
|
|
134
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
135
|
-
{renderInputField()}
|
|
136
|
-
{type === "textarea" && (
|
|
137
|
-
<Box marginTop={1}>
|
|
138
|
-
<Text color="#6b7280" dimColor>
|
|
139
|
-
Enter for new line • Tab to submit
|
|
140
|
-
</Text>
|
|
141
|
-
</Box>
|
|
142
|
-
)}
|
|
143
|
-
</Box>
|
|
144
|
-
)}
|
|
145
|
-
|
|
146
|
-
{/* Footer */}
|
|
147
|
-
<Box
|
|
148
|
-
borderTop={true}
|
|
149
|
-
borderStyle="single"
|
|
150
|
-
borderColor="#374151"
|
|
151
|
-
paddingTop={1}
|
|
152
|
-
>
|
|
153
|
-
<Text color="#6b7280">
|
|
154
|
-
{type === "select" ? (
|
|
155
|
-
<Text>
|
|
156
|
-
<Text color="#60a5fa">↑↓</Text> Navigate •{" "}
|
|
157
|
-
<Text color="#10b981">Enter</Text> Confirm •{" "}
|
|
158
|
-
<Text color="#ef4444">Esc</Text> Cancel
|
|
159
|
-
</Text>
|
|
160
|
-
) : type === "textarea" ? (
|
|
161
|
-
<Text>
|
|
162
|
-
<Text color="#60a5fa">←→</Text> Move •{" "}
|
|
163
|
-
<Text color="#10b981">Tab</Text> Submit •{" "}
|
|
164
|
-
<Text color="#ef4444">Esc</Text> Cancel
|
|
165
|
-
</Text>
|
|
166
|
-
) : (
|
|
167
|
-
<Text>
|
|
168
|
-
<Text color="#60a5fa">←→</Text> Move •{" "}
|
|
169
|
-
<Text color="#10b981">Enter</Text> Confirm •{" "}
|
|
170
|
-
<Text color="#ef4444">Esc</Text> Cancel
|
|
171
|
-
</Text>
|
|
172
|
-
)}
|
|
173
|
-
</Text>
|
|
174
|
-
</Box>
|
|
175
|
-
</Box>
|
|
176
|
-
);
|
|
177
|
-
};
|