chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { render, Box, Text } from 'ink';
|
||||
import { AskUserDialog } from '../src/ui/components/AskUserDialog.js';
|
||||
import { KeypressProvider } from '../src/ui/contexts/KeypressContext.js';
|
||||
import { QuestionType, type Question } from '@google/gemini-cli-core';
|
||||
|
||||
const DEMO_QUESTIONS: Question[] = [
|
||||
{
|
||||
question: 'What type of project are you building?',
|
||||
header: 'Project Type',
|
||||
options: [
|
||||
{ label: 'Web Application', description: 'React, Next.js, or similar' },
|
||||
{ label: 'CLI Tool', description: 'Command-line interface with Node.js' },
|
||||
{ label: 'Library', description: 'NPM package or shared utility' },
|
||||
],
|
||||
multiSelect: false,
|
||||
},
|
||||
{
|
||||
question: 'Which features should be enabled?',
|
||||
header: 'Features',
|
||||
options: [
|
||||
{ label: 'TypeScript', description: 'Add static typing' },
|
||||
{ label: 'ESLint', description: 'Add linting and formatting' },
|
||||
{ label: 'Unit Tests', description: 'Add Vitest setup' },
|
||||
{ label: 'CI/CD', description: 'Add GitHub Actions' },
|
||||
],
|
||||
multiSelect: true,
|
||||
},
|
||||
{
|
||||
question: 'What is the project name?',
|
||||
header: 'Name',
|
||||
type: QuestionType.TEXT,
|
||||
placeholder: 'my-awesome-project',
|
||||
},
|
||||
{
|
||||
question: 'Initialize git repository?',
|
||||
header: 'Git',
|
||||
type: QuestionType.YESNO,
|
||||
},
|
||||
];
|
||||
|
||||
const Demo = () => {
|
||||
const [result, setResult] = useState<null | { [key: string]: string }>(null);
|
||||
const [cancelled, setCancelled] = useState(false);
|
||||
|
||||
if (cancelled) {
|
||||
return (
|
||||
<Box padding={1}>
|
||||
<Text color="red">
|
||||
Dialog was cancelled. Project initialization aborted.
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
return (
|
||||
<Box
|
||||
flexDirection="column"
|
||||
padding={1}
|
||||
borderStyle="single"
|
||||
borderColor="green"
|
||||
>
|
||||
<Text bold color="green">
|
||||
Success! Project Configuration:
|
||||
</Text>
|
||||
{DEMO_QUESTIONS.map((q, i) => (
|
||||
<Box key={i} marginTop={1}>
|
||||
<Text color="gray">{q.header}: </Text>
|
||||
<Text>{result[i] || '(not answered)'}</Text>
|
||||
</Box>
|
||||
))}
|
||||
<Box marginTop={1}>
|
||||
<Text color="dim">Press Ctrl+C to exit</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<KeypressProvider>
|
||||
<Box padding={1} flexDirection="column">
|
||||
<Text bold marginBottom={1}>
|
||||
AskUserDialog Demo
|
||||
</Text>
|
||||
<AskUserDialog
|
||||
questions={DEMO_QUESTIONS}
|
||||
onSubmit={setResult}
|
||||
onCancel={() => setCancelled(true)}
|
||||
/>
|
||||
</Box>
|
||||
</KeypressProvider>
|
||||
);
|
||||
};
|
||||
|
||||
render(<Demo />);
|
||||
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { render, Box, Text, useInput, useStdout } from 'ink';
|
||||
import {
|
||||
ScrollableList,
|
||||
type ScrollableListRef,
|
||||
} from '../src/ui/components/shared/ScrollableList.js';
|
||||
import { ScrollProvider } from '../src/ui/contexts/ScrollProvider.js';
|
||||
import { MouseProvider } from '../src/ui/contexts/MouseContext.js';
|
||||
import { KeypressProvider } from '../src/ui/contexts/KeypressContext.js';
|
||||
import {
|
||||
enableMouseEvents,
|
||||
disableMouseEvents,
|
||||
} from '../src/ui/utils/mouse.js';
|
||||
|
||||
interface Item {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const getLorem = (index: number) =>
|
||||
Array(10)
|
||||
.fill(null)
|
||||
.map(() => 'lorem ipsum '.repeat((index % 3) + 1).trim())
|
||||
.join('\n');
|
||||
|
||||
const Demo = () => {
|
||||
const { stdout } = useStdout();
|
||||
const [size, setSize] = useState({
|
||||
columns: stdout.columns,
|
||||
rows: stdout.rows,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const onResize = () => {
|
||||
setSize({
|
||||
columns: stdout.columns,
|
||||
rows: stdout.rows,
|
||||
});
|
||||
};
|
||||
|
||||
stdout.on('resize', onResize);
|
||||
return () => {
|
||||
stdout.off('resize', onResize);
|
||||
};
|
||||
}, [stdout]);
|
||||
|
||||
const [items, setItems] = useState<Item[]>(() =>
|
||||
Array.from({ length: 1000 }, (_, i) => ({
|
||||
id: String(i),
|
||||
title: `Item ${i + 1}`,
|
||||
})),
|
||||
);
|
||||
|
||||
const listRef = useRef<ScrollableListRef<Item>>(null);
|
||||
|
||||
useInput((input, key) => {
|
||||
if (input === 'a' || input === 'A') {
|
||||
setItems((prev) => [
|
||||
...prev,
|
||||
{ id: String(prev.length), title: `Item ${prev.length + 1}` },
|
||||
]);
|
||||
}
|
||||
if ((input === 'e' || input === 'E') && !key.ctrl) {
|
||||
setItems((prev) => {
|
||||
if (prev.length === 0) return prev;
|
||||
const lastIndex = prev.length - 1;
|
||||
const lastItem = prev[lastIndex]!;
|
||||
const newItem = { ...lastItem, title: lastItem.title + 'e' };
|
||||
return [...prev.slice(0, lastIndex), newItem];
|
||||
});
|
||||
}
|
||||
if (key.ctrl && input === 'e') {
|
||||
listRef.current?.scrollToEnd();
|
||||
}
|
||||
// Let Ink handle Ctrl+C via exitOnCtrlC (default true) or handle explicitly if needed.
|
||||
// For alternate buffer, explicit handling is often safer for cleanup.
|
||||
if (key.escape || (key.ctrl && input === 'c')) {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<MouseProvider mouseEventsEnabled={true}>
|
||||
<KeypressProvider>
|
||||
<ScrollProvider>
|
||||
<Box
|
||||
flexDirection="column"
|
||||
width={size.columns}
|
||||
height={size.rows - 1}
|
||||
padding={1}
|
||||
>
|
||||
<Text>
|
||||
Press 'A' to add an item. Press 'E' to edit
|
||||
last item. Press 'Ctrl+E' to scroll to end. Press
|
||||
'Esc' to exit. Mouse wheel or Shift+Up/Down to scroll.
|
||||
</Text>
|
||||
<Box flexGrow={1} borderStyle="round" borderColor="cyan">
|
||||
<ScrollableList
|
||||
ref={listRef}
|
||||
data={items}
|
||||
renderItem={({ item, index }) => (
|
||||
<Box flexDirection="column" paddingBottom={2}>
|
||||
<Box
|
||||
sticky
|
||||
flexDirection="column"
|
||||
width={size.columns - 2}
|
||||
opaque
|
||||
stickyChildren={
|
||||
<Box
|
||||
flexDirection="column"
|
||||
width={size.columns - 2}
|
||||
opaque
|
||||
>
|
||||
<Text>{item.title}</Text>
|
||||
<Box
|
||||
borderStyle="single"
|
||||
borderTop={true}
|
||||
borderBottom={false}
|
||||
borderLeft={false}
|
||||
borderRight={false}
|
||||
borderColor="gray"
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Text>{item.title}</Text>
|
||||
</Box>
|
||||
<Text color="gray">{getLorem(index)}</Text>
|
||||
</Box>
|
||||
)}
|
||||
estimatedItemHeight={() => 14}
|
||||
keyExtractor={(item) => item.id}
|
||||
hasFocus={true}
|
||||
initialScrollIndex={Number.MAX_SAFE_INTEGER}
|
||||
initialScrollOffsetInIndex={Number.MAX_SAFE_INTEGER}
|
||||
/>
|
||||
</Box>
|
||||
<Text>Count: {items.length}</Text>
|
||||
</Box>
|
||||
</ScrollProvider>
|
||||
</KeypressProvider>
|
||||
</MouseProvider>
|
||||
);
|
||||
};
|
||||
|
||||
// Enable mouse reporting before rendering
|
||||
enableMouseEvents();
|
||||
|
||||
// Ensure cleanup happens on exit
|
||||
process.on('exit', () => {
|
||||
disableMouseEvents();
|
||||
});
|
||||
|
||||
// Handle SIGINT explicitly to ensure cleanup runs if Ink doesn't catch it in time
|
||||
process.on('SIGINT', () => {
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
render(<Demo />, { alternateBuffer: true });
|
||||
Reference in New Issue
Block a user