diff --git a/.changeset/hip-dancers-cheat.md b/.changeset/hip-dancers-cheat.md new file mode 100644 index 00000000..13aca5ea --- /dev/null +++ b/.changeset/hip-dancers-cheat.md @@ -0,0 +1,5 @@ +--- +"@clack/prompts": patch +--- + +docs: add missing jsdoc comments diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index f840c717..100b2e90 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -41,30 +41,41 @@ function getSelectedOptions(values: T[], options: Option[]): Option[] { return results; } +/** + * Options for the {@link autocomplete} component + */ interface AutocompleteSharedOptions extends CommonOptions { /** * The message to display to the user. */ message: string; + /** - * Available options for the autocomplete prompt. + * The options to present, or a function that returns the options to present + * allowing for custom search/filtering. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#dynamic-options-getter */ options: Option[] | ((this: AutocompletePrompt>) => Option[]); + /** * Maximum number of items to display at once. */ maxItems?: number; + /** - * Placeholder text to display when no input is provided. + * Placeholder text displayed when the search field is empty. When set, pressing + * tab copies the placeholder into the input. */ placeholder?: string; + /** - * Validates the value + * Allows custom validation of the value */ validate?: (value: Value | Value[] | undefined) => string | Error | undefined; + /** - * Custom filter function to match options against search input. - * If not provided, a default filter that matches label, hint, and value is used. + * Custom filter function to match options against the search input. */ filter?: (search: string, option: Option) => boolean; } @@ -74,12 +85,38 @@ export interface AutocompleteOptions extends AutocompleteSharedOptions(opts: AutocompleteOptions) => { const prompt = new AutocompletePrompt({ options: opts.options, @@ -223,20 +260,45 @@ export const autocomplete = (opts: AutocompleteOptions) => { return prompt.prompt() as Promise; }; -// Type definition for the autocompleteMultiselect component +/** + * Options for the {@link autocompleteMultiselect} component + */ export interface AutocompleteMultiSelectOptions extends AutocompleteSharedOptions { /** - * The initial selected values + * The initially selected values. */ initialValues?: Value[]; + /** - * If true, at least one option must be selected + * When `true`, at least one option must be selected. + * @default false */ required?: boolean; } /** - * Integrated autocomplete multiselect - combines type-ahead filtering with multiselect in one UI + * The `autocompleteMultiselect` combines the search functionality of autocomplete + * with the ability to select multiple options. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#autocomplete-multiselect + * + * @example + * ```ts + * import { autocompleteMultiselect } from '@clack/prompts'; + * + * const frameworks = await autocompleteMultiselect({ + * message: 'Select frameworks', + * options: [ + * { value: 'next', label: 'Next.js', hint: 'React framework' }, + * { value: 'astro', label: 'Astro', hint: 'Content-focused' }, + * { value: 'svelte', label: 'SvelteKit', hint: 'Compile-time framework' }, + * { value: 'remix', label: 'Remix', hint: 'Full stack framework' }, + * { value: 'nuxt', label: 'Nuxt', hint: 'Vue framework' }, + * ], + * placeholder: 'Type to search...', + * maxItems: 5, + * }); + * ``` */ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOptions) => { const formatOption = ( diff --git a/packages/prompts/src/box.ts b/packages/prompts/src/box.ts index e8f3b326..44f7768e 100644 --- a/packages/prompts/src/box.ts +++ b/packages/prompts/src/box.ts @@ -16,28 +16,66 @@ import { S_CORNER_TOP_RIGHT, } from './common.js'; +/** + * Alignment for content or titles within the box. + */ export type BoxAlignment = 'left' | 'center' | 'right'; -type BoxSymbols = [topLeft: string, topRight: string, bottomLeft: string, bottomRight: string]; - -const roundedSymbols: BoxSymbols = [ - S_CORNER_TOP_LEFT, - S_CORNER_TOP_RIGHT, - S_CORNER_BOTTOM_LEFT, - S_CORNER_BOTTOM_RIGHT, -]; -const squareSymbols: BoxSymbols = [S_BAR_START, S_BAR_START_RIGHT, S_BAR_END, S_BAR_END_RIGHT]; - +/** + * Options for the {@link box} component + */ export interface BoxOptions extends CommonOptions { + /** + * Alignment of the content (`'left'`, `'center'`, or `'right'`). + * @default 'left' + */ contentAlign?: BoxAlignment; + + /** + * Alignment of the title (`'left'`, `'center'`, or `'right'`). + * @default 'left' + */ titleAlign?: BoxAlignment; + + /** + * Box width. Use `'auto'` to fit content or a number for a fraction of the terminal width. + */ width?: number | 'auto'; + + /** + * Padding around the title. + * @default 1 + */ titlePadding?: number; + + /** + * Padding around the content. + * @default 2 + */ contentPadding?: number; + + /** + * Use rounded corners when `true`, square corners when `false`. + * @default true + */ rounded?: boolean; + + /** + * Custom function to style the border characters. + */ formatBorder?: (text: string) => string; } +type BoxSymbols = [topLeft: string, topRight: string, bottomLeft: string, bottomRight: string]; + +const roundedSymbols: BoxSymbols = [ + S_CORNER_TOP_LEFT, + S_CORNER_TOP_RIGHT, + S_CORNER_BOTTOM_LEFT, + S_CORNER_BOTTOM_RIGHT, +]; +const squareSymbols: BoxSymbols = [S_BAR_START, S_BAR_START_RIGHT, S_BAR_END, S_BAR_END_RIGHT]; + function getPaddingForLine( lineLength: number, innerWidth: number, @@ -59,6 +97,24 @@ function getPaddingForLine( const defaultFormatBorder = (text: string) => text; +/** + * Renders a customizable box around text content. It's similar to `note` but offers + * more styling options. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#box + * + * @example + * ```ts + * import { box } from '@clack/prompts'; + * + * box('This is the content of the box', 'Box Title', { + * contentAlign: 'center', + * titleAlign: 'center', + * width: 'auto', + * rounded: true, + * }); + * ``` + */ export const box = (message = '', title = '', opts?: BoxOptions) => { const output: Writable = opts?.output ?? process.stdout; const columns = getColumns(output); diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index cf348b38..000da4b5 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -3,42 +3,172 @@ import { styleText } from 'node:util'; import type { State } from '@clack/core'; import isUnicodeSupported from 'is-unicode-supported'; +/** + * Whether the current terminal supports unicode characters. + */ export const unicode = isUnicodeSupported(); + +/** + * Returns `true` if the process is running in a CI environment. + */ export const isCI = (): boolean => process.env.CI === 'true'; + +/** + * Returns `true` if the given output stream is a TTY. + */ export const isTTY = (output: Writable): boolean => { return (output as Writable & { isTTY?: boolean }).isTTY === true; }; + +/** + * Returns the unicode character if supported, otherwise the corresponding fallback character. + */ export const unicodeOr = (c: string, fallback: string) => (unicode ? c : fallback); + +// ── Step symbols ────────────────────────────────────────────────────────────── + +/** + * Symbol shown for a step currently in progress (active). + */ export const S_STEP_ACTIVE = unicodeOr('◆', '*'); + +/** + * Symbol shown for a step that was cancelled. + */ export const S_STEP_CANCEL = unicodeOr('■', 'x'); + +/** + * Symbol shown for a step that encountered an error. + */ export const S_STEP_ERROR = unicodeOr('▲', 'x'); + +/** + * Symbol shown for a step that was submitted successfully. + */ export const S_STEP_SUBMIT = unicodeOr('◇', 'o'); +// ── Guide bar symbols ───────────────────────────────────────────────────────── + +/** + * Top-left corner of the guide bar. + */ export const S_BAR_START = unicodeOr('┌', 'T'); + +/** + * Vertical bar used in guide lines. + */ export const S_BAR = unicodeOr('│', '|'); + +/** + * Bottom-left corner of the guide bar. + */ export const S_BAR_END = unicodeOr('└', '—'); + +/** + * Top-right corner of the guide bar. + */ export const S_BAR_START_RIGHT = unicodeOr('┐', 'T'); + +/** + * Bottom-right corner of the guide bar. + */ export const S_BAR_END_RIGHT = unicodeOr('┘', '—'); +// ── Radio / checkbox symbols ────────────────────────────────────────────────── + +/** + * Symbol for an active (focused) radio button. + */ export const S_RADIO_ACTIVE = unicodeOr('●', '>'); + +/** + * Symbol for an inactive radio button. + */ export const S_RADIO_INACTIVE = unicodeOr('○', ' '); + +/** + * Symbol for an active (focused) checkbox. + */ export const S_CHECKBOX_ACTIVE = unicodeOr('◻', '[•]'); + +/** + * Symbol for a selected checkbox. + */ export const S_CHECKBOX_SELECTED = unicodeOr('◼', '[+]'); + +/** + * Symbol for an inactive checkbox. + */ export const S_CHECKBOX_INACTIVE = unicodeOr('◻', '[ ]'); + +/** + * Mask character used in the password prompt. + */ export const S_PASSWORD_MASK = unicodeOr('▪', '•'); +// ── Box drawing symbols ─────────────────────────────────────────────────────── + +/** + * Horizontal bar used in box drawing. + */ export const S_BAR_H = unicodeOr('─', '-'); + +/** + * Top-right corner of a box. + */ export const S_CORNER_TOP_RIGHT = unicodeOr('╮', '+'); + +/** + * Left connector (T-junction) in box drawing. + */ export const S_CONNECT_LEFT = unicodeOr('├', '+'); + +/** + * Bottom-right corner of a box. + */ export const S_CORNER_BOTTOM_RIGHT = unicodeOr('╯', '+'); + +/** + * Bottom-left corner of a box. + */ export const S_CORNER_BOTTOM_LEFT = unicodeOr('╰', '+'); + +/** + * Top-left corner of a box. + */ export const S_CORNER_TOP_LEFT = unicodeOr('╭', '+'); +// ── Status icons ────────────────────────────────────────────────────────────── + +/** + * Info icon. + */ export const S_INFO = unicodeOr('●', '•'); + +/** + * Success icon. + */ export const S_SUCCESS = unicodeOr('◆', '*'); + +/** + * Warning icon. + */ export const S_WARN = unicodeOr('▲', '!'); + +/** + * Error icon. + */ export const S_ERROR = unicodeOr('■', 'x'); +// ── Symbol helpers ──────────────────────────────────────────────────────────── + +/** + * Returns a styled symbol for a given prompt state. + * + * Maps each `State` value to the appropriate step symbol styled with the + * corresponding color: `cyan` for initial/active, `red` for cancel, + * `yellow` for error, and `green` for submit. + */ export const symbol = (state: State) => { switch (state) { case 'initial': @@ -53,6 +183,13 @@ export const symbol = (state: State) => { } }; +/** + * Returns a styled vertical bar for a given prompt state. + * + * Returns the same `S_BAR` character styled with the corresponding color + * for each state: `cyan` for initial/active, `red` for cancel, + * `yellow` for error, and `green` for submit. + */ export const symbolBar = (state: State) => { switch (state) { case 'initial': @@ -67,9 +204,31 @@ export const symbolBar = (state: State) => { } }; +/** + * Common options shared by all prompts. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#common-options + */ export interface CommonOptions { + /** + * Custom readable stream for input (e.g. a file or pipe). + */ input?: Readable; + + /** + * Custom writable stream for output (e.g. a file or pipe). + */ output?: Writable; + + /** + * An `AbortSignal` for programmatic cancellation of the prompt. + */ signal?: AbortSignal; + + /** + * When `true`, renders guide lines (border bars) alongside the prompt. + * + * @default false + */ withGuide?: boolean; } diff --git a/packages/prompts/src/confirm.ts b/packages/prompts/src/confirm.ts index 75b322ca..32e5b328 100644 --- a/packages/prompts/src/confirm.ts +++ b/packages/prompts/src/confirm.ts @@ -9,13 +9,54 @@ import { symbol, } from './common.js'; +/** + * Options for the {@link confirm} component. + */ export interface ConfirmOptions extends CommonOptions { + /** + * The message to display to the user. + */ message: string; + + /** + * The label to use for the active (true) option. + * @default 'Yes' + */ active?: string; + + /** + * The label to use for the inactive (false) option. + * @default 'No' + */ inactive?: string; + + /** + * The initial selected value (true or false). + * @default true + */ initialValue?: boolean; + + /** + * Whether to render the options vertically instead of horizontally. + * @default false + */ vertical?: boolean; } + +/** + * The `confirm` prompt asks the user to confirm or decline an action with a yes/no choice. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#confirmation + * + * @example + * ```ts + * import { confirm } from '@clack/prompts'; + * + * const shouldProceed = await confirm({ + * message: 'Do you want to continue?', + * }); + * ``` + */ export const confirm = (opts: ConfirmOptions) => { const active = opts.active ?? 'Yes'; const inactive = opts.inactive ?? 'No'; diff --git a/packages/prompts/src/date.ts b/packages/prompts/src/date.ts index 5f87d197..f9cefe24 100644 --- a/packages/prompts/src/date.ts +++ b/packages/prompts/src/date.ts @@ -5,17 +5,70 @@ import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js'; export type { DateFormat }; +/** + * Options for the {@link date} prompt. + */ export interface DateOptions extends CommonOptions { + /** + * The message to display to the user. + */ message: string; + + /** + * The date format for the input segments. + */ format?: DateFormat; + + /** + * The locale to use for formatting (e.g., `'en-US'`, `'de-DE'`). + */ locale?: string; + + /** + * The default value returned when the user cancels without selecting a date. + */ defaultValue?: Date; + + /** + * The initial date value to pre-fill the input with. + */ initialValue?: Date; + + /** + * The minimum allowed date for validation. + */ minDate?: Date; + + /** + * The maximum allowed date for validation. + */ maxDate?: Date; + + /** + * Custom validation function for the selected date. + */ validate?: (value: Date | undefined) => string | Error | undefined; } +/** + * The `date` prompt provides an interactive date picker for selecting a date. + * Users can navigate between year, month, and day segments and increment or + * decrement values using keyboard controls. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#date-input + * + * @example + * ```ts + * import { date } from '@clack/prompts'; + * + * const birthday = await date({ + * message: 'Pick your birthday', + * minDate: new Date('1900-01-01'), + * maxDate: new Date(), + * initialValue: new Date(), + * }); + * ``` + */ export const date = (opts: DateOptions) => { const validate = opts.validate; return new DatePrompt({ diff --git a/packages/prompts/src/group-multi-select.ts b/packages/prompts/src/group-multi-select.ts index 99c4dbf6..c277fe3b 100644 --- a/packages/prompts/src/group-multi-select.ts +++ b/packages/prompts/src/group-multi-select.ts @@ -11,15 +11,70 @@ import { } from './common.js'; import type { Option } from './select.js'; +/** + * Options for the {@link groupMultiselect} component + */ export interface GroupMultiSelectOptions extends CommonOptions { + /** + * The message to display to the user. + */ message: string; + + /** + * Grouped options to display. Each key is a group label, and each value is an array of options. + */ options: Record[]>; + + /** + * Initial selected values. + */ initialValues?: Value[]; + + /** + * Whether the user must select at least one option. Defaults to `true`. + */ required?: boolean; + + /** + * The value to position the cursor at initially. + */ cursorAt?: Value; + + /** + * Whether groups are selectable. Defaults to `true`. + */ selectableGroups?: boolean; + + /** + * Number of blank lines between groups. Defaults to `0`. + */ groupSpacing?: number; } + +/** + * The `groupMultiselect` prompt displays grouped options for the user to select from. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#group-multiselect + * + * @example + * ```ts + * import { groupMultiselect } from '@clack/prompts'; + * + * const result = await groupMultiselect({ + * message: 'Define your project', + * options: { + * 'Testing': [ + * { value: 'Jest', hint: 'JavaScript testing framework' }, + * { value: 'Playwright', hint: 'End-to-end testing' }, + * ], + * 'Language': [ + * { value: 'js', label: 'JavaScript', hint: 'Dynamic typing' }, + * { value: 'ts', label: 'TypeScript', hint: 'Static typing' }, + * ], + * }, + * }); + * ``` + */ export const groupMultiselect = (opts: GroupMultiSelectOptions) => { const { selectableGroups = true, groupSpacing = 0 } = opts; const opt = ( diff --git a/packages/prompts/src/group.ts b/packages/prompts/src/group.ts index 25e7365c..6bcc3ec0 100644 --- a/packages/prompts/src/group.ts +++ b/packages/prompts/src/group.ts @@ -4,18 +4,30 @@ type Prettify = { [P in keyof T]: T[P]; } & {}; +/** + * The return type of a {@link PromptGroup}. + * + * Resolves all prompt results, excluding the cancel symbol. + */ export type PromptGroupAwaitedReturn = { [P in keyof T]: Exclude, symbol>; }; +/** + * Options for the {@link group} function. + */ export interface PromptGroupOptions { /** - * Control how the group can be canceled - * if one of the prompts is canceled. + * Called when one of the prompts is canceled. */ onCancel?: (opts: { results: Prettify>> }) => void; } +/** + * A group of prompts to be displayed sequentially. + * + * Each prompt receives the results of all previous prompts in the group. + */ export type PromptGroup = { [P in keyof T]: (opts: { results: Prettify>>>; @@ -23,8 +35,30 @@ export type PromptGroup = { }; /** - * Define a group of prompts to be displayed - * and return a results of objects within the group + * Define a group of prompts to be displayed and return the results as an object. + * + * Each prompt in the group receives the results of all previously completed prompts. + * Prompts are executed sequentially in the order they are defined. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#group + * + * @example + * ```ts + * import { group, text, password } from '@clack/prompts'; + * + * const account = await group({ + * email: () => text({ + * message: 'What is your email address?', + * }), + * username: ({ results }) => text({ + * message: 'What is your username?', + * placeholder: results.email?.replace(/@.+$/, '').toLowerCase() ?? '', + * }), + * password: () => password({ + * message: 'Define your password', + * }), + * }); + * ``` */ export const group = async ( prompts: PromptGroup, diff --git a/packages/prompts/src/limit-options.ts b/packages/prompts/src/limit-options.ts index f9b63d3d..f0f25718 100644 --- a/packages/prompts/src/limit-options.ts +++ b/packages/prompts/src/limit-options.ts @@ -3,12 +3,41 @@ import { getColumns, getRows } from '@clack/core'; import { wrapAnsi } from 'fast-wrap-ansi'; import type { CommonOptions } from './common.js'; +/** + * Parameters for the {@link limitOptions} function. + */ export interface LimitOptionsParams extends CommonOptions { + /** + * The list of options to display. + */ options: TOption[]; + + /** + * The index of the currently active/selected option. + */ cursor: number; + + /** + * A function that styles an option string. The `active` parameter indicates + * whether the option is currently selected. + */ style: (option: TOption, active: boolean) => string; + + /** + * Maximum number of options to display at once. Defaults to `Infinity`. + */ maxItems?: number; + + /** + * Number of columns to reserve for padding (e.g., the guide prefix `| `). + * @default 0 + */ columnPadding?: number; + + /** + * Number of rows to reserve for padding (e.g., headings and footers). + * @default 4 + */ rowPadding?: number; } @@ -32,6 +61,17 @@ const trimLines = ( return { lineCount, removals }; }; +/** + * Trims a long option list to what fits the terminal, returning the lines to render. + * Keeps the active (cursor) option visible using a sliding window approach. + * + * Intended for **custom** prompts that mirror Clack's option display behavior. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#limit-options + * + * @param params Configuration parameters for limiting and displaying options + * @returns Array of formatted lines to render in the terminal + */ export const limitOptions = ({ cursor, options, diff --git a/packages/prompts/src/log.ts b/packages/prompts/src/log.ts index 2728d385..88e309d5 100644 --- a/packages/prompts/src/log.ts +++ b/packages/prompts/src/log.ts @@ -10,13 +10,58 @@ import { S_WARN, } from './common.js'; +/** + * Options for the {@link log} utility. + */ export interface LogMessageOptions extends CommonOptions { + /** + * Custom symbol to display before the message. Overrides the default semantic symbol. + */ symbol?: string; + + /** + * Number of blank lines to prepend before the message. + */ spacing?: number; + + /** + * Custom symbol used for secondary lines (continuation lines) when `withGuide` is enabled. + */ secondarySymbol?: string; } +/** + * Utility functions for displaying semantic log messages with specific styling. + * + * Each method renders with a distinct symbol and color to communicate the status + * of an operation. Messages support multi-line text and guide characters for + * visual alignment with other log output. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#logs + * + * @example + * ```ts + * import { log } from '@clack/prompts'; + * + * log.message('Hello world'); + * log.info('Information message'); + * log.success('Operation complete'); + * log.step('Processing item 3 of 10'); + * log.warn('Disk space running low'); + * log.error('Connection refused'); + * ``` + */ export const log = { + /** + * Display a message with the given styling. Supports single strings or + * arrays (each element becomes a separate line). + * + * @example + * ```ts + * log.message('Build complete'); + * log.message(['Step 1: Done', 'Step 2: Done', 'Step 3: Done']); + * ``` + */ message: ( message: string | string[] = [], { @@ -55,22 +100,45 @@ export const log = { } output.write(`${parts.join('\n')}\n`); }, + + /** + * Display an informational message with a blue info symbol. + */ info: (message: string, opts?: LogMessageOptions) => { log.message(message, { ...opts, symbol: styleText('blue', S_INFO) }); }, + + /** + * Display a success message with a green checkmark symbol. + */ success: (message: string, opts?: LogMessageOptions) => { log.message(message, { ...opts, symbol: styleText('green', S_SUCCESS) }); }, + + /** + * Display a step completion message with a green checkmark symbol. + */ step: (message: string, opts?: LogMessageOptions) => { log.message(message, { ...opts, symbol: styleText('green', S_STEP_SUBMIT) }); }, + + /** + * Display a warning message with a yellow warning symbol. + */ warn: (message: string, opts?: LogMessageOptions) => { log.message(message, { ...opts, symbol: styleText('yellow', S_WARN) }); }, - /** alias for `log.warn()`. */ + + /** + * Alias for {@link log.warn}. + */ warning: (message: string, opts?: LogMessageOptions) => { log.warn(message, opts); }, + + /** + * Display an error message with a red error symbol. + */ error: (message: string, opts?: LogMessageOptions) => { log.message(message, { ...opts, symbol: styleText('red', S_ERROR) }); }, diff --git a/packages/prompts/src/messages.ts b/packages/prompts/src/messages.ts index 8b57df32..3d966372 100644 --- a/packages/prompts/src/messages.ts +++ b/packages/prompts/src/messages.ts @@ -3,6 +3,18 @@ import { styleText } from 'node:util'; import { settings } from '@clack/core'; import { type CommonOptions, S_BAR, S_BAR_END, S_BAR_START } from './common.js'; +/** + * Display a red cancel message with a visual prefix. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#messages + * + * @example + * ```ts + * import { cancel } from '@clack/prompts'; + * + * cancel('Installation canceled'); + * ``` + */ export const cancel = (message = '', opts?: CommonOptions) => { const output: Writable = opts?.output ?? process.stdout; const hasGuide = opts?.withGuide ?? settings.withGuide; @@ -10,6 +22,18 @@ export const cancel = (message = '', opts?: CommonOptions) => { output.write(`${prefix}${styleText('red', message)}\n\n`); }; +/** + * Display an introductory message with a visual prefix. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#messages + * + * @example + * ```ts + * import { intro } from '@clack/prompts'; + * + * intro('Welcome to clack'); + * ``` + */ export const intro = (title = '', opts?: CommonOptions) => { const output: Writable = opts?.output ?? process.stdout; const hasGuide = opts?.withGuide ?? settings.withGuide; @@ -17,6 +41,18 @@ export const intro = (title = '', opts?: CommonOptions) => { output.write(`${prefix}${title}\n`); }; +/** + * Display a closing message with a visual suffix. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#messages + * + * @example + * ```ts + * import { outro } from '@clack/prompts'; + * + * outro('All operations are finished'); + * ``` + */ export const outro = (message = '', opts?: CommonOptions) => { const output: Writable = opts?.output ?? process.stdout; const hasGuide = opts?.withGuide ?? settings.withGuide; diff --git a/packages/prompts/src/multi-line.ts b/packages/prompts/src/multi-line.ts index 4eac8d8b..83fa1cb4 100644 --- a/packages/prompts/src/multi-line.ts +++ b/packages/prompts/src/multi-line.ts @@ -3,10 +3,31 @@ import { MultiLinePrompt, settings, wrapTextWithPrefix } from '@clack/core'; import { S_BAR, S_BAR_END, symbol } from './common.js'; import type { TextOptions } from './text.js'; +/** + * Options for the {@link multiline} component + */ export interface MultiLineOptions extends TextOptions { + /** + * When `true`, shows a `[ submit ]` button that can be focused with tab. + * @default false + */ showSubmit?: boolean; } +/** + * The `multiline` prompt allows multi-line text input. + * + * @example + * ```ts + * import { multiline } from '@clack/prompts'; + * + * const bio = await multiline({ + * message: 'Enter your bio', + * placeholder: 'Tell us about yourself...', + * showSubmit: true, + * }); + * ``` + */ export const multiline = (opts: MultiLineOptions) => { return new MultiLinePrompt({ validate: opts.validate, diff --git a/packages/prompts/src/multi-select.ts b/packages/prompts/src/multi-select.ts index 28b27aab..61f3c74d 100644 --- a/packages/prompts/src/multi-select.ts +++ b/packages/prompts/src/multi-select.ts @@ -13,14 +13,41 @@ import { import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; +/** + * Options for the {@link multiselect} component. + */ export interface MultiSelectOptions extends CommonOptions { + /** + * The message to display to the user. + */ message: string; + + /** + * Array of options to select from. + */ options: Option[]; + + /** + * Initial values that are pre-selected. + */ initialValues?: Value[]; + + /** + * Maximum number of items to display at once when scrolling. + */ maxItems?: number; + + /** + * Whether at least one option must be selected. Defaults to `true`. + */ required?: boolean; + + /** + * The value to position the cursor at initially. + */ cursorAt?: Value; } + const computeLabel = (label: string, format: (text: string) => string) => { return label .split('\n') @@ -28,6 +55,24 @@ const computeLabel = (label: string, format: (text: string) => string) => { .join('\n'); }; +/** + * The `multiselect` prompt allows selecting multiple values from a list. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#multiple-values + * + * @example + * ```ts + * import { multiselect } from '@clack/prompts'; + * + * const result = await multiselect({ + * message: 'Select frameworks', + * options: [ + * { value: 'next', label: 'Next.js', hint: 'React framework' }, + * { value: 'astro', label: 'Astro', hint: 'Content-focused' }, + * ], + * }); + * ``` + */ export const multiselect = (opts: MultiSelectOptions) => { const opt = ( option: Option, diff --git a/packages/prompts/src/note.ts b/packages/prompts/src/note.ts index aa68d2b7..455a89dc 100644 --- a/packages/prompts/src/note.ts +++ b/packages/prompts/src/note.ts @@ -15,8 +15,18 @@ import { S_STEP_SUBMIT, } from './common.js'; +/** + * A function that formats a single line of the note message. + */ type FormatFn = (line: string) => string; + +/** + * Options for the {@link note} component + */ export interface NoteOptions extends CommonOptions { + /** + * Custom formatting function for each line of the message. + */ format?: FormatFn; } @@ -34,6 +44,18 @@ const wrapWithFormat = (message: string, width: number, format: FormatFn): strin return wrapAnsi(message, wrapWidth, opts); }; +/** + * The `note` prompt displays a formatted note/message in a box. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#note + * + * @example + * ```ts + * import { note } from '@clack/prompts'; + * + * note('You can edit the file src/index.jsx', 'Next steps.'); + * ``` + */ export const note = (message = '', title = '', opts?: NoteOptions) => { const output: Writable = opts?.output ?? process.stdout; const hasGuide = opts?.withGuide ?? settings.withGuide; diff --git a/packages/prompts/src/password.ts b/packages/prompts/src/password.ts index 693a1710..8faa088d 100644 --- a/packages/prompts/src/password.ts +++ b/packages/prompts/src/password.ts @@ -2,12 +2,47 @@ import { styleText } from 'node:util'; import { PasswordPrompt, settings } from '@clack/core'; import { type CommonOptions, S_BAR, S_BAR_END, S_PASSWORD_MASK, symbol } from './common.js'; +/** + * Options for the {@link password} component + */ export interface PasswordOptions extends CommonOptions { + /** + * The message to display to the user. + */ message: string; + + /** + * Character to use for masking input. + * @default '•' + */ mask?: string; + + /** + * Custom validation function. Returns an error message if validation fails. + */ validate?: (value: string | undefined) => string | Error | undefined; + + /** + * When `true`, clears the input when validation fails. + * @default false + */ clearOnError?: boolean; } + +/** + * The `password` prompt collects a masked password from the user. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#password-input + * + * @example + * ```ts + * import { password } from '@clack/prompts'; + * + * const result = await password({ + * message: 'Enter your password', + * }); + * ``` + */ export const password = (opts: PasswordOptions) => { return new PasswordPrompt({ validate: opts.validate, diff --git a/packages/prompts/src/path.ts b/packages/prompts/src/path.ts index 70b8618e..07d05f3b 100644 --- a/packages/prompts/src/path.ts +++ b/packages/prompts/src/path.ts @@ -3,14 +3,54 @@ import { dirname, join } from 'node:path'; import { autocomplete } from './autocomplete.js'; import type { CommonOptions } from './common.js'; +/** + * Options for the {@link path} component. + */ export interface PathOptions extends CommonOptions { + /** + * Root directory for path selection. + */ root?: string; + + /** + * If `true`, only directories will be shown in the suggestions. + */ directory?: boolean; + + /** + * Initial value for the input field. Falls back to `root` or the current working directory. + */ initialValue?: string; + + /** + * The message to display to the user. + */ message: string; + + /** + * A validation function to check the selected path. + * Receives the current input value and should return `undefined` for valid input, + * or a string/error message for invalid input. + */ validate?: (value: string | undefined) => string | Error | undefined; } +/** + * The `path` prompt allows selecting a file or directory path from the filesystem. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#path-selection + * + * @example + * ```ts + * import { path } from '@clack/prompts'; + * + * const result = await path({ + * message: 'Select a file:', + * root: process.cwd(), + * directory: false, + * }); + * ``` + */ export const path = (opts: PathOptions) => { const validate = opts.validate; diff --git a/packages/prompts/src/progress-bar.ts b/packages/prompts/src/progress-bar.ts index 90079a10..0c610819 100644 --- a/packages/prompts/src/progress-bar.ts +++ b/packages/prompts/src/progress-bar.ts @@ -9,16 +9,58 @@ const S_PROGRESS_CHAR: Record, string> = { block: unicodeOr('█', '#'), }; +/** + * Options for the {@link progress} component. + */ export interface ProgressOptions extends SpinnerOptions { + /** + * Visual style of the progress bar characters. + * @default 'heavy' + */ style?: 'light' | 'heavy' | 'block'; + + /** + * Maximum value for the progress bar (total steps). + * @default 100 + */ max?: number; + + /** + * Display width of the progress bar in characters. + * @default 40 + */ size?: number; } +/** + * Result object returned by the {@link progress} component. + */ export interface ProgressResult extends SpinnerResult { + /** + * Advance the progress bar by a number of steps. + * @param step - Number of steps to advance (default: 1) + * @param msg - Optional message to display alongside the progress bar + */ advance(step?: number, msg?: string): void; } +/** + * The `progress` component displays an animated progress bar. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#progress + * + * @example + * ```ts + * import { progress } from '@clack/prompts'; + * + * const p = progress(); + * p.start('Uploading files'); + * p.advance(25); + * p.advance(25); + * p.advance(50); + * p.stop('Upload complete'); + * ``` + */ export function progress({ style = 'heavy', max: userMax = 100, diff --git a/packages/prompts/src/select-key.ts b/packages/prompts/src/select-key.ts index cceef81e..c4c56926 100644 --- a/packages/prompts/src/select-key.ts +++ b/packages/prompts/src/select-key.ts @@ -3,13 +3,55 @@ import { SelectKeyPrompt, settings, wrapTextWithPrefix } from '@clack/core'; import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js'; import type { Option } from './select.js'; +/** + * Options for the {@link selectKey} component + */ export interface SelectKeyOptions extends CommonOptions { + /** + * The message to display to the user. + */ message: string; + + /** + * Array of options to present. Each option has a `value` (the key to press), `label`, and optional `hint`. + */ options: Option[]; + + /** + * The initial selected option value. + */ initialValue?: Value; + + /** + * Whether key matching is case-sensitive. Defaults to `false`. + */ caseSensitive?: boolean; } +/** + * The `selectKey` prompt allows selecting an option by pressing a key. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#select-by-key + * + * @example + * ```ts + * import { selectKey, isCancel } from '@clack/prompts'; + * + * const action = await selectKey({ + * message: 'What next?', + * options: [ + * { value: 'y', label: 'Continue' }, + * { value: 'n', label: 'Stop' }, + * { value: 's', label: 'Skip', hint: 'optional' }, + * ], + * caseSensitive: false, + * }); + * + * if (isCancel(action)) { + * process.exit(0); + * } + * ``` + */ export const selectKey = (opts: SelectKeyOptions) => { const opt = ( option: Option, diff --git a/packages/prompts/src/select.ts b/packages/prompts/src/select.ts index a3407cdb..e2420b7c 100644 --- a/packages/prompts/src/select.ts +++ b/packages/prompts/src/select.ts @@ -19,12 +19,14 @@ export type Option = Value extends Primitive * Internal data for this option. */ value: Value; + /** * The optional, user-facing text for this option. * * By default, the `value` is converted to a string. */ label?: string; + /** * An optional hint to display to the user when * this option might be selected. @@ -32,6 +34,7 @@ export type Option = Value extends Primitive * By default, no `hint` is displayed. */ hint?: string; + /** * Whether this option is disabled. * Disabled options are visible but cannot be selected. @@ -45,10 +48,12 @@ export type Option = Value extends Primitive * Internal data for this option. */ value: Value; + /** * Required. The user-facing text for this option. */ label: string; + /** * An optional hint to display to the user when * this option might be selected. @@ -56,6 +61,7 @@ export type Option = Value extends Primitive * By default, no `hint` is displayed. */ hint?: string; + /** * Whether this option is disabled. * Disabled options are visible but cannot be selected. @@ -65,10 +71,28 @@ export type Option = Value extends Primitive disabled?: boolean; }; +/** + * Options for the {@link select} component + */ export interface SelectOptions extends CommonOptions { + /** + * The message to display to the user. + */ message: string; + + /** + * The options to present for the user to choose from. + */ options: Option[]; + + /** + * The initial selected value. + */ initialValue?: Value; + + /** + * Maximum number of items to display at once. + */ maxItems?: number; } @@ -82,6 +106,24 @@ const computeLabel = (label: string, format: (text: string) => string) => { .join('\n'); }; +/** + * The `select` prompt displays a list of options for the user to choose from. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#selection + * + * @example + * ```ts + * import { select } from '@clack/prompts'; + * + * const result = await select({ + * message: 'Select a framework', + * options: [ + * { value: 'next', label: 'Next.js', hint: 'React framework' }, + * { value: 'astro', label: 'Astro', hint: 'Content-focused' }, + * ], + * }); + * ``` + */ export const select = (opts: SelectOptions) => { const opt = ( option: Option, diff --git a/packages/prompts/src/spinner.ts b/packages/prompts/src/spinner.ts index 9b427e3a..6e45e0e0 100644 --- a/packages/prompts/src/spinner.ts +++ b/packages/prompts/src/spinner.ts @@ -12,28 +12,109 @@ import { unicode, } from './common.js'; +/** + * Options for the {@link spinner} component + */ export interface SpinnerOptions extends CommonOptions { + /** + * The type of indicator to display. `'dots'` shows an animated loading + * dot sequence, `'timer'` shows elapsed time. + * + * @default 'dots' + */ indicator?: 'dots' | 'timer'; + + /** + * Callback invoked when the spinner is cancelled (e.g. user presses Ctrl+C). + */ onCancel?: () => void; + + /** + * Message displayed when the spinner is cancelled. + */ cancelMessage?: string; + + /** + * Message displayed when the spinner encounters an error. + */ errorMessage?: string; + + /** + * Custom animation frames for the spinner indicator. + * @default ['◒', '◐', '◓', '◑'] (unicode) or ['•', 'o', 'O', '0'] (non-unicode) + */ frames?: string[]; + + /** + * Delay between frame updates in milliseconds. + * @default 80 (unicode) or 120 (non-unicode) + */ delay?: number; + + /** + * Custom function to style each spinner frame. + */ styleFrame?: (frame: string) => string; } +/** + * The result object returned by the {@link spinner} function. + */ export interface SpinnerResult { + /** + * Start the spinner with an optional message. + */ start(msg?: string): void; + + /** + * Stop the spinner and display a success message with a green checkmark. + */ stop(msg?: string): void; + + /** + * Stop the spinner and display a cancellation message with a red square. + */ cancel(msg?: string): void; + + /** + * Stop the spinner and display an error message with a yellow triangle. + */ error(msg?: string): void; + + /** + * Update the spinner message while it is running. + */ message(msg?: string): void; + + /** + * Clear the spinner without displaying any message. + */ clear(): void; + + /** + * Whether the spinner was cancelled (e.g. user pressed Ctrl+C). + */ readonly isCancelled: boolean; } const defaultStyleFn: SpinnerOptions['styleFrame'] = (frame) => styleText('magenta', frame); +/** + * The `spinner` component displays an animated loading indicator for + * long-running operations. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#spinner + * + * @example + * ```ts + * import { spinner } from '@clack/prompts'; + * + * const s = spinner(); + * s.start('Loading data'); + * // ... do work ... + * s.stop('Data loaded'); + * ``` + */ export const spinner = ({ indicator = 'dots', onCancel, diff --git a/packages/prompts/src/stream.ts b/packages/prompts/src/stream.ts index 4ff3db5e..a8f5cf11 100644 --- a/packages/prompts/src/stream.ts +++ b/packages/prompts/src/stream.ts @@ -9,7 +9,25 @@ const prefix = `${styleText('gray', S_BAR)} `; // // If we want to support `output` being passed in, we will need to use // a condition like `if (output insance Writable)` to check if it has columns + +/** + * Stream output from async iterables to the console with a visual prefix. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#stream + * + * @example + * ```ts + * import { stream } from '@clack/prompts'; + * + * await stream.message(['line 1', 'line 2', 'line 3']); + * await stream.info(['Information line']); + * await stream.success(['Success line']); + * ``` + */ export const stream = { + /** + * Stream message with a gray bar prefix. + */ message: async ( iterable: Iterable | AsyncIterable, { symbol = styleText('gray', S_BAR) }: LogMessageOptions = {} @@ -32,22 +50,45 @@ export const stream = { } process.stdout.write('\n'); }, + + /** + * Stream info message with a blue info symbol. + */ info: (iterable: Iterable | AsyncIterable) => { return stream.message(iterable, { symbol: styleText('blue', S_INFO) }); }, + + /** + * Stream success message with a green check mark. + */ success: (iterable: Iterable | AsyncIterable) => { return stream.message(iterable, { symbol: styleText('green', S_SUCCESS) }); }, + + /** + * Stream step message with a green check mark. + */ step: (iterable: Iterable | AsyncIterable) => { return stream.message(iterable, { symbol: styleText('green', S_STEP_SUBMIT) }); }, + + /** + * Stream warning message with a yellow warning symbol. + */ warn: (iterable: Iterable | AsyncIterable) => { return stream.message(iterable, { symbol: styleText('yellow', S_WARN) }); }, - /** alias for `log.warn()`. */ + + /** + * Alias for {@link stream.warn}. + */ warning: (iterable: Iterable | AsyncIterable) => { return stream.warn(iterable); }, + + /** + * Stream error message with a red error symbol. + */ error: (iterable: Iterable | AsyncIterable) => { return stream.message(iterable, { symbol: styleText('red', S_ERROR) }); }, diff --git a/packages/prompts/src/task-log.ts b/packages/prompts/src/task-log.ts index e69f2206..d094791e 100644 --- a/packages/prompts/src/task-log.ts +++ b/packages/prompts/src/task-log.ts @@ -11,18 +11,55 @@ import { } from './common.js'; import { log } from './log.js'; +/** + * Options for the {@link taskLog} component + */ export interface TaskLogOptions extends CommonOptions { + /** + * The title displayed for the task log. + */ title: string; + + /** + * Maximum number of log lines to display at once. + * Older lines are trimmed but can be retained via `retainLog`. + */ limit?: number; + + /** + * Number of empty lines (spacing) between the title and log output. + * @default 1 + */ spacing?: number; + + /** + * Whether to retain the full log history even when `limit` is set. + * Retained logs are shown when `showLog: true` on completion. + * @default false + */ retainLog?: boolean; } +/** + * Options for individual messages passed to {@link TaskLogResult.message | taskLog().message()}. + */ export interface TaskLogMessageOptions { + /** + * When `true`, the message is written as-is without additional formatting or line handling. + * Useful for raw output that should not be processed. + */ raw?: boolean; } +/** + * Options for task completion passed to {@link TaskLogResult.success | taskLog().success()} + * or {@link TaskLogResult.error | taskLog().error()}. + */ export interface TaskLogCompletionOptions { + /** + * Whether to show the retained log history on completion. + * Only has an effect when `retainLog: true` is set in {@link TaskLogOptions}. + */ showLog?: boolean; } diff --git a/packages/prompts/src/task.ts b/packages/prompts/src/task.ts index 9017ba25..aa7b8be9 100644 --- a/packages/prompts/src/task.ts +++ b/packages/prompts/src/task.ts @@ -1,24 +1,54 @@ import type { CommonOptions } from './common.js'; import { spinner } from './spinner.js'; +/** + * A single task to be executed by the {@link tasks} function. + */ export type Task = { /** - * Task title + * Task title displayed as the spinner message. */ title: string; + /** - * Task function + * Task function to execute. Receives a message updater function. */ - task: (message: (string: string) => void) => string | Promise | void | Promise; + task: (message: (msg: string) => void) => string | Promise | void | Promise; /** - * If enabled === false the task will be skipped + * If set to false, the task will be skipped. + * @default true */ enabled?: boolean; }; /** - * Define a group of tasks to be executed + * The `tasks` function executes a series of tasks sequentially, + * displaying a spinner for each one. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#tasks + * + * @example + * ```ts + * import { tasks } from '@clack/prompts'; + * + * await tasks([ + * { + * title: 'Installing dependencies', + * task: (message) => { + * message('Running npm install...'); + * // ... install logic ... + * }, + * }, + * { + * title: 'Building project', + * task: async (message) => { + * message('Compiling...'); + * await build(); + * }, + * }, + * ]); + * ``` */ export const tasks = async (tasks: Task[], opts?: CommonOptions) => { for (const task of tasks) { diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index 250fd8fd..34ff5034 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -2,14 +2,58 @@ import { styleText } from 'node:util'; import { settings, TextPrompt } from '@clack/core'; import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js'; +/** + * Options for the {@link text} component + */ export interface TextOptions extends CommonOptions { + /** + * The message to display to the user. + */ message: string; + + /** + * A placeholder string displayed when the input is empty. + * The first character is shown in inverse video, the rest in dim text. + */ placeholder?: string; + + /** + * A default value pre-filled in the input field. + */ defaultValue?: string; + + /** + * An initial value to set in the input field. + * Unlike `defaultValue`, this is the actual initial state (not just a fallback). + */ initialValue?: string; + + /** + * A validation function that receives the current input value. + * Return a string or Error to display as a validation error, or `undefined` to accept the value. + */ validate?: (value: string | undefined) => string | Error | undefined; } +/** + * The `text` prompt displays a single-line text input for collecting string values from the user. + * + * @see https://bomb.sh/docs/clack/packages/prompts/#text-input + * + * @example + * ```ts + * import { text } from '@clack/prompts'; + * + * const name = await text({ + * message: 'What is your name?', + * placeholder: 'John Doe', + * validate: (value) => { + * if (!value || value.length < 2) return 'Name must be at least 2 characters'; + * return undefined; + * }, + * }); + * ``` + */ export const text = (opts: TextOptions) => { return new TextPrompt({ validate: opts.validate,