Skip to content

Commit

Permalink
Next major release (#1355)
Browse files Browse the repository at this point in the history
* Breaking: Remove async message feature (#1322)

* BREAKING: Require Node >=18

* [major] Introduce theming capacities (#1323)

* Chore: Refactor usePrefix to rely on a new Theme construct

* Feat: Apply theming to all prompts

* Chore: Bump dependencies

* Fix: tsconfig.json formatting inconsistency
  • Loading branch information
SBoudrias committed Feb 4, 2024
1 parent b35b6bb commit c5fb9df
Show file tree
Hide file tree
Showing 88 changed files with 752 additions and 396 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/main.yml
Expand Up @@ -30,7 +30,6 @@ jobs:
node-version:
- 20
- 18
- 16
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
Expand All @@ -51,7 +50,6 @@ jobs:
node-version:
- 20
- 18
- 16
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
Expand Down
10 changes: 9 additions & 1 deletion .prettierrc
@@ -1,5 +1,13 @@
{
"endOfLine": "auto",
"singleQuote": true,
"printWidth": 90
"printWidth": 90,
"overrides": [
{
"files": ["tsconfig.json"],
"options": {
"trailingComma": "none"
}
}
]
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
18 changes: 9 additions & 9 deletions package.json
Expand Up @@ -4,7 +4,7 @@
"author": "Simon Boudrias <admin@simonboudrias.com>",
"license": "MIT",
"engines": {
"node": ">=14.18.0"
"node": ">=18"
},
"packageManager": "yarn@3.6.1",
"keywords": [
Expand Down Expand Up @@ -47,10 +47,10 @@
"zsh"
],
"devDependencies": {
"@sindresorhus/tsconfig": "^4.0.0",
"@types/node": "^20.11.10",
"@typescript-eslint/eslint-plugin": "^6.19.1",
"@typescript-eslint/parser": "^6.19.1",
"@sindresorhus/tsconfig": "^5.0.0",
"@types/node": "^20.11.16",
"@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.20.0",
"@vitest/coverage-v8": "^1.2.2",
"@vitest/ui": "^1.2.2",
"eslint": "^8.56.0",
Expand All @@ -61,12 +61,12 @@
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^50.0.1",
"globby": "^14.0.0",
"husky": "^9.0.6",
"husky": "^9.0.10",
"lerna": "^8.0.2",
"lint-staged": "^15.2.0",
"prettier": "^3.2.4",
"lint-staged": "^15.2.1",
"prettier": "^3.2.5",
"ts-node": "^10.9.2",
"turbo": "^1.11.3",
"turbo": "^1.12.2",
"typescript": "^5.3.3",
"vitest": "^1.2.2"
},
Expand Down
30 changes: 30 additions & 0 deletions packages/checkbox/README.md
Expand Up @@ -43,9 +43,39 @@ const answer = await checkbox({
| loop | `boolean` | no | Defaults to `true`. When set to `false`, the cursor will be constrained to the top and bottom of the choice list without looping. |
| required | `boolean` | no | When set to `true`, ensures at least one choice must be selected. |
| validate | `string\[\] => boolean \| string \| Promise<string \| boolean>` | no | On submit, validate the choices. When returning a string, it'll be used as the error message displayed to the user. Note: returning a rejected promise, we'll assume a code error happened and crash. |
| theme | [See Theming](#Theming) | no | Customize look of the prompt. |

The `Separator` object can be used to render non-selectable lines in the choice list. By default it'll render a line, but you can provide the text as argument (`new Separator('-- Dependencies --')`). This option is often used to add labels to groups within long list of options.

## 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;
defaultAnswer: (text: string) => string;
help: (text: string) => string;
highlight: (text: string) => string;
key: (text: string) => string;
disabledChoice: (text: string) => string;
};
icon: {
checked: string;
unchecked: string;
cursor: string;
};
};
```

# License

Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
Expand Down
2 changes: 1 addition & 1 deletion packages/checkbox/package.json
Expand Up @@ -2,7 +2,7 @@
"name": "@inquirer/checkbox",
"version": "1.5.2",
"engines": {
"node": ">=14.18.0"
"node": ">=18"
},
"description": "Inquirer checkbox prompt",
"main": "./dist/cjs/index.js",
Expand Down
87 changes: 56 additions & 31 deletions packages/checkbox/src/index.mts
Expand Up @@ -5,19 +5,42 @@ import {
usePrefix,
usePagination,
useMemo,
makeTheme,
isUpKey,
isDownKey,
isSpaceKey,
isNumberKey,
isEnterKey,
Separator,
type PromptConfig,
type Theme,
} from '@inquirer/core';
import type {} from '@inquirer/type';
import type { PartialDeep } from '@inquirer/type';
import chalk from 'chalk';
import figures from 'figures';
import ansiEscapes from 'ansi-escapes';

type CheckboxTheme = {
icon: {
checked: string;
unchecked: string;
cursor: string;
};
style: {
disabledChoice: (text: string) => string;
};
};

const checkboxTheme: CheckboxTheme = {
icon: {
checked: chalk.green(figures.circleFilled),
unchecked: figures.circle,
cursor: figures.pointer,
},
style: {
disabledChoice: (text: string) => chalk.dim(`- ${text}`),
},
};

type Choice<Value> = {
name?: string;
value: Value;
Expand All @@ -26,7 +49,8 @@ type Choice<Value> = {
type?: never;
};

type Config<Value> = PromptConfig<{
type Config<Value> = {
message: string;
prefix?: string;
pageSize?: number;
instructions?: string | boolean;
Expand All @@ -36,7 +60,8 @@ type Config<Value> = PromptConfig<{
validate?: (
items: ReadonlyArray<Item<Value>>,
) => boolean | string | Promise<string | boolean>;
}>;
theme?: PartialDeep<Theme<CheckboxTheme>>;
};

type Item<Value> = Separator | Choice<Value>;

Expand All @@ -58,35 +83,18 @@ function check(checked: boolean) {
};
}

function renderItem<Value>({ item, isActive }: { item: Item<Value>; isActive: boolean }) {
if (Separator.isSeparator(item)) {
return ` ${item.separator}`;
}

const line = item.name || item.value;
if (item.disabled) {
const disabledLabel =
typeof item.disabled === 'string' ? item.disabled : '(disabled)';
return chalk.dim(`- ${line} ${disabledLabel}`);
}

const checkbox = item.checked ? chalk.green(figures.circleFilled) : figures.circle;
const color = isActive ? chalk.cyan : (x: string) => x;
const prefix = isActive ? figures.pointer : ' ';
return color(`${prefix}${checkbox} ${line}`);
}

export default createPrompt(
<Value extends unknown>(config: Config<Value>, done: (value: Array<Value>) => void) => {
const {
prefix = usePrefix(),
instructions,
pageSize = 7,
loop = true,
choices,
required,
validate = () => true,
} = config;
const theme = makeTheme<CheckboxTheme>(checkboxTheme, config.theme);
const prefix = usePrefix({ theme });
const [status, setStatus] = useState('pending');
const [items, setItems] = useState<ReadonlyArray<Item<Value>>>(
choices.map((choice) => ({ ...choice })),
Expand Down Expand Up @@ -157,21 +165,38 @@ export default createPrompt(
}
});

const message = chalk.bold(config.message);
const message = theme.style.message(config.message);

const page = usePagination<Item<Value>>({
items,
active,
renderItem,
renderItem({ item, isActive }: { item: Item<Value>; isActive: boolean }) {
if (Separator.isSeparator(item)) {
return ` ${item.separator}`;
}

const line = item.name || item.value;
if (item.disabled) {
const disabledLabel =
typeof item.disabled === 'string' ? item.disabled : '(disabled)';
return theme.style.disabledChoice(`${line} ${disabledLabel}`);
}

const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
const color = isActive ? theme.style.highlight : (x: string) => x;
const cursor = isActive ? theme.icon.cursor : ' ';
return color(`${cursor}${checkbox} ${line}`);
},
pageSize,
loop,
theme,
});

if (status === 'done') {
const selection = items
.filter(isChecked)
.map((choice) => choice.name || choice.value);
return `${prefix} ${message} ${chalk.cyan(selection.join(', '))}`;
return `${prefix} ${message} ${theme.style.answer(selection.join(', '))}`;
}

let helpTip = '';
Expand All @@ -180,18 +205,18 @@ export default createPrompt(
helpTip = instructions;
} else {
const keys = [
`${chalk.cyan.bold('<space>')} to select`,
`${chalk.cyan.bold('<a>')} to toggle all`,
`${chalk.cyan.bold('<i>')} to invert selection`,
`and ${chalk.cyan.bold('<enter>')} to proceed`,
`${theme.style.key('space')} to select`,
`${theme.style.key('a')} to toggle all`,
`${theme.style.key('i')} to invert selection`,
`and ${theme.style.key('enter')} to proceed`,
];
helpTip = ` (Press ${keys.join(', ')})`;
}
}

let error = '';
if (errorMsg) {
error = chalk.red(`> ${errorMsg}`);
error = theme.style.error(errorMsg);
}

return `${prefix} ${message}${helpTip}\n${page}\n${error}${ansiEscapes.cursorHide}`;
Expand Down
4 changes: 2 additions & 2 deletions packages/checkbox/tsconfig.json
Expand Up @@ -8,6 +8,6 @@
"module": "NodeNext",
"moduleResolution": "nodenext",
"outDir": "dist/esm",
"declarationDir": "dist/esm/types",
},
"declarationDir": "dist/esm/types"
}
}
30 changes: 25 additions & 5 deletions packages/confirm/README.md
Expand Up @@ -22,11 +22,31 @@ const answer = await confirm({ message: 'Continue?' });

## Options

| Property | Type | Required | Description |
| ----------- | --------------------- | -------- | ------------------------------------------------------- |
| message | `string` | yes | The question to ask |
| default | `boolean` | no | Default answer (true or false) |
| transformer | `(boolean) => string` | no | Transform the prompt printed message to a custom string |
| Property | Type | Required | Description |
| ----------- | ----------------------- | -------- | ------------------------------------------------------- |
| message | `string` | yes | The question to ask |
| default | `boolean` | no | Default answer (true or false) |
| transformer | `(boolean) => string` | no | Transform the prompt printed message to a custom string |
| theme | [See Theming](#Theming) | no | Customize look of the prompt. |

## 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;
defaultAnswer: (text: string) => string;
};
};
```

# License

Expand Down
5 changes: 2 additions & 3 deletions packages/confirm/package.json
Expand Up @@ -55,8 +55,7 @@
"homepage": "https://github.com/SBoudrias/Inquirer.js/blob/master/packages/confirm/README.md",
"dependencies": {
"@inquirer/core": "^6.0.0",
"@inquirer/type": "^1.1.6",
"chalk": "^4.1.2"
"@inquirer/type": "^1.1.6"
},
"devDependencies": {
"@inquirer/testing": "^2.1.10"
Expand All @@ -70,7 +69,7 @@
"access": "public"
},
"engines": {
"node": ">=14.18.0"
"node": ">=18"
},
"exports": {
".": {
Expand Down

0 comments on commit c5fb9df

Please sign in to comment.