@rigerc/ink-ui 0.0.0-stage → 0.41.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +536 -0
- package/LICENSE +21 -0
- package/README.md +170 -2
- package/dist/index.d.ts +1094 -0
- package/dist/index.js +1920 -0
- package/package.json +68 -4
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import React__default, { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type BannerProps = {
|
|
5
|
+
title: string;
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
};
|
|
9
|
+
declare const Banner: ({ title, subtitle, icon }: BannerProps) => React__default.JSX.Element;
|
|
10
|
+
|
|
11
|
+
type BadgeVariant = "success" | "error" | "warning" | "info";
|
|
12
|
+
type BadgeProps = {
|
|
13
|
+
children: string;
|
|
14
|
+
variant?: BadgeVariant;
|
|
15
|
+
};
|
|
16
|
+
declare const Badge: ({ children, variant }: BadgeProps) => React__default.JSX.Element;
|
|
17
|
+
|
|
18
|
+
type PillVariant = "success" | "error" | "warning" | "info" | "accent" | "muted" | "group";
|
|
19
|
+
type PillTone = "solid" | "soft" | "outline" | "tonal";
|
|
20
|
+
type PillProps = {
|
|
21
|
+
children: string;
|
|
22
|
+
variant?: PillVariant;
|
|
23
|
+
/**
|
|
24
|
+
* Solid is an EVENT — something happened to the thing (`merged`, `NEW`,
|
|
25
|
+
* `GONE`). Soft is a CLASSIFICATION — what the thing is (`epic`, `bug`): a
|
|
26
|
+
* quiet permanent fill from `softColors`, built to sit on every row of a
|
|
27
|
+
* dark ground without out-shouting the row's news. Outline is the same
|
|
28
|
+
* classification drawn as a hue-only ring, for a ground where a fill of any
|
|
29
|
+
* weight is too much. Tonal is CHROME — a count, a slot label — a tint of
|
|
30
|
+
* the hue over the ground from `tonalColors`, inked in the hue: the
|
|
31
|
+
* quietest filled form, for what the frame says about itself rather than
|
|
32
|
+
* row data. The default stays solid so nothing already rendered moves.
|
|
33
|
+
*/
|
|
34
|
+
tone?: PillTone;
|
|
35
|
+
/**
|
|
36
|
+
* Tonal only: the thing you are ON takes the stronger tint from
|
|
37
|
+
* `tonalStrongColors` and bolds its label. Ignored by every other tone.
|
|
38
|
+
*/
|
|
39
|
+
strong?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* An explicit fill, for a state the external system INVENTED — GitHub's
|
|
42
|
+
* merged purple, a CI provider's result colours. The hue is that system's
|
|
43
|
+
* vocabulary for something that happened, and repainting it as a token would
|
|
44
|
+
* mislabel the event. Overrides `variant`.
|
|
45
|
+
*
|
|
46
|
+
* Never for that system's SKIN. Jira colours its issue types, but an issue
|
|
47
|
+
* type is a classification, and a classification takes a `variant` chosen by
|
|
48
|
+
* meaning, not the colour Jira happens to paint it. The test is whether the
|
|
49
|
+
* hue names a state the reader already knows from the source system, or
|
|
50
|
+
* merely decorates a category the tokens can already say.
|
|
51
|
+
*/
|
|
52
|
+
color?: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* A rounded label — a category the thing belongs to, or an event that has
|
|
56
|
+
* just happened to it.
|
|
57
|
+
*
|
|
58
|
+
* The law, in one line: the column says where it sits, soft says what it is,
|
|
59
|
+
* solid says something happened, tonal says where you are. A solid pill is an
|
|
60
|
+
* event (`merged`, `NEW`, `GONE`) and earns its loud fill by being news; a
|
|
61
|
+
* soft pill is a classification (`epic`, `bug`, `task`) and takes the quiet
|
|
62
|
+
* fill from `softColors`, because a type is a fact about the row, not
|
|
63
|
+
* something that happened to it — it has to be legible on every row without
|
|
64
|
+
* competing with the one row that has news. Outline is the classification
|
|
65
|
+
* with no fill at all, for a ground where even a soft block is too much.
|
|
66
|
+
* Tonal is chrome on the frame — a count, a slot label — the quietest filled
|
|
67
|
+
* form, with `strong` for the thing you are on. A screen where every pill is
|
|
68
|
+
* solid has no way to say which pill is the news.
|
|
69
|
+
*
|
|
70
|
+
* Reach for it when the word IS the information and you want it to read as
|
|
71
|
+
* one object rather than as more prose. For a reference the reader is meant
|
|
72
|
+
* to follow — a ticket key, a repo — leave the text dim: a fill gives a
|
|
73
|
+
* breadcrumb a weight it has not earned, and once everything is a pill none
|
|
74
|
+
* of them is.
|
|
75
|
+
*
|
|
76
|
+
* The WORD carries the meaning and the colour only reinforces it, so a pill
|
|
77
|
+
* survives being read in monochrome, piped, or by someone who cannot separate
|
|
78
|
+
* the hues.
|
|
79
|
+
*
|
|
80
|
+
* Powerline half-circles, like `ToggleSwitch` and for the same reason: they are
|
|
81
|
+
* the only way to get a genuinely rounded end out of a terminal cell, and a font
|
|
82
|
+
* without them renders blanks, which degrades to a square pill rather than
|
|
83
|
+
* breaking the layout. Not gated on `getIconMode` — that switch chooses between
|
|
84
|
+
* two glyph SETS, and this is one glyph with a graceful absence. Solid and soft take
|
|
85
|
+
* the filled caps, outline the thin ones from the same Powerline Extra block.
|
|
86
|
+
*
|
|
87
|
+
* `NO_COLOR` sends solid, soft and tonal to brackets: with the fill stripped,
|
|
88
|
+
* the caps would be drawing the outline of a pill that is not there. Outline
|
|
89
|
+
* keeps its caps — they ARE the outline, and there was never a fill for them
|
|
90
|
+
* to be missing.
|
|
91
|
+
*/
|
|
92
|
+
declare const Pill: ({ children, variant, tone, strong, color, }: PillProps) => React__default.JSX.Element;
|
|
93
|
+
/**
|
|
94
|
+
* How many columns `<Pill>` occupies for `text` — the label plus its two caps,
|
|
95
|
+
* whichever tone draws them.
|
|
96
|
+
*
|
|
97
|
+
* Exported because a caller laying out a fixed-width row has to price the pill
|
|
98
|
+
* BEFORE rendering it, and measuring the rendered output is not available at
|
|
99
|
+
* that point. A row that budgets for the label alone overflows by exactly two
|
|
100
|
+
* columns, which in a frame sized to fill the terminal scrolls the whole panel
|
|
101
|
+
* instead of clipping.
|
|
102
|
+
*/
|
|
103
|
+
declare const pillWidth: (text: string) => number;
|
|
104
|
+
|
|
105
|
+
type HeaderProps = {
|
|
106
|
+
children: string;
|
|
107
|
+
subtitle?: string;
|
|
108
|
+
};
|
|
109
|
+
declare const Header: ({ children, subtitle }: HeaderProps) => React__default.JSX.Element;
|
|
110
|
+
|
|
111
|
+
type SpinnerProps = {
|
|
112
|
+
label?: string;
|
|
113
|
+
/** Use ASCII spinner frames on limited terminals. */
|
|
114
|
+
ascii?: boolean;
|
|
115
|
+
};
|
|
116
|
+
declare const Spinner: ({ label, ascii }: SpinnerProps) => React__default.JSX.Element;
|
|
117
|
+
|
|
118
|
+
type ColumnAlign = "left" | "center" | "right";
|
|
119
|
+
type ColumnOverflow = "wrap" | "truncate";
|
|
120
|
+
|
|
121
|
+
type Column<T extends Record<string, unknown>> = {
|
|
122
|
+
key: keyof T & string;
|
|
123
|
+
header: string;
|
|
124
|
+
width?: number;
|
|
125
|
+
minWidth?: number;
|
|
126
|
+
align?: ColumnAlign;
|
|
127
|
+
overflow?: ColumnOverflow;
|
|
128
|
+
};
|
|
129
|
+
type TableProps<T extends Record<string, unknown>> = {
|
|
130
|
+
data: T[];
|
|
131
|
+
columns: Column<T>[];
|
|
132
|
+
gap?: number;
|
|
133
|
+
maxWidth?: number;
|
|
134
|
+
headerColor?: string;
|
|
135
|
+
};
|
|
136
|
+
declare const Table: <T extends Record<string, unknown>>({ data, columns, gap, maxWidth, headerColor, }: TableProps<T>) => React__default.JSX.Element;
|
|
137
|
+
|
|
138
|
+
type Hint = [key: string, label: string];
|
|
139
|
+
type FooterHintsProps = {
|
|
140
|
+
hints: Hint[];
|
|
141
|
+
/**
|
|
142
|
+
* Where this page sits, so the footer can end the same way on every page
|
|
143
|
+
* of every app: `⌫ back` on a nested page, then `? help`, then `q quit`,
|
|
144
|
+
* always last. A host passes only its own hints — navigation, actions,
|
|
145
|
+
* modes — and never these three. Omit it to draw the hints as given.
|
|
146
|
+
*/
|
|
147
|
+
page?: "root" | "nested";
|
|
148
|
+
/** Drop `? help` from the tail for an app with no legend. */
|
|
149
|
+
help?: boolean;
|
|
150
|
+
};
|
|
151
|
+
/** The cells every page ends with, in the order the eye learns once. */
|
|
152
|
+
declare const tailHints: (page: "root" | "nested", help?: boolean) => Hint[];
|
|
153
|
+
declare const FooterHints: ({ hints, page, help }: FooterHintsProps) => React__default.JSX.Element;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The title row's live segment. `news` is something to act on (`● 5 new ·
|
|
157
|
+
* 38 moved · r apply`) and draws bold in the accent; `busy` is in flight
|
|
158
|
+
* (`↻ refreshing…`) and draws in the info colour; `quiet` is freshness
|
|
159
|
+
* (`updated 42m ago`) and draws dim.
|
|
160
|
+
*/
|
|
161
|
+
type PageStatus = {
|
|
162
|
+
text: string;
|
|
163
|
+
tone: "news" | "busy" | "quiet";
|
|
164
|
+
};
|
|
165
|
+
type PageProps = {
|
|
166
|
+
/** The app's name, bold, in the accent. Constant across every page of the app. */
|
|
167
|
+
title: string;
|
|
168
|
+
/** One glyph before the name — the app's mark. */
|
|
169
|
+
icon?: string;
|
|
170
|
+
/** How many things the page holds; drawn dim as ` 127 items · ` with a width-stable count. */
|
|
171
|
+
count?: number;
|
|
172
|
+
/** What `count` counts. Defaults to `item`. */
|
|
173
|
+
noun?: string;
|
|
174
|
+
/** Whose things these are — `@kud` — drawn plain, never dim: scope is the fact the row exists to state. */
|
|
175
|
+
user?: string;
|
|
176
|
+
/** A further scope in the host's words, dim: `work`, or a breadcrumb like `ACME-102 · 3 of 12`. */
|
|
177
|
+
scope?: string;
|
|
178
|
+
/** Something worth acting on today — a budget running out — bold in the accent. `critical` turns it red. */
|
|
179
|
+
alert?: string;
|
|
180
|
+
critical?: boolean;
|
|
181
|
+
/** Freshness or news; see `PageStatus`. */
|
|
182
|
+
status?: PageStatus;
|
|
183
|
+
/** Free text after the name, dim — for a host with nothing structured to say (`loading…`). */
|
|
184
|
+
facts?: string;
|
|
185
|
+
/** The tab strip, only on pages that have one. A detail page passes none and the band is dropped, never left empty. */
|
|
186
|
+
tabs?: ReactNode;
|
|
187
|
+
/** Right-aligned, dim, above the hints: `3 of 12`, `↓ 40%`. */
|
|
188
|
+
counter?: string;
|
|
189
|
+
/** The host's own hints — navigation, actions, modes. The tail (`⌫ back · ? help · q quit`) is derived from `page`. */
|
|
190
|
+
hints?: Hint[];
|
|
191
|
+
/** Root or nested, which decides whether `⌫ back` is drawn. Defaults to root. */
|
|
192
|
+
page?: "root" | "nested";
|
|
193
|
+
/** Whether the app has a `?` legend; drops `? help` from the tail when false. */
|
|
194
|
+
help?: boolean;
|
|
195
|
+
/**
|
|
196
|
+
* Whether the frame draws the blank under the title (band two). A body that
|
|
197
|
+
* draws that band itself — a board whose search box lives there — passes
|
|
198
|
+
* `false`, or the two blanks together overflow the frame and one is lost.
|
|
199
|
+
*/
|
|
200
|
+
gap?: boolean;
|
|
201
|
+
/**
|
|
202
|
+
* A `FilterBar`, drawn in band two — the blank under the title, which is
|
|
203
|
+
* where search lives — so every app puts its filter in the same place. It
|
|
204
|
+
* takes the blank's row rather than adding one, so the body never moves
|
|
205
|
+
* when a filter opens or closes.
|
|
206
|
+
*/
|
|
207
|
+
filter?: ReactNode;
|
|
208
|
+
/**
|
|
209
|
+
* Size the frame to the terminal, and follow it on resize. Without it the
|
|
210
|
+
* frame is content-sized: as wide as its container and as tall as its body.
|
|
211
|
+
* An explicit `width` or `height` still wins over the window on that axis.
|
|
212
|
+
*/
|
|
213
|
+
fill?: boolean;
|
|
214
|
+
width?: number;
|
|
215
|
+
height?: number;
|
|
216
|
+
children: ReactNode;
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* The one frame every screen of an app sits in — list, detail, loading,
|
|
220
|
+
* error, prompt — so the border and the title row never come and go between
|
|
221
|
+
* states. Six bands inside a round border: title row, the blank under it,
|
|
222
|
+
* tabs where the page has them, body, counter, hints. Presentational: it
|
|
223
|
+
* binds no keys, so a host can take the frame without surrendering its own
|
|
224
|
+
* keyboard handling. `Panel` stays for inner regions inside `Columns`.
|
|
225
|
+
*
|
|
226
|
+
* The title row is the cockpit's, made general: brand · count · user · scope ·
|
|
227
|
+
* alert · status, then a dotted rule to the edge. The count is padded in
|
|
228
|
+
* front rather than behind so the header does not shuffle as it changes and
|
|
229
|
+
* the digits stay against their noun. Only the status segment changes width
|
|
230
|
+
* from one refresh to the next, and the rule after it absorbs the difference.
|
|
231
|
+
*
|
|
232
|
+
* A detail page keeps the app's title row rather than taking the item's key
|
|
233
|
+
* as its title: the title row is how you know which app you are in and how
|
|
234
|
+
* stale it is, and neither changes when you open an item. The item's key
|
|
235
|
+
* belongs in `scope` as a breadcrumb.
|
|
236
|
+
*/
|
|
237
|
+
declare const Page: ({ title, icon, count, noun, user, scope, alert, critical, status, facts, tabs, counter, hints, page, help, gap, filter, fill, width, height, children, }: PageProps) => React__default.JSX.Element;
|
|
238
|
+
/** Lines the frame itself spends around the body: two borders, the title row, the blank under it. */
|
|
239
|
+
declare const PAGE_CHROME = 4;
|
|
240
|
+
|
|
241
|
+
type KeyValueProps = {
|
|
242
|
+
label: string;
|
|
243
|
+
value: React__default.ReactNode;
|
|
244
|
+
labelWidth?: number;
|
|
245
|
+
};
|
|
246
|
+
declare const KeyValue: ({ label, value, labelWidth }: KeyValueProps) => React__default.JSX.Element;
|
|
247
|
+
|
|
248
|
+
type LoadingScreenProps = {
|
|
249
|
+
label: string;
|
|
250
|
+
};
|
|
251
|
+
declare const LoadingScreen: ({ label }: LoadingScreenProps) => React__default.JSX.Element;
|
|
252
|
+
|
|
253
|
+
type PanelProps = {
|
|
254
|
+
/** Optional title shown in the panel's header row. */
|
|
255
|
+
title?: string;
|
|
256
|
+
/** Accent colour for the border + title when focused. Defaults to the info token. */
|
|
257
|
+
color?: string;
|
|
258
|
+
/** When true the border brightens to `color` and the title gains a ● marker. */
|
|
259
|
+
focused?: boolean;
|
|
260
|
+
width?: number;
|
|
261
|
+
height?: number;
|
|
262
|
+
flexGrow?: number;
|
|
263
|
+
children: ReactNode;
|
|
264
|
+
};
|
|
265
|
+
declare const Panel: ({ title, color, focused, width, height, flexGrow, children, }: PanelProps) => React__default.JSX.Element;
|
|
266
|
+
|
|
267
|
+
type ColumnsProps = {
|
|
268
|
+
/** Horizontal gap between columns (character cells). Defaults to 1. */
|
|
269
|
+
gap?: number;
|
|
270
|
+
children: ReactNode;
|
|
271
|
+
};
|
|
272
|
+
declare const Columns: ({ gap, children }: ColumnsProps) => React__default.JSX.Element;
|
|
273
|
+
|
|
274
|
+
type SelectableRowProps = {
|
|
275
|
+
active?: boolean;
|
|
276
|
+
marker?: string;
|
|
277
|
+
children: React__default.ReactNode;
|
|
278
|
+
};
|
|
279
|
+
declare const SelectableRow: ({ active, marker, children, }: SelectableRowProps) => React__default.JSX.Element;
|
|
280
|
+
|
|
281
|
+
type SelectOption<T extends string = string> = {
|
|
282
|
+
label: string;
|
|
283
|
+
value: T;
|
|
284
|
+
};
|
|
285
|
+
type SelectProps<T extends string> = {
|
|
286
|
+
options: SelectOption<T>[];
|
|
287
|
+
onChange?: (value: T) => void;
|
|
288
|
+
onSubmit?: (value: T) => void;
|
|
289
|
+
defaultValue?: T;
|
|
290
|
+
isDisabled?: boolean;
|
|
291
|
+
visibleOptionCount?: number;
|
|
292
|
+
};
|
|
293
|
+
declare const Select: <T extends string>({ options, onChange, onSubmit, defaultValue, isDisabled, visibleOptionCount, }: SelectProps<T>) => React__default.JSX.Element;
|
|
294
|
+
|
|
295
|
+
type MultiSelectProps<T extends string> = {
|
|
296
|
+
options: SelectOption<T>[];
|
|
297
|
+
onChange?: (values: T[]) => void;
|
|
298
|
+
onSubmit?: (values: T[]) => void;
|
|
299
|
+
defaultValue?: T[];
|
|
300
|
+
isDisabled?: boolean;
|
|
301
|
+
visibleOptionCount?: number;
|
|
302
|
+
};
|
|
303
|
+
declare const MultiSelect: <T extends string>({ options, onChange, onSubmit, defaultValue, isDisabled, visibleOptionCount, }: MultiSelectProps<T>) => React__default.JSX.Element;
|
|
304
|
+
|
|
305
|
+
type TextInputProps = {
|
|
306
|
+
placeholder?: string;
|
|
307
|
+
defaultValue?: string;
|
|
308
|
+
onChange?: (value: string) => void;
|
|
309
|
+
onSubmit?: (value: string) => void;
|
|
310
|
+
/**
|
|
311
|
+
* Called on `esc`. The value is kept — cancelling blurs the field, it does
|
|
312
|
+
* not clear it — so the host can drop focus and let its own back key
|
|
313
|
+
* take over, without a second `useInput` around the box.
|
|
314
|
+
*/
|
|
315
|
+
onCancel?: () => void;
|
|
316
|
+
isDisabled?: boolean;
|
|
317
|
+
};
|
|
318
|
+
declare const TextInput: ({ placeholder, defaultValue, onChange, onSubmit, onCancel, isDisabled, }: TextInputProps) => React__default.JSX.Element;
|
|
319
|
+
|
|
320
|
+
type EmailInputProps = {
|
|
321
|
+
placeholder?: string;
|
|
322
|
+
defaultValue?: string;
|
|
323
|
+
domains?: string[];
|
|
324
|
+
onChange?: (value: string) => void;
|
|
325
|
+
onSubmit?: (value: string) => void;
|
|
326
|
+
isDisabled?: boolean;
|
|
327
|
+
};
|
|
328
|
+
declare const EmailInput: ({ placeholder, defaultValue, domains, onChange, onSubmit, isDisabled, }: EmailInputProps) => React__default.JSX.Element;
|
|
329
|
+
|
|
330
|
+
type PasswordInputProps = {
|
|
331
|
+
placeholder?: string;
|
|
332
|
+
mask?: string;
|
|
333
|
+
onChange?: (value: string) => void;
|
|
334
|
+
onSubmit?: (value: string) => void;
|
|
335
|
+
isDisabled?: boolean;
|
|
336
|
+
};
|
|
337
|
+
declare const PasswordInput: ({ placeholder, mask, onChange, onSubmit, isDisabled, }: PasswordInputProps) => React__default.JSX.Element;
|
|
338
|
+
|
|
339
|
+
type ConfirmInputProps = {
|
|
340
|
+
defaultChoice?: "confirm" | "cancel";
|
|
341
|
+
onConfirm?: () => void;
|
|
342
|
+
onCancel?: () => void;
|
|
343
|
+
isDisabled?: boolean;
|
|
344
|
+
};
|
|
345
|
+
declare const ConfirmInput: ({ defaultChoice, onConfirm, onCancel, isDisabled, }: ConfirmInputProps) => React__default.JSX.Element;
|
|
346
|
+
|
|
347
|
+
type ItemProps$1 = {
|
|
348
|
+
children: React__default.ReactNode;
|
|
349
|
+
};
|
|
350
|
+
type UnorderedListProps = {
|
|
351
|
+
children: React__default.ReactNode;
|
|
352
|
+
};
|
|
353
|
+
declare const UnorderedList: (({ children }: UnorderedListProps) => React__default.JSX.Element) & {
|
|
354
|
+
Item: ({ children }: ItemProps$1) => React__default.JSX.Element;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
type ItemProps = {
|
|
358
|
+
children: React__default.ReactNode;
|
|
359
|
+
_index?: number;
|
|
360
|
+
};
|
|
361
|
+
type OrderedListProps = {
|
|
362
|
+
children: React__default.ReactNode;
|
|
363
|
+
};
|
|
364
|
+
declare const OrderedList: (({ children }: OrderedListProps) => React__default.JSX.Element) & {
|
|
365
|
+
Item: ({ children, _index }: ItemProps) => React__default.JSX.Element;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
type Span = {
|
|
369
|
+
text: string;
|
|
370
|
+
color?: string;
|
|
371
|
+
bold?: boolean;
|
|
372
|
+
dim?: boolean;
|
|
373
|
+
italic?: boolean;
|
|
374
|
+
};
|
|
375
|
+
type StyledLine = {
|
|
376
|
+
text: string;
|
|
377
|
+
color?: string;
|
|
378
|
+
bold?: boolean;
|
|
379
|
+
dim?: boolean;
|
|
380
|
+
spans?: Span[];
|
|
381
|
+
};
|
|
382
|
+
type ScrollViewProps = {
|
|
383
|
+
lines: StyledLine[];
|
|
384
|
+
showIndicator?: boolean;
|
|
385
|
+
initialStart?: number;
|
|
386
|
+
isActive?: boolean;
|
|
387
|
+
};
|
|
388
|
+
declare const ScrollView: ({ lines, showIndicator, initialStart, isActive, }: ScrollViewProps) => React__default.JSX.Element;
|
|
389
|
+
|
|
390
|
+
type TabItem<T extends string = string> = {
|
|
391
|
+
value: T;
|
|
392
|
+
label: string;
|
|
393
|
+
/**
|
|
394
|
+
* A single glyph drawn between the `marker` gutter and the label, one space
|
|
395
|
+
* before the label — a Nerd Font char, a geometric shape, whatever names the
|
|
396
|
+
* tab at a glance.
|
|
397
|
+
*
|
|
398
|
+
* It is part of the label, not a second gutter: it takes the label's own
|
|
399
|
+
* style (bold in the accent when active, dim when not) and the active rule
|
|
400
|
+
* spans icon, space and label together. The marker keeps its own cell and its
|
|
401
|
+
* own colour, so a pulse appearing beside an icon still moves nothing.
|
|
402
|
+
* Measured with `string-width` rather than counted, so a wide glyph prices
|
|
403
|
+
* its own columns.
|
|
404
|
+
*
|
|
405
|
+
* It is also what a narrow terminal folds to: when the strip does not fit its
|
|
406
|
+
* `width`, an inactive tab with an icon drops its label and renders `icon
|
|
407
|
+
* count`. Omit it and the tab never folds.
|
|
408
|
+
*/
|
|
409
|
+
icon?: string;
|
|
410
|
+
/**
|
|
411
|
+
* How many things the tab holds, drawn as a soft chip beside the label — no
|
|
412
|
+
* parentheses. `null` means "not known yet" and draws a chip holding `–`,
|
|
413
|
+
* padded in front to the width of a two-digit count, so the bar does not
|
|
414
|
+
* shift when the real numbers land. Omit it for a tab with no count. With
|
|
415
|
+
* `NO_COLOR` the chip falls back to the parenthesised `(–)` form.
|
|
416
|
+
*/
|
|
417
|
+
count?: number | null;
|
|
418
|
+
/**
|
|
419
|
+
* A marker drawn immediately before the label, in its own colour.
|
|
420
|
+
*
|
|
421
|
+
* Give every tab one of the SAME WIDTH, or none at all. A marker that appears
|
|
422
|
+
* on one tab alone pushes every tab after it sideways — which is the whole
|
|
423
|
+
* reason this is a field rather than something a caller prepends to `label`:
|
|
424
|
+
* a bar that shifts when news arrives is a bar you have to re-find. A blank of
|
|
425
|
+
* the right width is how you say "not this one".
|
|
426
|
+
*
|
|
427
|
+
* Its own `Text` because the label's colour answers "is this tab active" and a
|
|
428
|
+
* marker usually answers something else; folded together, the marker would
|
|
429
|
+
* have to borrow the answer to the wrong question.
|
|
430
|
+
*
|
|
431
|
+
* Animating one costs nothing: the cell is already reserved, so a caller
|
|
432
|
+
* cycling the glyph or the colour per frame moves no layout at all.
|
|
433
|
+
*/
|
|
434
|
+
marker?: string;
|
|
435
|
+
markerColor?: string;
|
|
436
|
+
/**
|
|
437
|
+
* The true size of what `count` is a window onto, drawn inside the count's
|
|
438
|
+
* own chip as `count/total` — one chip, never two.
|
|
439
|
+
*
|
|
440
|
+
* A field rather than something a caller folds into `label`, for the same
|
|
441
|
+
* reason `marker` is one — and this one was learned the hard way. A caller
|
|
442
|
+
* with no channel for "there are more than these" smuggled it into the label
|
|
443
|
+
* as `Issues +129 (20)`, and `+` is an operator: it tells the reader to add,
|
|
444
|
+
* with nothing on screen to add it to. The arithmetic even works, which is
|
|
445
|
+
* what makes it vicious — a reader who obeys gets a real number for a question
|
|
446
|
+
* nobody asked, with no signal they have misread anything.
|
|
447
|
+
*
|
|
448
|
+
* A fraction is part-of-whole, the most over-learned notation there is: page 3
|
|
449
|
+
* of 12. Nobody computes with a slash. The distinction rides on a glyph rather
|
|
450
|
+
* than a hue, so it survives dimming, greyscale and colourblindness — and the
|
|
451
|
+
* magnitude survives with it, which is the half that matters most. `30/37`
|
|
452
|
+
* and `20/149` say very different things about how far a tab can be trusted,
|
|
453
|
+
* where a bare truncation flag says only "incomplete".
|
|
454
|
+
*
|
|
455
|
+
* With `NO_COLOR` the chip falls back to the parenthesised `(count/total)`
|
|
456
|
+
* form.
|
|
457
|
+
*
|
|
458
|
+
* Omit it when the count IS the whole, so one number keeps meaning "complete"
|
|
459
|
+
* and two always mean "you are looking at a sample".
|
|
460
|
+
*/
|
|
461
|
+
total?: number;
|
|
462
|
+
/**
|
|
463
|
+
* Optional group name. A divider is drawn between tabs whose group value
|
|
464
|
+
* changes. Tabs with no group, or the same group as their neighbour, render
|
|
465
|
+
* without a divider.
|
|
466
|
+
*/
|
|
467
|
+
group?: string;
|
|
468
|
+
};
|
|
469
|
+
type TabsProps<T extends string> = {
|
|
470
|
+
active: T;
|
|
471
|
+
items: TabItem<T>[];
|
|
472
|
+
/**
|
|
473
|
+
* The width the strip may spend. Defaults to the terminal's columns; when
|
|
474
|
+
* that is unknown — piped output, a captured stdout — the strip never folds,
|
|
475
|
+
* because a fold is a judgement about a visible edge.
|
|
476
|
+
*
|
|
477
|
+
* Folding is all-or-nothing and measured, never a breakpoint: when the full
|
|
478
|
+
* strip (every marker gutter, icon, label, chip, gap and divider) does not
|
|
479
|
+
* fit, each inactive tab WITH an icon drops its label and renders `icon` plus
|
|
480
|
+
* its chip. The active tab always keeps its full form, tabs without an icon
|
|
481
|
+
* never fold, and dividers stay where they were.
|
|
482
|
+
*/
|
|
483
|
+
width?: number;
|
|
484
|
+
};
|
|
485
|
+
declare const Tabs: <T extends string>({ active, items, width, }: TabsProps<T>) => React__default.JSX.Element;
|
|
486
|
+
|
|
487
|
+
/** Inspired by the MIT-licensed Stepper in rigerc/nosey. */
|
|
488
|
+
interface StepContext {
|
|
489
|
+
currentStep: number;
|
|
490
|
+
totalSteps: number;
|
|
491
|
+
isFirst: boolean;
|
|
492
|
+
isLast: boolean;
|
|
493
|
+
isValidating: boolean;
|
|
494
|
+
/** Enabled only when the parent opts into the Stepper's Enter shortcut. */
|
|
495
|
+
keyboardNav: boolean;
|
|
496
|
+
goNext: () => Promise<void>;
|
|
497
|
+
goBack: () => Promise<void>;
|
|
498
|
+
/** Jump without canProceed, but still run onExitStep. */
|
|
499
|
+
goTo: (index: number) => Promise<void>;
|
|
500
|
+
cancel: () => void;
|
|
501
|
+
}
|
|
502
|
+
interface StepProps {
|
|
503
|
+
name: string;
|
|
504
|
+
canProceed?: boolean | (() => boolean | Promise<boolean>);
|
|
505
|
+
children: ReactNode | ((context: StepContext) => ReactNode);
|
|
506
|
+
}
|
|
507
|
+
/** Marker component: only the active Step's content is mounted. */
|
|
508
|
+
declare const Step: (_props: StepProps) => null;
|
|
509
|
+
interface StepperProps {
|
|
510
|
+
/** Step elements or fragments of Steps; key conditional/reorderable Steps. */
|
|
511
|
+
children: ReactNode;
|
|
512
|
+
onComplete: () => void;
|
|
513
|
+
onCancel?: () => void;
|
|
514
|
+
onStepChange?: (index: number) => void;
|
|
515
|
+
/** Returning false blocks leaving the current step. */
|
|
516
|
+
onExitStep?: (index: number) => void | boolean | Promise<void | boolean>;
|
|
517
|
+
onEnterStep?: (index: number) => void;
|
|
518
|
+
onError?: (cause: unknown) => void;
|
|
519
|
+
/** Controlled, zero-based position. */
|
|
520
|
+
step?: number;
|
|
521
|
+
initialStep?: number;
|
|
522
|
+
/**
|
|
523
|
+
* Listen to Enter to advance. Off by default: Ink broadcasts input to all
|
|
524
|
+
* active useInput listeners, and nested inputs may already own Enter.
|
|
525
|
+
* Esc/backspace/q always belong to the host's useAppKeys.
|
|
526
|
+
*/
|
|
527
|
+
keyboardNav?: boolean;
|
|
528
|
+
/** Use ASCII symbols in the built-in progress display. */
|
|
529
|
+
ascii?: boolean;
|
|
530
|
+
showProgress?: boolean;
|
|
531
|
+
renderProgress?: (props: StepperProgressProps) => ReactNode;
|
|
532
|
+
}
|
|
533
|
+
interface StepperProgressProps {
|
|
534
|
+
steps: readonly {
|
|
535
|
+
id: string;
|
|
536
|
+
name: string;
|
|
537
|
+
}[];
|
|
538
|
+
/** steps.length means all steps have been completed. */
|
|
539
|
+
currentStep: number;
|
|
540
|
+
orientation?: "horizontal" | "vertical";
|
|
541
|
+
/** Use printable ASCII symbols rather than Unicode. */
|
|
542
|
+
ascii?: boolean;
|
|
543
|
+
}
|
|
544
|
+
declare const StepperProgress: ({ steps, currentStep, orientation, ascii, }: StepperProgressProps) => React__default.JSX.Element;
|
|
545
|
+
declare const useStepper: () => StepContext;
|
|
546
|
+
/** Hints follow the FooterHints visual language; the host still owns Back. */
|
|
547
|
+
declare const StepperNavigation: () => React__default.JSX.Element;
|
|
548
|
+
declare const Stepper: ({ children, onComplete, onCancel, onStepChange, onExitStep, onEnterStep, onError, step, initialStep, keyboardNav, ascii, showProgress, renderProgress, }: StepperProps) => React__default.JSX.Element | null;
|
|
549
|
+
|
|
550
|
+
type UseTabsOptions<T extends string> = {
|
|
551
|
+
initial?: T;
|
|
552
|
+
isActive?: boolean;
|
|
553
|
+
/**
|
|
554
|
+
* Whether ←→ move between tabs as well as Tab / Shift+Tab. On by default:
|
|
555
|
+
* a screen whose ←→ mean something else — a yes/no toggle, a horizontal
|
|
556
|
+
* scroll — turns them off here rather than fighting the hook for the keys.
|
|
557
|
+
*/
|
|
558
|
+
arrows?: boolean;
|
|
559
|
+
};
|
|
560
|
+
declare const useTabs: <T extends string>(items: TabItem<T>[], { initial, isActive, arrows }?: UseTabsOptions<T>) => {
|
|
561
|
+
active: T | undefined;
|
|
562
|
+
setActive: React.Dispatch<React.SetStateAction<T | undefined>>;
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
type PaletteItem = {
|
|
566
|
+
id: string;
|
|
567
|
+
/** What is matched and, absent `label`, what is drawn. */
|
|
568
|
+
title: string;
|
|
569
|
+
/**
|
|
570
|
+
* What is drawn instead of `title` when a row needs more than one ink — a
|
|
571
|
+
* ticket key in the accent beside a destination word in `info`. Matching
|
|
572
|
+
* still reads `title`, so a host that draws rich rows keeps them findable.
|
|
573
|
+
*/
|
|
574
|
+
label?: ReactNode;
|
|
575
|
+
/** Rows sharing a group are drawn under one header while the query is empty. */
|
|
576
|
+
group?: string;
|
|
577
|
+
/** Dim trailing text: a description, a path, what the row will do. */
|
|
578
|
+
hint?: string;
|
|
579
|
+
keywords?: string[];
|
|
580
|
+
/**
|
|
581
|
+
* One cell, drawn before the title. Every row in a list carries a marker of
|
|
582
|
+
* the same width or none — the cell is reserved for all rows the moment one
|
|
583
|
+
* row has one, so a marker arriving never shifts its neighbours.
|
|
584
|
+
*/
|
|
585
|
+
marker?: string;
|
|
586
|
+
markerColor?: string;
|
|
587
|
+
};
|
|
588
|
+
/**
|
|
589
|
+
* A message with a tone. `muted` (the default) is what the query did not find
|
|
590
|
+
* or what is not configured; `error` is a failure, drawn in `colors.error`
|
|
591
|
+
* behind the error glyph so it reads as one without the hue. `onSubmit` makes
|
|
592
|
+
* Enter act while the message shows — a failure the host can retry — where a
|
|
593
|
+
* bare message leaves Enter a no-op.
|
|
594
|
+
*/
|
|
595
|
+
type PaletteMessage = {
|
|
596
|
+
text: string;
|
|
597
|
+
tone?: "muted" | "error";
|
|
598
|
+
onSubmit?: () => void;
|
|
599
|
+
};
|
|
600
|
+
type CommandPaletteProps = {
|
|
601
|
+
/** The rows to draw, in order. The palette never filters them. */
|
|
602
|
+
items: PaletteItem[];
|
|
603
|
+
query: string;
|
|
604
|
+
onQueryChange: (query: string) => void;
|
|
605
|
+
onSelect: (id: string) => void;
|
|
606
|
+
/** `esc`. The value is the host's, so nothing here clears it. */
|
|
607
|
+
onClose?: () => void;
|
|
608
|
+
/**
|
|
609
|
+
* One line drawn where the rows would be, when there are none: what
|
|
610
|
+
* the query did not find, or what is not configured. Enter is a no-op while
|
|
611
|
+
* it shows, unless the message carries `onSubmit`. Never a row, so the
|
|
612
|
+
* cursor cannot land on it. A string is a muted message; pass a
|
|
613
|
+
* `PaletteMessage` for the error tone.
|
|
614
|
+
*/
|
|
615
|
+
message?: string | PaletteMessage;
|
|
616
|
+
/**
|
|
617
|
+
* One muted line drawn where the rows would be while the palette is still
|
|
618
|
+
* waiting for its first query: `items` is empty and the query is too, e.g.
|
|
619
|
+
* "type a ticket key". Loses to `message`, and never shows beside rows —
|
|
620
|
+
* once the host has rows (grouped or flat) they are drawn instead.
|
|
621
|
+
*/
|
|
622
|
+
emptyHint?: string;
|
|
623
|
+
placeholder?: string;
|
|
624
|
+
width?: number;
|
|
625
|
+
/** Visual lines the row area may spend before it scrolls. */
|
|
626
|
+
maxRows?: number;
|
|
627
|
+
hints?: Hint[];
|
|
628
|
+
isActive?: boolean;
|
|
629
|
+
};
|
|
630
|
+
/**
|
|
631
|
+
* A launcher: a query line over a list of rows, one cursor, Enter runs the row
|
|
632
|
+
* under it. Dumb by design — it holds no query state of its own and does no
|
|
633
|
+
* filtering, so the same component serves a host that narrows a fixed command
|
|
634
|
+
* tree (hand `fuzzyFilter` to `onQueryChange`) and a host that DERIVES rows from
|
|
635
|
+
* the query, where `ACC-1234` becomes two verbs. Uncontrolled in the keyboard
|
|
636
|
+
* sense: the query field owns typing, `↑↓` move the cursor, `⏎` selects, `esc`
|
|
637
|
+
* calls `onClose`. Mount it as the topmost layer and let the app's `useAppKeys`
|
|
638
|
+
* stand down while it is up, or `esc` will close it and go back a level too.
|
|
639
|
+
*/
|
|
640
|
+
declare const CommandPalette: ({ items, query, onQueryChange, onSelect, onClose, message, emptyHint, placeholder, width, maxRows, hints, isActive, }: CommandPaletteProps) => React__default.JSX.Element;
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* The static-list half of a launcher, as a pure function: the items that match
|
|
644
|
+
* `query`, substring hits first, in the caller's order within each rank. An
|
|
645
|
+
* empty or whitespace query returns every item unchanged — the palette then
|
|
646
|
+
* draws them grouped — so a host that only ever has a fixed command tree can
|
|
647
|
+
* hand this straight to `onQueryChange` and never filter itself.
|
|
648
|
+
*
|
|
649
|
+
* Deliberately NOT inside `CommandPalette`: the palette takes whatever rows the
|
|
650
|
+
* host gives it and knows nothing about where they came from, which is what
|
|
651
|
+
* lets a host derive rows from the query (a ticket key becomes two verbs) rather
|
|
652
|
+
* than narrow a preset list. Both shapes share one component; only this helper
|
|
653
|
+
* is optional.
|
|
654
|
+
*/
|
|
655
|
+
declare const fuzzyFilter: (items: PaletteItem[], query: string) => PaletteItem[];
|
|
656
|
+
|
|
657
|
+
type UseListCursorOptions = {
|
|
658
|
+
initial?: number;
|
|
659
|
+
wrap?: boolean;
|
|
660
|
+
vimKeys?: boolean;
|
|
661
|
+
isActive?: boolean;
|
|
662
|
+
};
|
|
663
|
+
declare const useListCursor: (length: number, { initial, wrap, vimKeys, isActive, }?: UseListCursorOptions) => {
|
|
664
|
+
cursor: number;
|
|
665
|
+
setCursor: React.Dispatch<React.SetStateAction<number>>;
|
|
666
|
+
moveCursor: (delta: number) => void;
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
type UseAppKeysOptions = {
|
|
670
|
+
/**
|
|
671
|
+
* The host's peel, called on `esc` and `backspace`: close the topmost layer
|
|
672
|
+
* it owns — a focused input, an overlay, a detail screen — and return
|
|
673
|
+
* `true`. Return `false` when there was nothing to pop; `atRoot` then says
|
|
674
|
+
* what happens. A `void` return counts as handled.
|
|
675
|
+
*/
|
|
676
|
+
onBack?: () => boolean | void;
|
|
677
|
+
/** What `q` does. Defaults to Ink's `exit()`, which restores the terminal. */
|
|
678
|
+
onQuit?: () => void;
|
|
679
|
+
/** `esc` with nothing left to pop: nothing (the default) or quit. */
|
|
680
|
+
atRoot?: "ignore" | "quit";
|
|
681
|
+
/** False while a text field has focus — `q` is a letter there and `backspace` deletes. */
|
|
682
|
+
isActive?: boolean;
|
|
683
|
+
};
|
|
684
|
+
/**
|
|
685
|
+
* The three keys that belong to the APP, never to a screen: `q` quits from
|
|
686
|
+
* anywhere, `esc` and `backspace` go back exactly one level. Mounted once, at
|
|
687
|
+
* the root — Ink runs every active `useInput` on every key with no order and
|
|
688
|
+
* no propagation, so "who gets `esc`" is only ever solved by there being one
|
|
689
|
+
* claimant. Not a back stack: a host's layers are a priority order over its
|
|
690
|
+
* own booleans, and that order stays in the host as `onBack`.
|
|
691
|
+
*
|
|
692
|
+
* `esc` at the root does nothing by default. A key that sometimes quits is a
|
|
693
|
+
* key you flinch from; quitting has its own key.
|
|
694
|
+
*/
|
|
695
|
+
declare const useAppKeys: ({ onBack, onQuit, atRoot, isActive, }?: UseAppKeysOptions) => void;
|
|
696
|
+
|
|
697
|
+
type UseFilterModeOptions = {
|
|
698
|
+
/** False while something above the list owns the keyboard — an overlay, a palette. */
|
|
699
|
+
isActive?: boolean;
|
|
700
|
+
/** Called after the term changes, with the new one. Typically `setCursor(0)`. */
|
|
701
|
+
onChange?: (term: string | null) => void;
|
|
702
|
+
};
|
|
703
|
+
/**
|
|
704
|
+
* A list filter with two modes, vim's split between inserting and acting.
|
|
705
|
+
*
|
|
706
|
+
* TYPING: `/` opens the field and every printable key goes into the term.
|
|
707
|
+
* `↵` and `esc` both leave typing and KEEP the filter (an empty term leaves
|
|
708
|
+
* no filter); `ctrl+u` clears the term and stays in the field.
|
|
709
|
+
* ↑↓ are not taken, so a host's `useListCursor` still walks the matches while
|
|
710
|
+
* the field is open — pass it `vimKeys: !typing` so `j`/`k` type instead.
|
|
711
|
+
*
|
|
712
|
+
* KEPT: the filter stays applied and the letter keys are the host's hotkeys
|
|
713
|
+
* again. `/` goes back INTO the term rather than starting over — the whole
|
|
714
|
+
* point of keeping it is being able to refine it. `esc` here is not this
|
|
715
|
+
* hook's: a kept filter is the bottom layer of the host's peel, so the host's
|
|
716
|
+
* `onBack` clears it through `clear()`, after every layer pushed above it.
|
|
717
|
+
* `esc` twice therefore clears a filter from anywhere.
|
|
718
|
+
*
|
|
719
|
+
* Owns its own `useInput`, like `useListCursor`. The host stands its other
|
|
720
|
+
* handlers down while `typing` is true — `useAppKeys({ isActive: !typing })`
|
|
721
|
+
* and an early return in its hotkey handler — because inside the field `q` is
|
|
722
|
+
* a letter, and Ink hands every key to every active hook.
|
|
723
|
+
*
|
|
724
|
+
* Extracted from gh-cockpit's inbox, where `/` used to clear on the way back
|
|
725
|
+
* in, so refining a kept query meant typing it again from scratch.
|
|
726
|
+
*/
|
|
727
|
+
declare const useFilterMode: ({ isActive, onChange, }?: UseFilterModeOptions) => {
|
|
728
|
+
term: string | null;
|
|
729
|
+
typing: boolean;
|
|
730
|
+
caret: number;
|
|
731
|
+
active: boolean;
|
|
732
|
+
clear: () => void;
|
|
733
|
+
hints: Hint[];
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
type FilterBarProps = {
|
|
737
|
+
/** From `useFilterMode`. `null` draws nothing. */
|
|
738
|
+
term: string | null;
|
|
739
|
+
/** From `useFilterMode`. Draws the caret. */
|
|
740
|
+
typing: boolean;
|
|
741
|
+
/** From `useFilterMode`. Where the caret sits; defaults to end-of-term. */
|
|
742
|
+
caret?: number;
|
|
743
|
+
/** How many rows survive the filter. Omit to draw no count. */
|
|
744
|
+
matches?: number;
|
|
745
|
+
};
|
|
746
|
+
/**
|
|
747
|
+
* The line a `useFilterMode` filter draws: `/ term▏ 3 matches`. The caret
|
|
748
|
+
* is there only while typing, which is the one visible difference between a
|
|
749
|
+
* field that takes your keys and a filter that is merely standing — so it is
|
|
750
|
+
* the thing that tells you whether `c` is a letter or a command.
|
|
751
|
+
*
|
|
752
|
+
* Presentational and keyless. It sits in the `Page`'s `filter` slot, band two,
|
|
753
|
+
* so every app draws its filter in the same place.
|
|
754
|
+
*/
|
|
755
|
+
declare const FilterBar: ({ term, typing, caret, matches }: FilterBarProps) => React__default.JSX.Element | null;
|
|
756
|
+
|
|
757
|
+
type SwitchValue = "left" | "right";
|
|
758
|
+
type SwitchProps = {
|
|
759
|
+
left: string;
|
|
760
|
+
right: string;
|
|
761
|
+
value: SwitchValue;
|
|
762
|
+
color?: string;
|
|
763
|
+
};
|
|
764
|
+
declare const Switch: ({ left, right, value, color, }: SwitchProps) => React__default.JSX.Element;
|
|
765
|
+
|
|
766
|
+
type ToggleProps = {
|
|
767
|
+
label: string;
|
|
768
|
+
on: boolean;
|
|
769
|
+
color?: string;
|
|
770
|
+
};
|
|
771
|
+
declare const Toggle: ({ label, on, color }: ToggleProps) => React__default.JSX.Element;
|
|
772
|
+
|
|
773
|
+
type ToggleSwitchProps = {
|
|
774
|
+
on: boolean;
|
|
775
|
+
label?: string;
|
|
776
|
+
onColor?: string;
|
|
777
|
+
offColor?: string;
|
|
778
|
+
};
|
|
779
|
+
declare const ToggleSwitch: ({ on, label, onColor, offColor, }: ToggleSwitchProps) => React__default.JSX.Element;
|
|
780
|
+
|
|
781
|
+
type UpdateBannerProps = {
|
|
782
|
+
name: string;
|
|
783
|
+
current: string;
|
|
784
|
+
latest: string;
|
|
785
|
+
command: string;
|
|
786
|
+
onConfirm: () => void;
|
|
787
|
+
onDecline: () => void;
|
|
788
|
+
focused?: boolean;
|
|
789
|
+
};
|
|
790
|
+
declare const UpdateBanner: ({ name, current, latest, command, onConfirm, onDecline, focused, }: UpdateBannerProps) => React__default.JSX.Element;
|
|
791
|
+
|
|
792
|
+
type ProgressBarProps = {
|
|
793
|
+
value: number;
|
|
794
|
+
width?: number;
|
|
795
|
+
color?: string;
|
|
796
|
+
trackColor?: string;
|
|
797
|
+
};
|
|
798
|
+
declare const ProgressBar: ({ value, width, color, trackColor, }: ProgressBarProps) => React__default.JSX.Element;
|
|
799
|
+
|
|
800
|
+
type SkeletonBarProps = {
|
|
801
|
+
/** Cells to draw. */
|
|
802
|
+
width: number;
|
|
803
|
+
/**
|
|
804
|
+
* Settle for the plain track bar. Defaults to true; still draws static when
|
|
805
|
+
* stdout is not a TTY or motion is opted out, whatever is passed.
|
|
806
|
+
*/
|
|
807
|
+
animate?: boolean;
|
|
808
|
+
/**
|
|
809
|
+
* A frame on the shared clock, passed by `SkeletonRows` so every row ticks
|
|
810
|
+
* together. Omit it and the bar keeps its own clock — that is the standalone
|
|
811
|
+
* behaviour.
|
|
812
|
+
*/
|
|
813
|
+
frame?: number;
|
|
814
|
+
/**
|
|
815
|
+
* Stagger within a shared clock, in frames. `SkeletonRows` passes each row's
|
|
816
|
+
* lag; a standalone bar leaves it at 0.
|
|
817
|
+
*/
|
|
818
|
+
rowOffset?: number;
|
|
819
|
+
};
|
|
820
|
+
/**
|
|
821
|
+
* A placeholder for text that has not arrived: a run of `█` over the track
|
|
822
|
+
* ground, swept left to right by a soft highlight band that loops after a
|
|
823
|
+
* short pause.
|
|
824
|
+
*
|
|
825
|
+
* Animated on purpose — amended 2026-10-05. This used to be static, on the
|
|
826
|
+
* grounds that a shimmer is motion with nothing behind it and the status slot
|
|
827
|
+
* is where a page says it is busy. In practice a first load with nothing on
|
|
828
|
+
* screen reads as frozen without motion: the status slot says busy, but there
|
|
829
|
+
* is no body yet for it to reassure. Still draws the plain static bar when
|
|
830
|
+
* there is nowhere for motion to show — stdout piped, `NO_MOTION` or
|
|
831
|
+
* `REDUCE_MOTION` set — or when `animate` is false.
|
|
832
|
+
*/
|
|
833
|
+
declare const SkeletonBar: ({ width, animate, frame, rowOffset, }: SkeletonBarProps) => React__default.JSX.Element;
|
|
834
|
+
type SkeletonRowsProps = {
|
|
835
|
+
/** How many bars to draw. Blank gap rows are extra. */
|
|
836
|
+
rows: number;
|
|
837
|
+
/**
|
|
838
|
+
* Each bar's length as a fraction (0–1) of the width left after `indent`,
|
|
839
|
+
* cycled in order: row `i` takes `widths[i % widths.length]`. Deterministic,
|
|
840
|
+
* so the placeholder does not reshuffle on every render.
|
|
841
|
+
*/
|
|
842
|
+
widths: number[];
|
|
843
|
+
/** Insert one blank row after every `gapEvery` bars, never after the last. */
|
|
844
|
+
gapEvery?: number;
|
|
845
|
+
/** Leading blank cells before every bar. Defaults to 0. */
|
|
846
|
+
indent?: number;
|
|
847
|
+
/**
|
|
848
|
+
* The width the fractions are taken of. Omit it and the rows measure the
|
|
849
|
+
* space their container gives them, re-measuring when the terminal resizes.
|
|
850
|
+
*/
|
|
851
|
+
width?: number;
|
|
852
|
+
/**
|
|
853
|
+
* Settle every row for the plain track bar. Passed through to each
|
|
854
|
+
* `SkeletonBar`; defaults to true.
|
|
855
|
+
*/
|
|
856
|
+
animate?: boolean;
|
|
857
|
+
};
|
|
858
|
+
/**
|
|
859
|
+
* The body of a list that has nothing to show yet: `rows` skeleton bars whose
|
|
860
|
+
* lengths cycle through `widths`, grouped by blank rows when `gapEvery` is set.
|
|
861
|
+
* Only for the first load, when nothing is known — once real rows exist, keep
|
|
862
|
+
* them on screen and say busy in `Page`'s status slot instead.
|
|
863
|
+
*
|
|
864
|
+
* One clock for the group: a single interval here hands each bar its frame,
|
|
865
|
+
* staggered by row so the wave travels diagonally down-and-across rather than
|
|
866
|
+
* marching in lockstep.
|
|
867
|
+
*/
|
|
868
|
+
declare const SkeletonRows: ({ rows, widths, gapEvery, indent, width, animate, }: SkeletonRowsProps) => React__default.JSX.Element;
|
|
869
|
+
|
|
870
|
+
type StatusVariant = "success" | "error" | "warning" | "info";
|
|
871
|
+
|
|
872
|
+
type StatusMessageProps = {
|
|
873
|
+
variant?: StatusVariant;
|
|
874
|
+
children: React__default.ReactNode;
|
|
875
|
+
};
|
|
876
|
+
declare const StatusMessage: ({ variant, children, }: StatusMessageProps) => React__default.JSX.Element;
|
|
877
|
+
|
|
878
|
+
type AlertProps = {
|
|
879
|
+
variant?: StatusVariant;
|
|
880
|
+
title?: string;
|
|
881
|
+
children: React__default.ReactNode;
|
|
882
|
+
};
|
|
883
|
+
declare const Alert: ({ variant, title, children }: AlertProps) => React__default.JSX.Element;
|
|
884
|
+
|
|
885
|
+
type ToastProps = {
|
|
886
|
+
message: string;
|
|
887
|
+
variant?: StatusVariant;
|
|
888
|
+
duration?: number;
|
|
889
|
+
onDone?: () => void;
|
|
890
|
+
};
|
|
891
|
+
declare const Toast: ({ message, variant, duration, onDone, }: ToastProps) => React__default.JSX.Element | null;
|
|
892
|
+
|
|
893
|
+
type IconMode = "text" | "nerd";
|
|
894
|
+
declare const setIconMode: (mode: IconMode) => void;
|
|
895
|
+
declare const getIconMode: () => IconMode;
|
|
896
|
+
|
|
897
|
+
type NotifyOptions = {
|
|
898
|
+
title?: string;
|
|
899
|
+
subtitle?: string;
|
|
900
|
+
sound?: boolean | string;
|
|
901
|
+
};
|
|
902
|
+
declare const notify: (message: string, options?: NotifyOptions) => void;
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* The raw hexes behind every token, tuned to the dark ground `#1a1b27`.
|
|
906
|
+
* Dark-only and truecolour assumed; on a 256-colour terminal chalk downsamples
|
|
907
|
+
* them (`tokens.test.ts` checks the pairs that must stay apart). Hex rather
|
|
908
|
+
* than ANSI names, because the terminal theme repaints a named colour.
|
|
909
|
+
*/
|
|
910
|
+
declare const palette: {
|
|
911
|
+
readonly orange: "#FF8C00";
|
|
912
|
+
readonly amber: "#FFB400";
|
|
913
|
+
readonly yellow: "#FFCB6B";
|
|
914
|
+
readonly red: "#FF5364";
|
|
915
|
+
readonly mint: "#5FD7A7";
|
|
916
|
+
readonly green: "#4ADE80";
|
|
917
|
+
readonly coral: "#FF6B6B";
|
|
918
|
+
readonly blue: "#82AAFF";
|
|
919
|
+
readonly cyan: "#89DDFF";
|
|
920
|
+
readonly purple: "#C792EA";
|
|
921
|
+
readonly ink: "#E4E4EC";
|
|
922
|
+
readonly grey: "#999999";
|
|
923
|
+
readonly slate: "#75767E";
|
|
924
|
+
readonly dusk: "#5C5E73";
|
|
925
|
+
readonly selection: "#30364A";
|
|
926
|
+
readonly track: "#2B323D";
|
|
927
|
+
readonly trackHigh: "#6B7480";
|
|
928
|
+
readonly ground: "#1A1B27";
|
|
929
|
+
};
|
|
930
|
+
type Palette = (typeof palette)[keyof typeof palette];
|
|
931
|
+
/**
|
|
932
|
+
* `secondary` is the third neutral: text that is context rather than the
|
|
933
|
+
* answer — a row's labels beside its title — and must read as its own tier.
|
|
934
|
+
* Default foreground is the answer, `muted` (measured #75767E) is furniture,
|
|
935
|
+
* and a middle tier drawn in either of those is not "quiet", it is absent: on
|
|
936
|
+
* a dark ground `dimColor` inks at L* 50 and the eye files it with the age
|
|
937
|
+
* column. Hex rather than an ANSI name because the tier only exists as a
|
|
938
|
+
* measured step — `#999999` is L* 63, 5.9:1 on `#1c1c28` and 13 L* above the
|
|
939
|
+
* faint, which reads as a decision; `#888888` is 7 L* above it and reads as a
|
|
940
|
+
* rendering wobble. Tuned to the dark ground every consumer already commits
|
|
941
|
+
* to; a light-theme terminal would need its own value.
|
|
942
|
+
*
|
|
943
|
+
* `track` is the unlit ground of a measured strip: the empty part of a
|
|
944
|
+
* `ProgressBar`, and the base of a `SkeletonBar` standing in for text that
|
|
945
|
+
* has not arrived. Furniture, never a state — kept clearly darker than any
|
|
946
|
+
* fill drawn over it, so the two separate by lightness for every reader.
|
|
947
|
+
*
|
|
948
|
+
* `trackHighlight` is the crest of that ground: the lighter step a skeleton's
|
|
949
|
+
* shimmer band peaks at while it sweeps, with the falloff each side blended
|
|
950
|
+
* between the two. Still ground, never ink — nothing is ever written in it,
|
|
951
|
+
* which is why it can sit well above `track` without becoming a state.
|
|
952
|
+
*/
|
|
953
|
+
declare const colors: {
|
|
954
|
+
readonly accent: "#FF8C00";
|
|
955
|
+
readonly secondary: "#999999";
|
|
956
|
+
readonly muted: "#75767E";
|
|
957
|
+
readonly success: "#5FD7A7";
|
|
958
|
+
readonly error: "#FF5364";
|
|
959
|
+
readonly warning: "#FFB400";
|
|
960
|
+
readonly info: "#89DDFF";
|
|
961
|
+
readonly group: "#C792EA";
|
|
962
|
+
readonly track: "#2B323D";
|
|
963
|
+
readonly trackHighlight: "#6B7480";
|
|
964
|
+
readonly ticket: "#FFCB6B";
|
|
965
|
+
readonly pr: "#FF8C00";
|
|
966
|
+
readonly link: "#82AAFF";
|
|
967
|
+
readonly added: "#4ADE80";
|
|
968
|
+
readonly removed: "#FF6B6B";
|
|
969
|
+
readonly pending: "#75767E";
|
|
970
|
+
readonly dim: "#5C5E73";
|
|
971
|
+
readonly selection: "#30364A";
|
|
972
|
+
};
|
|
973
|
+
type Color = (typeof colors)[keyof typeof colors];
|
|
974
|
+
/**
|
|
975
|
+
* The soft family: a filled block that is quiet enough to live on every row.
|
|
976
|
+
*
|
|
977
|
+
* Solid `colors` are events and shout on purpose. A permanent classifier —
|
|
978
|
+
* an issue type, a category — sits on a dark ground beside the row's real
|
|
979
|
+
* news, and a saturated fill there out-shouts it. These are the same
|
|
980
|
+
* semantics, desaturated and darkened so white ink reads on all seven,
|
|
981
|
+
* measured against `#1e1f26` for a deutan/protan reader. Hex rather than
|
|
982
|
+
* ANSI names because the point is a fixed, measured contrast, and a named
|
|
983
|
+
* colour is whatever the theme says it is.
|
|
984
|
+
*
|
|
985
|
+
* `group` is the container of the rows beneath it (an epic). It is
|
|
986
|
+
* deliberately not the accent hue: an accent-filled block on the same row
|
|
987
|
+
* as an accent-coloured key steals the key's colour.
|
|
988
|
+
*/
|
|
989
|
+
declare const softColors: {
|
|
990
|
+
readonly accent: "#8f4611";
|
|
991
|
+
readonly muted: "#474a54";
|
|
992
|
+
readonly success: "#2e6d4c";
|
|
993
|
+
readonly error: "#7e2a2c";
|
|
994
|
+
readonly warning: "#8a6a1e";
|
|
995
|
+
readonly info: "#3c74a2";
|
|
996
|
+
readonly group: "#524389";
|
|
997
|
+
};
|
|
998
|
+
type SoftColor = (typeof softColors)[keyof typeof softColors];
|
|
999
|
+
/**
|
|
1000
|
+
* The tonal family: a tint of the hue over the ground, inked in the hue — the
|
|
1001
|
+
* quietest filled form, for chrome on the frame (a count, a slot label)
|
|
1002
|
+
* rather than row data.
|
|
1003
|
+
*
|
|
1004
|
+
* Each fill is a linear blend of the variant's solid hue over `#1A1B27` at a
|
|
1005
|
+
* fixed weight (16% rest, 28% strong), computed once and written in as a
|
|
1006
|
+
* literal — no runtime mixing. The accent and muted pairs are frozen
|
|
1007
|
+
* measurements rather than exact mixes: the focus pill is about 16% orange,
|
|
1008
|
+
* the active tab chip about 28%, and the inactive tab chip a step above the
|
|
1009
|
+
* ground that never dims into it. Measured like `softColors`, for the same
|
|
1010
|
+
* reason: a tint is a fixed contrast, and a named colour is whatever the
|
|
1011
|
+
* theme says it is.
|
|
1012
|
+
*
|
|
1013
|
+
* `tonalStrongColors` is the thing you are ON — the active tab's chip — a
|
|
1014
|
+
* stronger tint of the same hue with the same ink, plus a bold label at the
|
|
1015
|
+
* call site. The keys are the seven `Pill` variant names, written out rather
|
|
1016
|
+
* than imported: tokens never reach back into components.
|
|
1017
|
+
*/
|
|
1018
|
+
declare const tonalColors: Record<"success" | "error" | "warning" | "info" | "accent" | "muted" | "group", {
|
|
1019
|
+
fill: string;
|
|
1020
|
+
ink: string;
|
|
1021
|
+
}>;
|
|
1022
|
+
declare const tonalStrongColors: Record<"success" | "error" | "warning" | "info" | "accent" | "muted" | "group", {
|
|
1023
|
+
fill: string;
|
|
1024
|
+
ink: string;
|
|
1025
|
+
}>;
|
|
1026
|
+
/**
|
|
1027
|
+
* Priority is ordinal, so it steps in lightness, never hue: a red chevron
|
|
1028
|
+
* would claim a failure. The chevron's shape carries the rank.
|
|
1029
|
+
*/
|
|
1030
|
+
declare const priorityColors: {
|
|
1031
|
+
readonly highest: "#E4E4EC";
|
|
1032
|
+
readonly high: "#E4E4EC";
|
|
1033
|
+
readonly medium: "#999999";
|
|
1034
|
+
readonly low: "#75767E";
|
|
1035
|
+
readonly lowest: "#75767E";
|
|
1036
|
+
};
|
|
1037
|
+
type PriorityColor = (typeof priorityColors)[keyof typeof priorityColors];
|
|
1038
|
+
declare const spacing: {
|
|
1039
|
+
readonly xs: 1;
|
|
1040
|
+
readonly sm: 2;
|
|
1041
|
+
readonly md: 3;
|
|
1042
|
+
readonly lg: 4;
|
|
1043
|
+
};
|
|
1044
|
+
type Spacing = (typeof spacing)[keyof typeof spacing];
|
|
1045
|
+
/**
|
|
1046
|
+
* Semantic terminal glyphs, inspired by the token categories in rigerc/nosey.
|
|
1047
|
+
* The default glyphs match ink-ui's existing StatusMessage and Select
|
|
1048
|
+
* conventions. Shape carries meaning; color only reinforces it.
|
|
1049
|
+
*/
|
|
1050
|
+
type Symbols = {
|
|
1051
|
+
/** Filled state marker used by switches, toggles and focused panels. */
|
|
1052
|
+
active: string;
|
|
1053
|
+
/** Nested square markers for lists. */
|
|
1054
|
+
nestedBullet: string;
|
|
1055
|
+
nestedBulletEmpty: string;
|
|
1056
|
+
/** Dotted horizontal rule for page headers. */
|
|
1057
|
+
rule: string;
|
|
1058
|
+
/** Terminal input caret. */
|
|
1059
|
+
caret: string;
|
|
1060
|
+
/** Vertical group separator. */
|
|
1061
|
+
verticalLine: string;
|
|
1062
|
+
success: string;
|
|
1063
|
+
error: string;
|
|
1064
|
+
warn: string;
|
|
1065
|
+
info: string;
|
|
1066
|
+
pointer: string;
|
|
1067
|
+
checkboxChecked: string;
|
|
1068
|
+
checkboxUnchecked: string;
|
|
1069
|
+
bullet: string;
|
|
1070
|
+
pending: string;
|
|
1071
|
+
ellipsis: string;
|
|
1072
|
+
line: string;
|
|
1073
|
+
barFull: string;
|
|
1074
|
+
barEmpty: string;
|
|
1075
|
+
};
|
|
1076
|
+
declare const symbols: Symbols;
|
|
1077
|
+
declare const asciiSymbols: Symbols;
|
|
1078
|
+
/** Border presets are style names accepted by Ink's Box borderStyle prop. */
|
|
1079
|
+
type BorderStyle = "round" | "single" | "bold" | "double" | "classic";
|
|
1080
|
+
declare const borders: {
|
|
1081
|
+
readonly base: "round";
|
|
1082
|
+
readonly focus: "bold";
|
|
1083
|
+
readonly ascii: "classic";
|
|
1084
|
+
};
|
|
1085
|
+
/** Motion and fallback frames are visual tokens, not animation state. */
|
|
1086
|
+
declare const motion: {
|
|
1087
|
+
readonly spinner: readonly ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
1088
|
+
readonly asciiSpinner: readonly ["-", "\\", "|", "/"];
|
|
1089
|
+
readonly intervalMs: 80;
|
|
1090
|
+
/** Skeleton shimmer has a faster independent rhythm. */
|
|
1091
|
+
readonly skeletonIntervalMs: 70;
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
export { Alert, Badge, type BadgeVariant, Banner, type BorderStyle, type Color, type Column, type ColumnAlign, type ColumnOverflow, Columns, CommandPalette, ConfirmInput, EmailInput, FilterBar, FooterHints, Header, type Hint, type IconMode, KeyValue, LoadingScreen, MultiSelect, type NotifyOptions, OrderedList, PAGE_CHROME, Page, type PageStatus, type Palette, type PaletteItem, type PaletteMessage, Panel, PasswordInput, Pill, type PillTone, type PillVariant, type PriorityColor, ProgressBar, ScrollView, Select, type SelectOption, SelectableRow, SkeletonBar, SkeletonRows, type SoftColor, type Spacing, type Span, Spinner, StatusMessage, type StatusVariant, Step, type StepContext, type StepProps, Stepper, StepperNavigation, StepperProgress, type StepperProgressProps, type StepperProps, type StyledLine, Switch, type SwitchValue, type Symbols, type TabItem, Table, Tabs, TextInput, Toast, Toggle, ToggleSwitch, UnorderedList, UpdateBanner, asciiSymbols, borders, colors, fuzzyFilter, getIconMode, motion, notify, palette, pillWidth, priorityColors, setIconMode, softColors, spacing, symbols, tailHints, tonalColors, tonalStrongColors, useAppKeys, useFilterMode, useListCursor, useStepper, useTabs };
|