Skip to content

Commit

Permalink
Feat(@inquirer/search) Introduce new search prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
SBoudrias committed Jul 21, 2024
1 parent 2cf6284 commit 5d40787
Show file tree
Hide file tree
Showing 15 changed files with 871 additions and 5 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
.yarn
*.gz
*.zip
*.png
coverage
yarn.lock
Binary file added assets/screenshots/search.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions packages/demo/demos/search.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as url from 'node:url';
import { search } from '@inquirer/prompts';

const demo = async () => {
let answer;

answer = await search({
message: 'Select an npm package',
source: async (input = 'inquirer', { signal }) => {
// eslint-disable-next-line n/no-unsupported-features/node-builtins
const response = await fetch(
`https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(input)}&size=20`,
{ signal },
);
const data = await response.json();

return data.objects.map((pkg) => ({
name: pkg.package.name,
value: pkg.package.name,
description: pkg.package.description,
}));
},
});
console.log('Answer:', answer);
};

if (import.meta.url.startsWith('file:')) {
const modulePath = url.fileURLToPath(import.meta.url);
if (process.argv[1] === modulePath) {
demo();
}
}

export default demo;
3 changes: 3 additions & 0 deletions packages/demo/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import loaderDemo from './demos/loader.mjs';
import numberDemo from './demos/number.mjs';
import passwordDemo from './demos/password.mjs';
import rawlistDemo from './demos/rawlist.mjs';
import searchDemo from './demos/search.mjs';
import selectDemo from './demos/select.mjs';
import timeoutDemo from './demos/timeout.mjs';

Expand All @@ -23,6 +24,7 @@ const demos = {
number: numberDemo,
password: passwordDemo,
rawlist: rawlistDemo,
search: searchDemo,
select: selectDemo,
timeout: timeoutDemo,
};
Expand All @@ -36,6 +38,7 @@ async function askNextDemo() {
{ name: 'Confirm', value: 'confirm' },
{ name: 'Select', value: 'select' },
{ name: 'Checkbox', value: 'checkbox' },
{ name: 'Search', value: 'search' },
{ name: 'Expand', value: 'expand' },
{ name: 'Rawlist', value: 'rawlist' },
{ name: 'Editor', value: 'editor' },
Expand Down
10 changes: 10 additions & 0 deletions packages/prompts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ import { confirm } from '@inquirer/prompts';

[See documentation](https://github.com/SBoudrias/Inquirer.js/tree/main/packages/confirm) for usage example and options documentation.

## [Search](https://github.com/SBoudrias/Inquirer.js/tree/main/packages/search)

![search prompt](https://raw.githubusercontent.com/SBoudrias/Inquirer.js/f459199e679aec7676cecc0fc12ef8a4cd3dda0b/assets/screenshots/search.png)

```js
import { search } from '@inquirer/prompts';
```

[See documentation](https://github.com/SBoudrias/Inquirer.js/tree/main/packages/search) for usage example and options documentation.

## [Password](https://github.com/SBoudrias/Inquirer.js/tree/main/packages/password)

![Password prompt](https://cdn.rawgit.com/SBoudrias/Inquirer.js/28ae8337ba51d93e359ef4f7ee24e79b69898962/assets/screenshots/password.svg)
Expand Down
1 change: 1 addition & 0 deletions packages/prompts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"@inquirer/number": "^1.0.4",
"@inquirer/password": "^2.1.16",
"@inquirer/rawlist": "^2.1.16",
"@inquirer/search": "^0.0.0",
"@inquirer/select": "^2.4.1"
},
"devDependencies": {
Expand Down
16 changes: 12 additions & 4 deletions packages/prompts/prompts.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
input,
password,
rawlist,
search,
select,
Separator,
} from './src/index.mjs';
Expand All @@ -20,13 +21,20 @@ describe('@inquirer/prompts', () => {
expect(input).toBeTypeOf('function');
expect(password).toBeTypeOf('function');
expect(rawlist).toBeTypeOf('function');
expect(search).toBeTypeOf('function');
expect(select).toBeTypeOf('function');
expect(Separator).toBeTypeOf('function');
});

it('checkbox and select have matching helpMode', () => {
expectTypeOf<
NonNullable<Parameters<typeof checkbox>[0]['theme']>['helpMode']
>().toEqualTypeOf<NonNullable<Parameters<typeof select>[0]['theme']>['helpMode']>();
it('checkbox, search and select have matching helpMode', () => {
type CheckboxHelpMode = NonNullable<
Parameters<typeof checkbox>[0]['theme']
>['helpMode'];
type SearchHelpMode = NonNullable<Parameters<typeof search>[0]['theme']>['helpMode'];
type SelectHelpMode = NonNullable<Parameters<typeof select>[0]['theme']>['helpMode'];

expectTypeOf<CheckboxHelpMode>().toEqualTypeOf<SelectHelpMode>();
expectTypeOf<SelectHelpMode>().toEqualTypeOf<SearchHelpMode>();
expectTypeOf<SearchHelpMode>().toEqualTypeOf<CheckboxHelpMode>();
});
});
2 changes: 1 addition & 1 deletion packages/prompts/src/index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ export { default as number } from '@inquirer/number';
export { default as expand } from '@inquirer/expand';
export { default as rawlist } from '@inquirer/rawlist';
export { default as password } from '@inquirer/password';

export { default as search } from '@inquirer/search';
export { default as select } from '@inquirer/select';
145 changes: 145 additions & 0 deletions packages/search/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# `@inquirer/search`

Interactive search prompt component for command line interfaces.

![search prompt](https://raw.githubusercontent.com/SBoudrias/Inquirer.js/f459199e679aec7676cecc0fc12ef8a4cd3dda0b/assets/screenshots/search.png)

# Installation

<table>
<tr>
<th>npm</th>
<th>yarn</th>
</tr>
<tr>
<td>

```sh
npm install @inquirer/search
```

</td>
<td>

```sh
yarn add @inquirer/search
```

</td>
</tr>
</table>

# Usage

```js
import search, { Separator } from '@inquirer/search';

const answer = await search({
message: 'Select an npm package',
source: async (input, { signal }) => {
if (!input) {
return [];
}

const response = await fetch(
`https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(input)}&size=20`,
{ signal },
);
const data = await response.json();

return data.objects.map((pkg) => ({
name: pkg.package.name,
value: pkg.package.name,
description: pkg.package.description,
}));
},
});
```

## Options

| Property | Type | Required | Description |
| -------- | --------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| message | `string` | yes | The question to ask |
| source | `(term: string \| void) => Promise<Choice[]>` | yes | This function returns the choices relevant to the search term. |
| pageSize | `number` | no | By default, lists of choice longer than 7 will be paginated. Use this option to control how many choices will appear on the screen at once. |
| theme | [See Theming](#Theming) | no | Customize look of the prompt. |

### `source` function

The full signature type of `source` is as follow:

```ts
function(
term: string | void,
opt: { signal: AbortSignal },
): Promise<ReadonlyArray<Choice<Value> | Separator>>;
```

When `term` is `undefined`, it means the search term input is empty. You can use this to return default choices, or return an empty array.

Aside from returning the choices:

1. An `AbortSignal` is passed in to cancel ongoing network calls when the search term change.
2. `Separator`s can be used to organize the list.

### `Choice` object

The `Choice` object is typed as

```ts
type Choice<Value> = {
value: Value;
name?: string;
description?: string;
short?: string;
disabled?: boolean | string;
};
```

Here's each property:

- `value`: The value is what will be returned by `await search()`.
- `name`: This is the string displayed in the choice list.
- `description`: Option for a longer description string that'll appear under the list when the cursor highlight a given choice.
- `short`: Once the prompt is done (press enter), we'll use `short` if defined to render next to the question. By default we'll use `name`.
- `disabled`: Disallow the option from being selected. If `disabled` is a string, it'll be used as a help tip explaining why the choice isn't available.

## Theming

You can theme a prompt by passing a `theme` object option. The theme object only need to includes the keys you wish to modify, we'll fallback on the defaults for the rest.

```ts
type Theme = {
prefix: string;
spinner: {
interval: number;
frames: string[];
};
style: {
answer: (text: string) => string;
message: (text: string) => string;
error: (text: string) => string;
help: (text: string) => string;
highlight: (text: string) => string;
description: (text: string) => string;
disabled: (text: string) => string;
searchTerm: (text: string) => string;
};
icon: {
cursor: string;
};
helpMode: 'always' | 'never' | 'auto';
};
```

### `theme.helpMode`

- `auto` (default): Hide the help tips after an interaction occurs.
- `always`: The help tips will always show and never hide.
- `never`: The help tips will never show.

# License

Copyright (c) 2024 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
Licensed under the MIT license.
90 changes: 90 additions & 0 deletions packages/search/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{
"name": "@inquirer/search",
"version": "0.0.0",
"description": "Inquirer search prompt",
"main": "./dist/cjs/index.js",
"typings": "./dist/cjs/types/index.d.ts",
"files": [
"dist/**/*"
],
"repository": {
"type": "git",
"url": "https://github.com/SBoudrias/Inquirer.js.git"
},
"keywords": [
"answer",
"answers",
"ask",
"base",
"cli",
"command",
"command-line",
"confirm",
"enquirer",
"generate",
"generator",
"hyper",
"input",
"inquire",
"inquirer",
"interface",
"iterm",
"javascript",
"menu",
"node",
"nodejs",
"prompt",
"promptly",
"prompts",
"question",
"readline",
"scaffold",
"scaffolder",
"scaffolding",
"stdin",
"stdout",
"terminal",
"tty",
"ui",
"yeoman",
"yo",
"zsh"
],
"author": "Simon Boudrias <admin@simonboudrias.com>",
"license": "MIT",
"homepage": "https://github.com/SBoudrias/Inquirer.js/blob/main/packages/search/README.md",
"dependencies": {
"@inquirer/core": "^9.0.4",
"@inquirer/figures": "^1.0.4",
"@inquirer/type": "^1.5.0",
"yoctocolors-cjs": "^2.1.2"
},
"devDependencies": {
"@inquirer/testing": "^2.1.27"
},
"scripts": {
"tsc": "yarn run tsc:esm && yarn run tsc:cjs",
"tsc:esm": "rm -rf dist/esm && tsc -p ./tsconfig.json",
"tsc:cjs": "rm -rf dist/cjs && tsc -p ./tsconfig.cjs.json && node ../../tools/fix-ext.mjs",
"attw": "attw --pack"
},
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=18"
},
"exports": {
".": {
"import": {
"types": "./dist/esm/types/index.d.mts",
"default": "./dist/esm/index.mjs"
},
"require": {
"types": "./dist/cjs/types/index.d.ts",
"default": "./dist/cjs/index.js"
}
}
},
"sideEffects": false
}
Loading

0 comments on commit 5d40787

Please sign in to comment.