Skip to content

Commit

Permalink
Refactor new expand prompt with the hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
SBoudrias committed Sep 21, 2019
1 parent 7cf9d99 commit d76c929
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 78 deletions.
58 changes: 58 additions & 0 deletions packages/expand/README.md
@@ -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.
158 changes: 80 additions & 78 deletions packages/expand/index.js
@@ -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')
];
});

0 comments on commit d76c929

Please sign in to comment.