ink icon indicating copy to clipboard operation
ink copied to clipboard

Multiple inputs show multiple cursors at the same time?

Open deadcoder0904 opened this issue 3 years ago • 2 comments

I am trying to make a simple 5 input CLI. Currently, it has 2 inputs. And it always shows cursors on both.

I have searched all over GitHub & it looks like some examples are creating a single component containing TextInput like renderTitle & renderDescription.

How do I solve it? Do I have to use useFocus or useFocusManager? I did try them but got nowhere. I'd appreciate an example of just seeing 2 inputs one-below-the-other or just point me to a repo.

Here's my code right now:

Input

import React from 'react'
import { Box, Text } from 'ink'
import TextInput from 'ink-text-input'

interface IInput {
	label: string
	onChange: (s: string) => string
	onSubmit: (s: string) => void
}

export const Input = ({ label, onChange, onSubmit }: IInput) => {
	const [query, setQuery] = React.useState('')
	const [submitted, setSubmitted] = React.useState(false)

	const handleChange = (q: string) => {
		setQuery(q)
		onChange(q)
	}

	const handleSubmit = (q: string) => {
		onSubmit(q)
		setSubmitted(true)
	}

	if (submitted) return null

	return (
		<Box flexDirection="column">
			<Text>
				<Text color="#6EE7B7">❯ {label}: </Text>
				<TextInput value={query} onChange={handleChange} onSubmit={handleSubmit} />
			</Text>
		</Box>
	)
}

ui.tsx

import React from 'react'
import { Box } from 'ink'
import Gradient from 'ink-gradient'
import BigText from 'ink-big-text'

import { Input } from './components/Input'

interface IApp {}

const App = ({}: IApp) => {
	const [_, setTitle] = React.useState('')
	const [__, setDescription] = React.useState('')

	return (
		<Box flexDirection="column">
			<Gradient name="fruit">
				<BigText text="Compose New Post" font="tiny" />
			</Gradient>
			<Input
				label="Title"
				onChange={(q: string) => {
					setTitle(q)
					return q
				}}
				onSubmit={(q) => {
					console.log('submit', q)
				}}
			/>
			<Input
				label="Description"
				onChange={(q: string) => {
					setDescription(q)
					return q
				}}
				onSubmit={(q) => {
					console.log('submit', q)
				}}
			/>
		</Box>
	)
}

module.exports = App
export default App

deadcoder0904 avatar Aug 03 '21 01:08 deadcoder0904

I solved it by using a boolean fieldNo state which I feel is unnecessary.

import React from 'react'
import { Box,  Text, useApp, useInput } from 'ink'
import Gradient from 'ink-gradient'
import BigText from 'ink-big-text'

const App = () => {
	const [fieldNo, setFieldNo] = React.useState(1)
	const { exit } = useApp()

	useInput((_, key) => {
		if (key.escape) {
			setTimeout(() => {
				exit()
			}, 500)
		}
	})

	return (
		<Box flexDirection="column">
			<Gradient name="fruit">
				<BigText text="Compose New Post" font="tiny" />
			</Gradient>

			<Text italic color="#708090">
				All fields are optional*
			</Text>

			<Input
				label="Title"
				placeholder={`default: "Untitled"`}
				focus={fieldNo === 1}
				onSubmit={(title: string) => {
					if (title.trim() === '') {
						state.title = 'Untitled'
					} else {
						state.title = title
					}
					setFieldNo(2)
				}}
			/>

			{fieldNo >= 2 && (
				<Input
					label="Description"
					placeholder={`default: ""`}
					focus={fieldNo === 2}
					onSubmit={(description: string) => {
						state.description = description
						setFieldNo(3)
						exit()
					}}
				/>
			)}
	)
}

Isn't there any way to make this work without having to write an unnecessary field?

deadcoder0904 avatar Aug 17 '21 12:08 deadcoder0904

I'm also wondering about this. I haven't found any examples with >1 input (text input, select, or any other prompt).

alamothe avatar Sep 04 '22 10:09 alamothe

@deadcoder0904 Your solution is a correct one, since Input component needs to know whether it's focused or not. Otherwise both of them will receive keyboard input.

vadimdemedes avatar Apr 01 '23 11:04 vadimdemedes