Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor new expand prompt with the hooks
- Loading branch information
Showing
2 changed files
with
138 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# `@inquirer/expand` | ||
|
||
Compact single select prompt. Every option is assigned a shortcut key, and selecting `h` will expand all the choices and their descriptions. | ||
|
||
# Installation | ||
|
||
```sh | ||
npm install @inquirer/expand | ||
|
||
yarn add @inquirer/expand | ||
``` | ||
|
||
# Usage | ||
|
||
```js | ||
import expand from '@inquirer/expand'; | ||
|
||
const answer = await expand({ | ||
message: 'Conflict on file.js', | ||
default: 'y', | ||
choices: [ | ||
{ | ||
key: 'y', | ||
name: 'Overwrite', | ||
value: 'overwrite' | ||
}, | ||
{ | ||
key: 'a', | ||
name: 'Overwrite this one and all next', | ||
value: 'overwrite_all' | ||
}, | ||
{ | ||
key: 'd', | ||
name: 'Show diff', | ||
value: 'diff' | ||
}, | ||
{ | ||
key: 'x', | ||
name: 'Abort', | ||
value: 'abort' | ||
} | ||
] | ||
}); | ||
``` | ||
|
||
## Options | ||
|
||
| Property | Type | Required | Description | | ||
| -------- | ------------------------------------------------------ | -------- | ----------------------------------------------------------------------------------------- | | ||
| message | `string` | yes | The question to ask | | ||
| choices | `Array<{ key: string, name: string, value?: string }>` | yes | Array of the different allowed choices. The `h`/help option is always provided by default | | ||
| default | `string` | no | Default choices to be selected. (value must be one of the choices `key`) | | ||
| expanded | `boolean` | no | Expand the choices by default | | ||
|
||
# License | ||
|
||
Copyright (c) 2019 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart)) | ||
Licensed under the MIT license. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,97 +1,99 @@ | ||
const { createPrompt } = require('@inquirer/core'); | ||
const { createPrompt, useState, useKeypress } = require('@inquirer/core/hooks'); | ||
const { usePrefix } = require('@inquirer/core/lib/prefix'); | ||
const { isEnterKey } = require('@inquirer/core/lib/key'); | ||
const chalk = require('chalk'); | ||
|
||
module.exports = createPrompt( | ||
{ | ||
onLine(state, { submit, setState }) { | ||
// Handle expanding the prompt only if: | ||
// 1. Prompt isn't expanded | ||
// 2. If user do not provide a default (in such case we select the help as default) | ||
if ( | ||
!state.expanded && | ||
(state.value.toLowerCase() === 'h' || (state.value === '' && !state.default)) | ||
) { | ||
setState({ expanded: true }); | ||
} else { | ||
submit(); | ||
} | ||
}, | ||
mapStateToValue({ value = '', choices, default: defaultOption }) { | ||
let selection; | ||
if (value) { | ||
selection = choices.find(choice => choice.key === value.toLowerCase()); | ||
} else if (defaultOption) { | ||
selection = choices.find(choice => choice.key === defaultOption.toLowerCase()); | ||
} | ||
const helpChoice = { | ||
key: 'h', | ||
name: 'Help, list all options', | ||
value: undefined | ||
}; | ||
|
||
if (!selection) { | ||
return undefined; | ||
} | ||
|
||
return selection.value || selection.name; | ||
}, | ||
validate(selection, { value = '', choices }) { | ||
// If we matched an option, no need to run validation. | ||
if (selection) { | ||
return true; | ||
} | ||
module.exports = createPrompt((config, done) => { | ||
const { | ||
choices, | ||
default: defaultKey = 'h', | ||
expanded: defaultExpandState = false | ||
} = config; | ||
const [status, setStatus] = useState('pending'); | ||
const [value, setValue] = useState(''); | ||
const [expanded, setExpanded] = useState(defaultExpandState); | ||
const [errorMsg, setError] = useState(); | ||
const prefix = usePrefix(); | ||
|
||
if (value === '') { | ||
return 'Please input a value'; | ||
useKeypress((key, rl) => { | ||
if (isEnterKey(key)) { | ||
const answer = (value || defaultKey).toLowerCase(); | ||
if (answer === 'h' && !expanded) { | ||
setExpanded(true); | ||
} else { | ||
const selectedChoice = choices.find(({ key }) => key === answer); | ||
if (selectedChoice) { | ||
const finalValue = selectedChoice.value || selectedChoice.name; | ||
setValue(finalValue); | ||
setStatus('done'); | ||
done(finalValue); | ||
} else if (value === '') { | ||
setError('Please input a value'); | ||
} else { | ||
setError(`"${chalk.red(value)}" isn't an available option`); | ||
} | ||
} | ||
|
||
return ( | ||
Boolean(choices.find(({ key }) => key === value.toLowerCase())) || | ||
`"${chalk.red(value)}" isn't an available option` | ||
); | ||
} else { | ||
setValue(rl.line); | ||
setError(undefined); | ||
} | ||
}, | ||
(state, { mapStateToValue }) => { | ||
const { prefix, choices, value = '', expanded = false, default: rawDefault } = state; | ||
const message = chalk.bold(state.message); | ||
}); | ||
|
||
if (state.status === 'done') { | ||
const selection = mapStateToValue(state); | ||
return `${prefix} ${message} ${chalk.cyan(selection)}`; | ||
} | ||
const message = chalk.bold(config.message); | ||
|
||
let choicesDisplay; | ||
if (status === 'done') { | ||
return `${prefix} ${message} ${chalk.cyan(value)}`; | ||
} | ||
|
||
// Expanded display style | ||
if (expanded) { | ||
choicesDisplay = choices.map(choice => { | ||
const line = `\n ${choice.key}) ${choice.name || choice.value}`; | ||
if (choice.key === value.toLowerCase()) { | ||
return chalk.cyan(line); | ||
} | ||
const allChoices = expanded ? choices : [...choices, helpChoice]; | ||
|
||
return line; | ||
}); | ||
// Collapsed display style | ||
let longChoices = ''; | ||
let shortChoices = allChoices | ||
.map(choice => { | ||
if (choice.key === defaultKey) { | ||
return choice.key.toUpperCase(); | ||
} | ||
|
||
return `${prefix} ${message} ${value} ${choicesDisplay}`; | ||
} | ||
return choice.key; | ||
}) | ||
.join(''); | ||
shortChoices = chalk.dim(` (${shortChoices})`); | ||
|
||
// Collapsed display style | ||
choicesDisplay = choices | ||
// Expanded display style | ||
if (expanded) { | ||
shortChoices = ''; | ||
longChoices = allChoices | ||
.map(choice => { | ||
if (choice.key === rawDefault) { | ||
return choice.key.toUpperCase(); | ||
const line = ` ${choice.key}) ${choice.name || choice.value}`; | ||
if (choice.key === value.toLowerCase()) { | ||
return chalk.cyan(line); | ||
} | ||
|
||
return choice.key; | ||
return line; | ||
}) | ||
.join(''); | ||
choicesDisplay += rawDefault ? 'h' : 'H'; | ||
choicesDisplay = chalk.dim(`(${choicesDisplay})`); | ||
.join('\n'); | ||
} | ||
|
||
let helpTip = ''; | ||
const currentOption = choices.find(({ key }) => key === value.toLowerCase()); | ||
if (currentOption) { | ||
helpTip = `\n${chalk.cyan('>>')} ${currentOption.name || currentOption.value}`; | ||
} else if (value.toLowerCase() === 'h') { | ||
helpTip = `\n${chalk.cyan('>>')} Help, list all options`; | ||
} | ||
let helpTip = ''; | ||
const currentOption = allChoices.find(({ key }) => key === value.toLowerCase()); | ||
if (currentOption) { | ||
helpTip = `${chalk.cyan('>>')} ${currentOption.name || currentOption.value}`; | ||
} | ||
|
||
return `${prefix} ${message} ${choicesDisplay} ${value}${helpTip}`; | ||
let error = ''; | ||
if (errorMsg) { | ||
error = chalk.red(`> ${errorMsg}`); | ||
} | ||
); | ||
|
||
return [ | ||
`${prefix} ${message}${shortChoices} ${value}`, | ||
[longChoices, helpTip, error].filter(Boolean).join('\n') | ||
]; | ||
}); |