|
1 | 1 | import { State } from "@clack/core"; |
2 | 2 | import { TextPrompt, SelectPrompt, ConfirmPrompt, block } from "@clack/core"; |
3 | | -import color from 'picocolors'; |
4 | | -import { cursor, erase } from 'sisteransi'; |
| 3 | +import color from "picocolors"; |
| 4 | +import { cursor, erase } from "sisteransi"; |
5 | 5 |
|
6 | 6 | export { isCancel } from "@clack/core"; |
7 | 7 |
|
8 | 8 | const symbol = (state: State) => { |
9 | | - switch (state) { |
10 | | - case 'initial': |
11 | | - case 'active': return color.cyan('●') |
12 | | - case 'cancel': return color.red('■') |
13 | | - case 'error': return color.yellow('▲') |
14 | | - case 'submit': return color.green('✔') |
15 | | - } |
16 | | -} |
| 9 | + switch (state) { |
| 10 | + case "initial": |
| 11 | + case "active": |
| 12 | + return color.cyan("●"); |
| 13 | + case "cancel": |
| 14 | + return color.red("■"); |
| 15 | + case "error": |
| 16 | + return color.yellow("▲"); |
| 17 | + case "submit": |
| 18 | + return color.green("✔"); |
| 19 | + } |
| 20 | +}; |
17 | 21 |
|
18 | | -const barStart = '┌'; |
19 | | -const bar = '│'; |
20 | | -const barEnd = '└'; |
| 22 | +const barStart = "┌"; |
| 23 | +const bar = "│"; |
| 24 | +const barEnd = "└"; |
21 | 25 |
|
22 | 26 | export interface TextOptions { |
23 | | - message: string; |
24 | | - placeholder?: string; |
25 | | - validate?: ((value: string) => string | void); |
| 27 | + message: string; |
| 28 | + placeholder?: string; |
| 29 | + validate?: (value: string) => string | void; |
26 | 30 | } |
27 | 31 | export const text = (opts: TextOptions) => { |
28 | | - return new TextPrompt({ |
29 | | - validate: opts.validate, |
30 | | - render() { |
31 | | - const title = `${color.gray(bar)}\n${symbol(this.state)} ${opts.message}\n`; |
32 | | - const placeholder = opts.placeholder ? color.inverse(opts.placeholder[0]) + color.dim(opts.placeholder.slice(1)) : color.inverse(color.hidden('_')); |
33 | | - const value = !this.value ? placeholder : this.valueWithCursor; |
| 32 | + return new TextPrompt({ |
| 33 | + validate: opts.validate, |
| 34 | + render() { |
| 35 | + const title = `${color.gray(bar)}\n${symbol(this.state)} ${ |
| 36 | + opts.message |
| 37 | + }\n`; |
| 38 | + const placeholder = opts.placeholder |
| 39 | + ? color.inverse(opts.placeholder[0]) + |
| 40 | + color.dim(opts.placeholder.slice(1)) |
| 41 | + : color.inverse(color.hidden("_")); |
| 42 | + const value = !this.value ? placeholder : this.valueWithCursor; |
34 | 43 |
|
35 | | - switch (this.state) { |
36 | | - case 'error': return `${title.trim()}\n${color.yellow(bar)} ${value}\n${color.yellow(barEnd)} ${color.yellow(this.error)}\n`; |
37 | | - case 'submit': return `${title}${color.gray(bar)} ${color.dim(this.value)}`; |
38 | | - case 'cancel': return `${title}${color.gray(bar)} ${color.strikethrough(color.dim(this.value))}${this.value.trim() ? '\n' + color.gray(bar) : ''}`; |
39 | | - default: return `${title}${color.cyan(bar)} ${value}\n${color.cyan(barEnd)}\n`; |
40 | | - } |
41 | | - } |
42 | | - }).prompt(); |
43 | | -} |
| 44 | + switch (this.state) { |
| 45 | + case "error": |
| 46 | + return `${title.trim()}\n${color.yellow( |
| 47 | + bar |
| 48 | + )} ${value}\n${color.yellow(barEnd)} ${color.yellow(this.error)}\n`; |
| 49 | + case "submit": |
| 50 | + return `${title}${color.gray(bar)} ${color.dim(this.value)}`; |
| 51 | + case "cancel": |
| 52 | + return `${title}${color.gray(bar)} ${color.strikethrough( |
| 53 | + color.dim(this.value) |
| 54 | + )}${this.value.trim() ? "\n" + color.gray(bar) : ""}`; |
| 55 | + default: |
| 56 | + return `${title}${color.cyan(bar)} ${value}\n${color.cyan( |
| 57 | + barEnd |
| 58 | + )}\n`; |
| 59 | + } |
| 60 | + }, |
| 61 | + }).prompt(); |
| 62 | +}; |
44 | 63 |
|
45 | 64 | export interface ConfirmOptions { |
46 | | - message: string; |
47 | | - active?: string; |
48 | | - inactive?: string; |
49 | | - initialValue?: boolean; |
| 65 | + message: string; |
| 66 | + active?: string; |
| 67 | + inactive?: string; |
| 68 | + initialValue?: boolean; |
50 | 69 | } |
51 | 70 | export const confirm = (opts: ConfirmOptions) => { |
52 | | - const active = opts.active ?? 'Yes'; |
53 | | - const inactive = opts.inactive ?? 'No'; |
54 | | - return new ConfirmPrompt({ |
55 | | - active, |
56 | | - inactive, |
57 | | - initialValue: opts.initialValue ?? true, |
58 | | - render() { |
59 | | - const title = `${color.gray(bar)}\n${symbol(this.state)} ${opts.message}\n`; |
60 | | - const value = this.value ? active : inactive; |
| 71 | + const active = opts.active ?? "Yes"; |
| 72 | + const inactive = opts.inactive ?? "No"; |
| 73 | + return new ConfirmPrompt({ |
| 74 | + active, |
| 75 | + inactive, |
| 76 | + initialValue: opts.initialValue ?? true, |
| 77 | + render() { |
| 78 | + const title = `${color.gray(bar)}\n${symbol(this.state)} ${ |
| 79 | + opts.message |
| 80 | + }\n`; |
| 81 | + const value = this.value ? active : inactive; |
61 | 82 |
|
62 | | - switch (this.state) { |
63 | | - case 'submit': return `${title}${color.gray(bar)} ${color.dim(value)}`; |
64 | | - case 'cancel': return `${title}${color.gray(bar)} ${color.strikethrough(color.dim(value))}\n${color.gray(bar)}`; |
65 | | - default: { |
66 | | - return `${title}${color.cyan(bar)} ${this.value ? `${color.green('◼')} ${active}` : `${color.dim('◻')} ${color.dim(active)}`} ${color.dim('/')} ${!this.value ? `${color.green('◼')} ${inactive}` : `${color.dim('◻')} ${color.dim(inactive)}`}\n${color.cyan(barEnd)}\n`; |
67 | | - } |
68 | | - } |
| 83 | + switch (this.state) { |
| 84 | + case "submit": |
| 85 | + return `${title}${color.gray(bar)} ${color.dim(value)}`; |
| 86 | + case "cancel": |
| 87 | + return `${title}${color.gray(bar)} ${color.strikethrough( |
| 88 | + color.dim(value) |
| 89 | + )}\n${color.gray(bar)}`; |
| 90 | + default: { |
| 91 | + return `${title}${color.cyan(bar)} ${ |
| 92 | + this.value |
| 93 | + ? `${color.green("◼")} ${active}` |
| 94 | + : `${color.dim("◻")} ${color.dim(active)}` |
| 95 | + } ${color.dim("/")} ${ |
| 96 | + !this.value |
| 97 | + ? `${color.green("◼")} ${inactive}` |
| 98 | + : `${color.dim("◻")} ${color.dim(inactive)}` |
| 99 | + }\n${color.cyan(barEnd)}\n`; |
69 | 100 | } |
70 | | - }).prompt(); |
71 | | -} |
| 101 | + } |
| 102 | + }, |
| 103 | + }).prompt(); |
| 104 | +}; |
72 | 105 |
|
73 | 106 | interface Option { |
74 | | - value: any; |
75 | | - label?: string; |
76 | | - hint?: string; |
| 107 | + value: any; |
| 108 | + label?: string; |
| 109 | + hint?: string; |
77 | 110 | } |
78 | 111 | export interface SelectOptions<Options extends Option[]> { |
79 | | - message: string; |
80 | | - options: Options; |
81 | | - initialValue?: Options[number]['value']; |
| 112 | + message: string; |
| 113 | + options: Options; |
| 114 | + initialValue?: Options[number]["value"]; |
82 | 115 | } |
83 | | -export const select = <Options extends Option[]>(opts: SelectOptions<Options>) => { |
84 | | - const opt = (option: Options[number], state: 'inactive' | 'active' | 'selected' | 'cancelled') => { |
85 | | - const label = option.label ?? option.value; |
86 | | - if (state === 'active') { |
87 | | - return `${color.green('◼')} ${label} ${option.hint ? color.dim(`(${option.hint})`) : ''}` |
88 | | - } else if (state === 'selected') { |
89 | | - return `${color.dim(label)}` |
90 | | - } else if (state === 'cancelled') { |
91 | | - return `${color.strikethrough(color.dim(label))}` |
92 | | - } |
93 | | - return `${color.dim('◻')} ${color.dim(label)}`; |
| 116 | +export const select = <Options extends Option[]>( |
| 117 | + opts: SelectOptions<Options> |
| 118 | +) => { |
| 119 | + const opt = ( |
| 120 | + option: Options[number], |
| 121 | + state: "inactive" | "active" | "selected" | "cancelled" |
| 122 | + ) => { |
| 123 | + const label = option.label ?? option.value; |
| 124 | + if (state === "active") { |
| 125 | + return `${color.green("◼")} ${label} ${ |
| 126 | + option.hint ? color.dim(`(${option.hint})`) : "" |
| 127 | + }`; |
| 128 | + } else if (state === "selected") { |
| 129 | + return `${color.dim(label)}`; |
| 130 | + } else if (state === "cancelled") { |
| 131 | + return `${color.strikethrough(color.dim(label))}`; |
94 | 132 | } |
| 133 | + return `${color.dim("◻")} ${color.dim(label)}`; |
| 134 | + }; |
95 | 135 |
|
96 | | - return new SelectPrompt({ |
97 | | - options: opts.options, |
98 | | - initialValue: opts.initialValue, |
99 | | - render() { |
100 | | - const title = `${color.gray(bar)}\n${symbol(this.state)} ${opts.message}\n`; |
| 136 | + return new SelectPrompt({ |
| 137 | + options: opts.options, |
| 138 | + initialValue: opts.initialValue, |
| 139 | + render() { |
| 140 | + const title = `${color.gray(bar)}\n${symbol(this.state)} ${ |
| 141 | + opts.message |
| 142 | + }\n`; |
101 | 143 |
|
102 | | - switch (this.state) { |
103 | | - case 'submit': return `${title}${color.gray(bar)} ${opt(this.options[this.cursor], 'selected')}`; |
104 | | - case 'cancel': return `${title}${color.gray(bar)} ${opt(this.options[this.cursor], 'cancelled')}\n${color.gray(bar)}`; |
105 | | - default: { |
106 | | - return `${title}${color.cyan(bar)} ${this.options.map((option, i) => opt(option, i === this.cursor ? 'active' : 'inactive')).join(`\n${color.cyan(bar)} `)}\n${color.cyan(barEnd)}\n`; |
107 | | - } |
108 | | - } |
| 144 | + switch (this.state) { |
| 145 | + case "submit": |
| 146 | + return `${title}${color.gray(bar)} ${opt( |
| 147 | + this.options[this.cursor], |
| 148 | + "selected" |
| 149 | + )}`; |
| 150 | + case "cancel": |
| 151 | + return `${title}${color.gray(bar)} ${opt( |
| 152 | + this.options[this.cursor], |
| 153 | + "cancelled" |
| 154 | + )}\n${color.gray(bar)}`; |
| 155 | + default: { |
| 156 | + return `${title}${color.cyan(bar)} ${this.options |
| 157 | + .map((option, i) => |
| 158 | + opt(option, i === this.cursor ? "active" : "inactive") |
| 159 | + ) |
| 160 | + .join(`\n${color.cyan(bar)} `)}\n${color.cyan(barEnd)}\n`; |
109 | 161 | } |
110 | | - }).prompt(); |
111 | | -} |
| 162 | + } |
| 163 | + }, |
| 164 | + }).prompt(); |
| 165 | +}; |
112 | 166 |
|
113 | | -export const cancel = (message = '') => { |
114 | | - process.stdout.write(`${color.gray(barEnd)} ${color.red(message)}\n\n`); |
115 | | -} |
| 167 | +export const cancel = (message = "") => { |
| 168 | + process.stdout.write(`${color.gray(barEnd)} ${color.red(message)}\n\n`); |
| 169 | +}; |
116 | 170 |
|
117 | | -export const intro = (title = '') => { |
118 | | - process.stdout.write(`${color.gray(barStart)} ${title}\n`); |
119 | | -} |
| 171 | +export const intro = (title = "") => { |
| 172 | + process.stdout.write(`${color.gray(barStart)} ${title}\n`); |
| 173 | +}; |
120 | 174 |
|
121 | | -export const outro = (message = '') => { |
122 | | - process.stdout.write(`${color.gray(bar)}\n${color.gray(barEnd)} ${color.green(message)}\n\n`); |
123 | | -} |
| 175 | +export const outro = (message = "") => { |
| 176 | + process.stdout.write( |
| 177 | + `${color.gray(bar)}\n${color.gray(barEnd)} ${color.green(message)}\n\n` |
| 178 | + ); |
| 179 | +}; |
124 | 180 |
|
125 | | -export const spinner = () => { |
126 | | - let unblock: () => void; |
127 | | - let loop: NodeJS.Timer; |
128 | | - return { |
129 | | - start(message = '') { |
130 | | - message = message.replace(/\.\.\.$/, ''); |
131 | | - unblock = block(); |
132 | | - process.stdout.write(`${color.gray(bar)}\n${color.magenta('◆')} ${message}\n`); |
133 | | - let i = 0; |
134 | | - loop = setInterval(() => { |
135 | | - process.stdout.write(cursor.move(-999, -2)); |
136 | | - process.stdout.write(erase.down(2)) |
137 | | - process.stdout.write(`${color.gray(bar)}\n${color.magenta('◆')} ${message}${i ? '.'.repeat(i) : ''}\n`); |
138 | | - i = i > 2 ? 0 : i + 1; |
139 | | - }, 300) |
140 | | - }, |
141 | | - stop(message = '') { |
142 | | - process.stdout.write(cursor.move(-999, -2)); |
143 | | - process.stdout.write(erase.down(2)) |
144 | | - clearInterval(loop); |
145 | | - process.stdout.write(`${color.gray(bar)}\n${color.gray('◆')} ${message}\n`); |
146 | | - unblock(); |
147 | | - } |
148 | | - } |
| 181 | +const arc = [ |
| 182 | + '◒', '◒', '◐', '◐', '◓', '◓', '◑', '◑' |
| 183 | +] |
149 | 184 |
|
150 | | -} |
| 185 | +export const spinner = () => { |
| 186 | + let unblock: () => void; |
| 187 | + let loop: NodeJS.Timer; |
| 188 | + const frames = arc; |
| 189 | + const delay = 120; |
| 190 | + return { |
| 191 | + start(message = "") { |
| 192 | + message = message.replace(/\.?\.?\.$/, ""); |
| 193 | + unblock = block(); |
| 194 | + process.stdout.write( |
| 195 | + `${color.gray(bar)}\n${color.magenta("○")} ${message}\n` |
| 196 | + ); |
| 197 | + let i = 0; |
| 198 | + let dot = 0; |
| 199 | + loop = setInterval(() => { |
| 200 | + let frame = frames[i]; |
| 201 | + dot = i % 2 === 0 ? i / 2 : dot; |
| 202 | + process.stdout.write(cursor.move(-999, -1)); |
| 203 | + process.stdout.write( |
| 204 | + `${color.magenta(frame)} ${message}${dot > 0 ? '.'.repeat(dot) : ''} \n` |
| 205 | + ); |
| 206 | + i = i > frames.length - 2 ? 0 : i + 1; |
| 207 | + }, delay); |
| 208 | + }, |
| 209 | + stop(message = "") { |
| 210 | + process.stdout.write(cursor.move(-999, -2)); |
| 211 | + process.stdout.write(erase.down(2)); |
| 212 | + clearInterval(loop); |
| 213 | + process.stdout.write( |
| 214 | + `${color.gray(bar)}\n${color.gray("○")} ${message}\n` |
| 215 | + ); |
| 216 | + unblock(); |
| 217 | + }, |
| 218 | + }; |
| 219 | +}; |
0 commit comments