@kud/ink-ui 0.7.0 → 0.9.0
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/README.md +36 -2
- package/dist/index.d.ts +36 -3
- package/dist/index.js +76 -20
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -13,11 +13,18 @@
|
|
|
13
13
|
|
|
14
14
|
## Features
|
|
15
15
|
|
|
16
|
-
- **
|
|
16
|
+
- **25 ready-made components**, pre-styled and ready to drop in:
|
|
17
|
+
- **Inputs** — `TextInput`, `EmailInput` (domain completion), `PasswordInput` (masked), `ConfirmInput`
|
|
18
|
+
- **Lists** — `UnorderedList`, `OrderedList` (both nestable), `Table`
|
|
19
|
+
- **Selection & navigation** — `Select`, `MultiSelect`, `Tabs`, `Switch`, `Toggle`, `SelectableRow`
|
|
20
|
+
- **Status & feedback** — `Spinner`, `ProgressBar`, `StatusMessage`, `Alert`, `Badge`, `Toast`
|
|
21
|
+
- **Layout & chrome** — `Banner`, `Header`, `FooterHints`, `KeyValue`, `LoadingScreen`, `ScrollView`
|
|
22
|
+
- **Full [@inkjs/ui](https://github.com/vadimdemedes/ink-ui) parity** — every upstream component has an equivalent, plus a dozen more the design system adds on top
|
|
23
|
+
- **Colourblind-safe by design** — state is signalled by shape, case, and glyph, never colour alone
|
|
17
24
|
- **Design tokens included** — a shared colour palette (`colors`) and spacing scale (`spacing`) to keep every screen consistent
|
|
18
25
|
- **Full TypeScript support** — ships compiled output with `.d.ts` declarations for every component and token type
|
|
19
26
|
- **ESM only, zero config** — no runtime bundling step; just import and render
|
|
20
|
-
- **Peer-dependency light** — only requires `ink ≥
|
|
27
|
+
- **Peer-dependency light** — only requires `ink ≥ 7` and `react ≥ 19`
|
|
21
28
|
|
|
22
29
|
## Install
|
|
23
30
|
|
|
@@ -50,6 +57,33 @@ const App = () => (
|
|
|
50
57
|
render(<App />)
|
|
51
58
|
```
|
|
52
59
|
|
|
60
|
+
Inputs report their value through `onChange`/`onSubmit`, and lists nest with a shape-distinct marker per level:
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { Text } from "ink"
|
|
64
|
+
import { EmailInput, PasswordInput, UnorderedList } from "@kud/ink-ui"
|
|
65
|
+
|
|
66
|
+
const SignUp = () => (
|
|
67
|
+
<>
|
|
68
|
+
<EmailInput placeholder="you@example.com" onSubmit={setEmail} />
|
|
69
|
+
<PasswordInput placeholder="Password" onSubmit={setPassword} />
|
|
70
|
+
<UnorderedList>
|
|
71
|
+
<UnorderedList.Item>
|
|
72
|
+
<Text>Choose a plan</Text>
|
|
73
|
+
<UnorderedList>
|
|
74
|
+
<UnorderedList.Item>
|
|
75
|
+
<Text>Free</Text>
|
|
76
|
+
</UnorderedList.Item>
|
|
77
|
+
<UnorderedList.Item>
|
|
78
|
+
<Text>Pro</Text>
|
|
79
|
+
</UnorderedList.Item>
|
|
80
|
+
</UnorderedList>
|
|
81
|
+
</UnorderedList.Item>
|
|
82
|
+
</UnorderedList>
|
|
83
|
+
</>
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
53
87
|
All components accept only the props they need — no theme provider or context required. Design tokens are plain objects:
|
|
54
88
|
|
|
55
89
|
```ts
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
2
|
|
|
3
3
|
type BannerProps = {
|
|
4
4
|
title: string;
|
|
@@ -54,6 +54,38 @@ type LoadingScreenProps = {
|
|
|
54
54
|
};
|
|
55
55
|
declare const LoadingScreen: ({ label }: LoadingScreenProps) => React.JSX.Element;
|
|
56
56
|
|
|
57
|
+
type ScreenProps = {
|
|
58
|
+
children: ReactNode;
|
|
59
|
+
/**
|
|
60
|
+
* Enter the terminal's alternate buffer so the app owns the whole screen and
|
|
61
|
+
* the previous scrollback is restored on exit. Defaults to true. Set false to
|
|
62
|
+
* render full-height inline instead.
|
|
63
|
+
*/
|
|
64
|
+
altScreen?: boolean;
|
|
65
|
+
};
|
|
66
|
+
declare const Screen: ({ children, altScreen }: ScreenProps) => React.JSX.Element;
|
|
67
|
+
|
|
68
|
+
type PanelProps = {
|
|
69
|
+
/** Optional title shown in the panel's header row. */
|
|
70
|
+
title?: string;
|
|
71
|
+
/** Accent colour for the border + title when focused. Defaults to the info token. */
|
|
72
|
+
color?: string;
|
|
73
|
+
/** When true the border brightens to `color` and the title gains a ● marker. */
|
|
74
|
+
focused?: boolean;
|
|
75
|
+
width?: number;
|
|
76
|
+
height?: number;
|
|
77
|
+
flexGrow?: number;
|
|
78
|
+
children: ReactNode;
|
|
79
|
+
};
|
|
80
|
+
declare const Panel: ({ title, color, focused, width, height, flexGrow, children, }: PanelProps) => React.JSX.Element;
|
|
81
|
+
|
|
82
|
+
type ColumnsProps = {
|
|
83
|
+
/** Horizontal gap between columns (character cells). Defaults to 1. */
|
|
84
|
+
gap?: number;
|
|
85
|
+
children: ReactNode;
|
|
86
|
+
};
|
|
87
|
+
declare const Columns: ({ gap, children }: ColumnsProps) => React.JSX.Element;
|
|
88
|
+
|
|
57
89
|
type SelectableRowProps = {
|
|
58
90
|
active?: boolean;
|
|
59
91
|
marker?: string;
|
|
@@ -160,8 +192,9 @@ type ScrollViewProps = {
|
|
|
160
192
|
lines: StyledLine[];
|
|
161
193
|
showIndicator?: boolean;
|
|
162
194
|
initialStart?: number;
|
|
195
|
+
isActive?: boolean;
|
|
163
196
|
};
|
|
164
|
-
declare const ScrollView: ({ lines, showIndicator, initialStart, }: ScrollViewProps) => React.JSX.Element;
|
|
197
|
+
declare const ScrollView: ({ lines, showIndicator, initialStart, isActive, }: ScrollViewProps) => React.JSX.Element;
|
|
165
198
|
|
|
166
199
|
type TabItem<T extends string = string> = {
|
|
167
200
|
value: T;
|
|
@@ -247,4 +280,4 @@ declare const spacing: {
|
|
|
247
280
|
};
|
|
248
281
|
type Spacing = (typeof spacing)[keyof typeof spacing];
|
|
249
282
|
|
|
250
|
-
export { Alert, Badge, type BadgeVariant, Banner, type Color, type Column, ConfirmInput, EmailInput, FooterHints, Header, type Hint, type IconMode, KeyValue, LoadingScreen, MultiSelect, type NotifyOptions, OrderedList, PasswordInput, ProgressBar, ScrollView, Select, type SelectOption, SelectableRow, type Spacing, type Span, Spinner, StatusMessage, type StatusVariant, type StyledLine, Switch, type SwitchValue, type TabItem, Table, Tabs, TextInput, Toast, Toggle, UnorderedList, colors, getIconMode, notify, setIconMode, spacing };
|
|
283
|
+
export { Alert, Badge, type BadgeVariant, Banner, type Color, type Column, Columns, ConfirmInput, EmailInput, FooterHints, Header, type Hint, type IconMode, KeyValue, LoadingScreen, MultiSelect, type NotifyOptions, OrderedList, Panel, PasswordInput, ProgressBar, Screen, ScrollView, Select, type SelectOption, SelectableRow, type Spacing, type Span, Spinner, StatusMessage, type StatusVariant, type StyledLine, Switch, type SwitchValue, type TabItem, Table, Tabs, TextInput, Toast, Toggle, UnorderedList, colors, getIconMode, notify, setIconMode, spacing };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Box, Text, useInput, measureElement } from 'ink';
|
|
1
|
+
import { Box, Text, useStdout, useInput, measureElement } from 'ink';
|
|
2
2
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
-
import
|
|
3
|
+
import React9, { createContext, useContext, useState, useEffect, useRef, useLayoutEffect } from 'react';
|
|
4
4
|
import cliSpinners from 'cli-spinners';
|
|
5
5
|
import { glyphs } from '@kud/glyphs';
|
|
6
6
|
import { spawn } from 'child_process';
|
|
@@ -73,6 +73,55 @@ var KeyValue = ({ label, value, labelWidth = 12 }) => /* @__PURE__ */ jsxs(Box,
|
|
|
73
73
|
isTextValue(value) ? /* @__PURE__ */ jsx(Text, { children: value }) : value
|
|
74
74
|
] });
|
|
75
75
|
var LoadingScreen = ({ label }) => /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Spinner, { label }) });
|
|
76
|
+
var ENTER_ALT = "\x1B[?1049h";
|
|
77
|
+
var LEAVE_ALT = "\x1B[?1049l";
|
|
78
|
+
var Screen = ({ children, altScreen = true }) => {
|
|
79
|
+
const { stdout } = useStdout();
|
|
80
|
+
const [size, setSize] = useState({
|
|
81
|
+
columns: stdout.columns || 80,
|
|
82
|
+
rows: stdout.rows || 24
|
|
83
|
+
});
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
const restore = () => {
|
|
86
|
+
if (altScreen) stdout.write(LEAVE_ALT);
|
|
87
|
+
};
|
|
88
|
+
if (altScreen) stdout.write(ENTER_ALT);
|
|
89
|
+
const onResize = () => setSize({ columns: stdout.columns || 80, rows: stdout.rows || 24 });
|
|
90
|
+
stdout.on("resize", onResize);
|
|
91
|
+
process.on("exit", restore);
|
|
92
|
+
return () => {
|
|
93
|
+
stdout.off("resize", onResize);
|
|
94
|
+
process.off("exit", restore);
|
|
95
|
+
restore();
|
|
96
|
+
};
|
|
97
|
+
}, [stdout, altScreen]);
|
|
98
|
+
return /* @__PURE__ */ jsx(Box, { width: size.columns, height: size.rows, flexDirection: "column", children });
|
|
99
|
+
};
|
|
100
|
+
var Panel = ({
|
|
101
|
+
title,
|
|
102
|
+
color = colors.info,
|
|
103
|
+
focused = false,
|
|
104
|
+
width,
|
|
105
|
+
height,
|
|
106
|
+
flexGrow,
|
|
107
|
+
children
|
|
108
|
+
}) => /* @__PURE__ */ jsxs(
|
|
109
|
+
Box,
|
|
110
|
+
{
|
|
111
|
+
flexDirection: "column",
|
|
112
|
+
width,
|
|
113
|
+
height,
|
|
114
|
+
flexGrow,
|
|
115
|
+
borderStyle: "round",
|
|
116
|
+
borderColor: focused ? color : colors.muted,
|
|
117
|
+
overflow: "hidden",
|
|
118
|
+
children: [
|
|
119
|
+
title && /* @__PURE__ */ jsx(Text, { color: focused ? color : colors.muted, bold: true, children: focused ? `${title} \u25CF` : title }),
|
|
120
|
+
children
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
var Columns = ({ gap = 1, children }) => /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap, flexGrow: 1, children });
|
|
76
125
|
var isTextValue2 = (value) => typeof value === "string" || typeof value === "number";
|
|
77
126
|
var SelectableRow = ({
|
|
78
127
|
active = false,
|
|
@@ -115,7 +164,10 @@ var Select = ({
|
|
|
115
164
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
116
165
|
visible.map((option, i) => {
|
|
117
166
|
const active = start + i === cursor;
|
|
118
|
-
return /* @__PURE__ */
|
|
167
|
+
return /* @__PURE__ */ jsxs(SelectableRow, { active, children: [
|
|
168
|
+
/* @__PURE__ */ jsx(Text, { bold: active, color: active ? colors.info : void 0, children: option.label }),
|
|
169
|
+
active ? /* @__PURE__ */ jsx(Text, { color: colors.success, children: " \u2713" }) : null
|
|
170
|
+
] }, option.value);
|
|
119
171
|
}),
|
|
120
172
|
options.length > count && /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2026 ${options.length} total` })
|
|
121
173
|
] });
|
|
@@ -159,8 +211,8 @@ var MultiSelect = ({
|
|
|
159
211
|
const active = start + i === cursor;
|
|
160
212
|
const on = selected.has(option.value);
|
|
161
213
|
return /* @__PURE__ */ jsxs(SelectableRow, { active, children: [
|
|
162
|
-
/* @__PURE__ */ jsx(Text, { color:
|
|
163
|
-
/* @__PURE__ */ jsx(Text, {
|
|
214
|
+
/* @__PURE__ */ jsx(Text, { bold: active, color: active ? colors.info : void 0, children: option.label }),
|
|
215
|
+
on ? /* @__PURE__ */ jsx(Text, { color: colors.success, children: " \u2713" }) : null
|
|
164
216
|
] }, option.value);
|
|
165
217
|
}),
|
|
166
218
|
options.length > count && /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2026 ${options.length} total` })
|
|
@@ -420,10 +472,10 @@ var OrderedListItem = ({ children, _index = 1 }) => /* @__PURE__ */ jsxs(Box, {
|
|
|
420
472
|
] });
|
|
421
473
|
var OrderedListRoot = ({ children }) => {
|
|
422
474
|
let n = 0;
|
|
423
|
-
const numbered =
|
|
424
|
-
if (
|
|
475
|
+
const numbered = React9.Children.map(children, (child) => {
|
|
476
|
+
if (React9.isValidElement(child) && child.type === OrderedListItem) {
|
|
425
477
|
n += 1;
|
|
426
|
-
return
|
|
478
|
+
return React9.cloneElement(child, {
|
|
427
479
|
_index: n
|
|
428
480
|
});
|
|
429
481
|
}
|
|
@@ -437,7 +489,8 @@ var OrderedList = Object.assign(OrderedListRoot, {
|
|
|
437
489
|
var ScrollView = ({
|
|
438
490
|
lines,
|
|
439
491
|
showIndicator = true,
|
|
440
|
-
initialStart = 0
|
|
492
|
+
initialStart = 0,
|
|
493
|
+
isActive = true
|
|
441
494
|
}) => {
|
|
442
495
|
const ref = useRef(null);
|
|
443
496
|
const [height, setHeight] = useState(1);
|
|
@@ -450,16 +503,19 @@ var ScrollView = ({
|
|
|
450
503
|
});
|
|
451
504
|
const maxStart = Math.max(0, lines.length - height);
|
|
452
505
|
const clamped = Math.min(start, maxStart);
|
|
453
|
-
useInput(
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
506
|
+
useInput(
|
|
507
|
+
(input, key) => {
|
|
508
|
+
if (key.downArrow || input === "j")
|
|
509
|
+
setStart((s) => Math.min(maxStart, s + 1));
|
|
510
|
+
if (key.upArrow || input === "k") setStart((s) => Math.max(0, s - 1));
|
|
511
|
+
if (input === " " || key.pageDown || input === "f")
|
|
512
|
+
setStart((s) => Math.min(maxStart, s + height));
|
|
513
|
+
if (key.pageUp || input === "b") setStart((s) => Math.max(0, s - height));
|
|
514
|
+
if (input === "g") setStart(0);
|
|
515
|
+
if (input === "G") setStart(maxStart);
|
|
516
|
+
},
|
|
517
|
+
{ isActive }
|
|
518
|
+
);
|
|
463
519
|
const visible = lines.slice(clamped, clamped + height);
|
|
464
520
|
const hasMore = lines.length > height;
|
|
465
521
|
const atEnd = clamped >= maxStart;
|
|
@@ -660,4 +716,4 @@ var notify = (message, options = {}) => {
|
|
|
660
716
|
child.unref();
|
|
661
717
|
};
|
|
662
718
|
|
|
663
|
-
export { Alert, Badge, Banner, ConfirmInput, EmailInput, FooterHints, Header, KeyValue, LoadingScreen, MultiSelect, OrderedList, PasswordInput, ProgressBar, ScrollView, Select, SelectableRow, Spinner, StatusMessage, Switch, Table, Tabs, TextInput, Toast, Toggle, UnorderedList, colors, getIconMode, notify, setIconMode, spacing };
|
|
719
|
+
export { Alert, Badge, Banner, Columns, ConfirmInput, EmailInput, FooterHints, Header, KeyValue, LoadingScreen, MultiSelect, OrderedList, Panel, PasswordInput, ProgressBar, Screen, ScrollView, Select, SelectableRow, Spinner, StatusMessage, Switch, Table, Tabs, TextInput, Toast, Toggle, UnorderedList, colors, getIconMode, notify, setIconMode, spacing };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kud/ink-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "React component library for Ink CLIs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "tsup",
|
|
22
22
|
"dev": "tsup --watch",
|
|
23
|
+
"demo": "tsx src/demo/index.tsx",
|
|
23
24
|
"typecheck": "tsc --noEmit",
|
|
24
25
|
"test": "vitest run",
|
|
25
26
|
"test:watch": "vitest"
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"react": "19.2.7",
|
|
42
43
|
"react-devtools-core": "7.0.1",
|
|
43
44
|
"tsup": "8.5.1",
|
|
45
|
+
"tsx": "4.23.1",
|
|
44
46
|
"typescript": "5.9.3",
|
|
45
47
|
"vitest": "4.1.10"
|
|
46
48
|
},
|