import React , { useState , useEffect , useRef , KeyboardEvent , ChangeEvent } from 'react' ; import './style.css' ; export enum LineType { Input , Output } export enum ColorMode { Light , Dark } export interface Props { name ? : string ; prompt ? : string ; colorMode ? : ColorMode ; lineData : Array < { type : LineType ; value : string ; } > ; onInput ? : ( ( input : string ) => void ) | null | undefined ; startingInputValue ? : string ; } const Terminal = ( { name , prompt , colorMode , lineData , onInput , startingInputValue = "" } ) => { const [ currentLineInput , setCurrentLineInput ] = useState ( '' ) ; const lastLineRef = useRef < null | HTMLElement > ( null ) ; const updateCurrentLineInput = ( event ) => { setCurrentLineInput ( event . target . value ) ; } ; const handleEnter = ( event ) => { if ( onInput != null && event . key === 'Enter' ) { onInput ( currentLineInput ) ; setCurrentLineInput ( '' ) ; } } ; useEffect ( ( ) => { setCurrentLineInput ( startingInputValue . trim ( ) ) ; } , [ startingInputValue ] ) ; const performScrolldown = useRef ( false ) ; useEffect ( ( ) => { if ( performScrolldown . current ) { setTimeout ( ( ) => lastLineRef ?. current ?. scrollIntoView ( { behavior : "smooth" , block : "nearest" } ) , 500 ) ; } performScrolldown . current = true ; } , [ lineData . length ] ) ; useEffect ( ( ) => { if ( onInput == null ) { return ; } const elListeners = [ ] ; for ( const terminalEl of document . getElementsByClassName ( 'react-terminal-wrapper' ) ) { const listener = ( ) => ( terminalEl ?. querySelector ( '.terminal-hidden-input' ) as HTMLElement ) ?. focus ( ) ; terminalEl ?. addEventListener ( 'click' , listener ) ; elListeners . push ( { terminalEl , listener } ) ; } return function cleanup ( ) { elListeners . forEach ( elListener => { elListener . terminalEl . removeEventListener ( 'click' , elListener . listener ) ; } ) ; } ; } , [ onInput ] ) ; const renderedLineData = lineData . map ( ( ld , i ) => { const classes = [ 'react-terminal-line' ] ; if ( ld . type === LineType . Input ) { classes . push ( 'react-terminal-input' ) ; } if ( lineData . length === i + 1 && onInput == null ) { return ( < span className = { classes . join ( ' ' ) } key = { i } ref = { lastLineRef } > { ld . value } < /span>); } else { return ( < span className = { classes . join ( ' ' ) } key = { i } > { ld . value } < /span>); } } ) ; if ( onInput != null ) { renderedLineData . push ( < span className = "react-terminal-line react-terminal-input react-terminal-active-input" data - terminal - prompt = { prompt || '$' } key = { lineData . length } ref = { lastLineRef } > { currentLineInput } < /span>); } const classes = [ 'react-terminal-wrapper' ] ; if ( colorMode === ColorMode . Light ) { classes . push ( 'react-terminal-light' ) ; } return ( < div className = { classes . join ( ' ' ) } data - terminal - name = { name } > < div className = "react-terminal" > { renderedLineData } < /div> < input className = "terminal-hidden-input" placeholder = "Terminal Hidden Input" value = { currentLineInput } autoFocus = { onInput != null } onChange = { updateCurrentLineInput } onKeyDown = { handleEnter } / > < /div>); } ; export default Terminal ;