import React, { useState, useCallback, useEffect, useRef } from 'react'; import { Box, Text, useInput } from 'ink'; import { Header } from '../parts/Header.js'; import { Footer } from '../parts/Footer.js'; import { Select } from '../common/Select.js'; import { Input } from '../common/Input.js'; import { useTerminalSize } from '../../hooks/useTerminalSize.js'; import { BRANCH_PREFIXES } from '../../../config/constants.js'; type BranchType = 'feature' | 'hotfix' | 'release'; type Step = 'type-selection' | 'name-input'; const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧']; export interface BranchCreatorScreenProps { onBack: () => void; onCreate: (branchName: string) => Promise; baseBranch?: string; version?: string | null; disableAnimation?: boolean; } interface BranchTypeItem { label: string; value: BranchType; description: string; } /** * BranchCreatorScreen - Screen for creating new branches * Layout: Header + Type Selection or Name Input + Footer * Flow: Type Selection → Name Input → onCreate */ export function BranchCreatorScreen({ onBack, onCreate, baseBranch, version, disableAnimation = false, }: BranchCreatorScreenProps) { const { rows } = useTerminalSize(); const [step, setStep] = useState('type-selection'); const [selectedType, setSelectedType] = useState('feature'); const [branchName, setBranchName] = useState(''); const [isCreating, setIsCreating] = useState(false); const [pendingBranchName, setPendingBranchName] = useState(null); const spinnerIndexRef = useRef(0); const [spinnerIndex, setSpinnerIndex] = useState(0); const spinnerFrame = SPINNER_FRAMES[spinnerIndex] ?? SPINNER_FRAMES[0]; // Handle keyboard input for back navigation useInput((input, key) => { if (isCreating) { return; } if (key.escape) { onBack(); } }); // Branch type options const branchTypeItems: BranchTypeItem[] = [ { label: 'feature', value: 'feature', description: 'New feature development', }, { label: 'hotfix', value: 'hotfix', description: 'Critical bug fix', }, { label: 'release', value: 'release', description: 'Release preparation', }, ]; // Handle branch type selection const handleTypeSelect = useCallback((item: BranchTypeItem) => { setSelectedType(item.value); setStep('name-input'); }, []); // Handle branch name input const handleNameChange = useCallback((value: string) => { if (isCreating) { return; } setBranchName(value); }, [isCreating]); // Handle branch creation const handleCreate = useCallback(async () => { if (isCreating) { return; } const trimmedName = branchName.trim(); if (!trimmedName) { return; } const prefix = BRANCH_PREFIXES[selectedType.toUpperCase() as keyof typeof BRANCH_PREFIXES]; const fullBranchName = `${prefix}${trimmedName}`; setIsCreating(true); setPendingBranchName(fullBranchName); try { await onCreate(fullBranchName); } catch (error) { setPendingBranchName(null); setIsCreating(false); throw error; } }, [branchName, selectedType, onCreate, isCreating]); // Footer actions const footerActions = isCreating ? [] : step === 'type-selection' ? [ { key: 'enter', description: 'Select' }, { key: 'esc', description: 'Back' }, ] : [ { key: 'enter', description: 'Create' }, { key: 'esc', description: 'Back' }, ]; useEffect(() => { if (!isCreating || disableAnimation) { spinnerIndexRef.current = 0; setSpinnerIndex(0); return undefined; } const interval = setInterval(() => { spinnerIndexRef.current = (spinnerIndexRef.current + 1) % SPINNER_FRAMES.length; setSpinnerIndex(spinnerIndexRef.current); }, 120); return () => { clearInterval(interval); spinnerIndexRef.current = 0; setSpinnerIndex(0); }; }, [isCreating, disableAnimation]); return ( {/* Header */}
{/* Content */} {baseBranch && ( Base branch: {baseBranch} )} {isCreating ? ( {spinnerFrame}{' '} Creating branch{' '} {pendingBranchName ?? `${BRANCH_PREFIXES[selectedType.toUpperCase() as keyof typeof BRANCH_PREFIXES]}${branchName.trim()}`} Please wait while the branch is being created... ) : step === 'type-selection' ? ( Select branch type: )} {/* Footer */}