Initial Commit

This commit is contained in:
2026-01-29 07:13:33 +00:00
commit 399e93f714
34 changed files with 7663 additions and 0 deletions

View File

@@ -0,0 +1,243 @@
/**
* Dialog components for modals, confirmations, and input dialogs.
*/
import React, { useState } from 'react';
import { Box, Text, useInput } from 'ink';
import TextInput from 'ink-text-input';
import { colors } from '../theme.js';
/**
* Base dialog wrapper props.
*/
interface DialogWrapperProps {
/** Dialog title */
title: string;
/** Border color */
borderColor?: string;
/** Dialog content */
children: React.ReactNode;
/** Dialog width */
width?: number;
}
/**
* Base dialog wrapper component.
*/
function DialogWrapper({
title,
borderColor = colors.primary,
children,
width = 60,
}: DialogWrapperProps): React.ReactElement {
return (
<Box
flexDirection="column"
borderStyle="double"
borderColor={borderColor}
paddingX={2}
paddingY={1}
width={width}
>
<Text color={borderColor} bold>{title}</Text>
<Box marginY={1} flexDirection="column">
{children}
</Box>
</Box>
);
}
/**
* Props for InputDialog component.
*/
interface InputDialogProps {
/** Dialog title */
title: string;
/** Input prompt/label */
prompt: string;
/** Initial value */
initialValue?: string;
/** Placeholder text */
placeholder?: string;
/** Submit handler */
onSubmit: (value: string) => void;
/** Cancel handler */
onCancel: () => void;
/** Whether dialog is visible/active */
isActive?: boolean;
}
/**
* Input dialog for getting text input from user.
*/
export function InputDialog({
title,
prompt,
initialValue = '',
placeholder,
onSubmit,
onCancel,
isActive = true,
}: InputDialogProps): React.ReactElement {
const [value, setValue] = useState(initialValue);
useInput((input, key) => {
if (!isActive) return;
if (key.escape) {
onCancel();
}
}, { isActive });
const handleSubmit = (val: string) => {
onSubmit(val);
};
return (
<DialogWrapper title={title} borderColor={colors.primary}>
<Text>{prompt}</Text>
<Box marginTop={1} borderStyle="single" borderColor={colors.focus} paddingX={1}>
<TextInput
value={value}
onChange={setValue}
onSubmit={handleSubmit}
placeholder={placeholder}
focus={isActive}
/>
</Box>
<Box marginTop={1}>
<Text color={colors.textMuted}>Enter to submit Esc to cancel</Text>
</Box>
</DialogWrapper>
);
}
/**
* Props for ConfirmDialog component.
*/
interface ConfirmDialogProps {
/** Dialog title */
title: string;
/** Confirmation message */
message: string;
/** Confirm handler */
onConfirm: () => void;
/** Cancel handler */
onCancel: () => void;
/** Whether dialog is visible/active */
isActive?: boolean;
/** Confirm button label */
confirmLabel?: string;
/** Cancel button label */
cancelLabel?: string;
}
/**
* Confirmation dialog with Yes/No options.
*/
export function ConfirmDialog({
title,
message,
onConfirm,
onCancel,
isActive = true,
confirmLabel = 'Yes',
cancelLabel = 'No',
}: ConfirmDialogProps): React.ReactElement {
const [selected, setSelected] = useState<'confirm' | 'cancel'>('confirm');
useInput((input, key) => {
if (!isActive) return;
if (key.leftArrow || key.rightArrow || key.tab) {
setSelected(prev => prev === 'confirm' ? 'cancel' : 'confirm');
} else if (key.return) {
if (selected === 'confirm') {
onConfirm();
} else {
onCancel();
}
} else if (key.escape || input === 'n' || input === 'N') {
onCancel();
} else if (input === 'y' || input === 'Y') {
onConfirm();
}
}, { isActive });
return (
<DialogWrapper title={title} borderColor={colors.warning}>
<Text wrap="wrap">{message}</Text>
<Box marginTop={1} gap={2}>
<Text
backgroundColor={selected === 'confirm' ? colors.focus : colors.secondary}
color={selected === 'confirm' ? colors.bg : colors.text}
bold={selected === 'confirm'}
>
{` ${confirmLabel} `}
</Text>
<Text
backgroundColor={selected === 'cancel' ? colors.focus : colors.secondary}
color={selected === 'cancel' ? colors.bg : colors.text}
bold={selected === 'cancel'}
>
{` ${cancelLabel} `}
</Text>
</Box>
<Box marginTop={1}>
<Text color={colors.textMuted}>Y/N or Tab to switch Enter to select</Text>
</Box>
</DialogWrapper>
);
}
/**
* Props for MessageDialog component.
*/
interface MessageDialogProps {
/** Dialog title */
title: string;
/** Message content */
message: string;
/** Close handler */
onClose: () => void;
/** Dialog type for styling */
type?: 'info' | 'error' | 'success';
/** Whether dialog is visible/active */
isActive?: boolean;
}
/**
* Simple message dialog (info, error, success).
*/
export function MessageDialog({
title,
message,
onClose,
type = 'info',
isActive = true,
}: MessageDialogProps): React.ReactElement {
useInput((input, key) => {
if (!isActive) return;
if (key.return || key.escape) {
onClose();
}
}, { isActive });
const borderColor = type === 'error' ? colors.error :
type === 'success' ? colors.success :
colors.info;
const icon = type === 'error' ? '✗' :
type === 'success' ? '✓' :
'';
return (
<DialogWrapper title={`${icon} ${title}`} borderColor={borderColor}>
<Text wrap="wrap">{message}</Text>
<Box marginTop={1}>
<Text color={colors.textMuted}>Press Enter or Esc to close</Text>
</Box>
</DialogWrapper>
);
}