@kud/ink-ui 0.16.0 → 0.18.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/AGENTS.md +9 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +43 -2
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -13,7 +13,7 @@ because it tells you a component is missing when it is not.
|
|
|
13
13
|
|
|
14
14
|
**Before writing any component, check it isn't already here.** Bordered panes,
|
|
15
15
|
scrolling viewports, selectable rows, tables, tab bars, spinners, progress
|
|
16
|
-
bars, key/value pairs, badges and footer key hints are all provided. A
|
|
16
|
+
bars, key/value pairs, badges, pills and footer key hints are all provided. A
|
|
17
17
|
hand-rolled version of one of these is the single most common mistake in a
|
|
18
18
|
consuming repo.
|
|
19
19
|
|
|
@@ -56,6 +56,7 @@ differently on every screen.
|
|
|
56
56
|
| Pick one / pick many from a list | `Select` / `MultiSelect` — these own their keyboard, unlike `SelectableRow` |
|
|
57
57
|
| A persistent key-hints footer | `FooterHints` with `Hint` tuples: `[["↑↓", "move"], ["q", "quit"]]` |
|
|
58
58
|
| Label/value detail rows | `KeyValue` with a shared `labelWidth` so values align |
|
|
59
|
+
| A category label — `epic`, `draft` — that should read as one object | `Pill`, filled and rounded; `Badge` for the bracket form. Never for a reference the reader follows |
|
|
59
60
|
| Transient feedback | `StatusMessage` (inline) · `Alert` (boxed, with title) · `Toast` (self-dismissing) |
|
|
60
61
|
| App chrome | `Banner` at the top, `Header` per section, `LoadingScreen` while booting |
|
|
61
62
|
|
|
@@ -78,6 +79,13 @@ import { colors, spacing } from "@kud/ink-ui"
|
|
|
78
79
|
A literal like `color="orange"` or `color="#FF8C00"` is wrong even when it
|
|
79
80
|
renders identically — it breaks the moment a token moves.
|
|
80
81
|
|
|
82
|
+
The one exception is `<Pill color>`, for a surface mirroring an external
|
|
83
|
+
system whose colours ARE its vocabulary — GitHub's merged purple, a CI
|
|
84
|
+
provider's result colours. The test is whether the hue is a **fact about the
|
|
85
|
+
thing being labelled**; a colour picked because it looks right is still a token
|
|
86
|
+
job. The pill inks itself against whatever fill it is given, so a caller reaching
|
|
87
|
+
for this never picks a foreground.
|
|
88
|
+
|
|
81
89
|
**State is never signalled by colour alone.** Every status carries a shape, a
|
|
82
90
|
glyph or a weight as well, because a colourblind reader cannot see the hue and
|
|
83
91
|
a piped terminal has no colour at all. `SelectableRow` marks the active row
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,56 @@ type BadgeProps = {
|
|
|
15
15
|
};
|
|
16
16
|
declare const Badge: ({ children, variant }: BadgeProps) => React__default.JSX.Element;
|
|
17
17
|
|
|
18
|
+
type PillVariant = "success" | "error" | "warning" | "info" | "accent" | "muted";
|
|
19
|
+
type PillProps = {
|
|
20
|
+
children: string;
|
|
21
|
+
variant?: PillVariant;
|
|
22
|
+
/**
|
|
23
|
+
* An explicit fill, for a caller that owns a palette of its own — one
|
|
24
|
+
* mirroring an external system whose colours ARE the vocabulary (GitHub's
|
|
25
|
+
* merged purple, a CI provider's result colours). Overrides `variant`.
|
|
26
|
+
*
|
|
27
|
+
* Not a way round the token rule. A colour picked because it looks nice is
|
|
28
|
+
* still wrong here; the test is whether the hue is a fact about the thing
|
|
29
|
+
* being labelled rather than a preference about the label.
|
|
30
|
+
*/
|
|
31
|
+
color?: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* A filled, rounded label — a category the thing belongs to, or an event that
|
|
35
|
+
* has just happened to it.
|
|
36
|
+
*
|
|
37
|
+
* Reach for it when the word IS the information (`epic`, `draft`, `blocked`,
|
|
38
|
+
* `NEW`) and you want it to read as one object rather than as more prose. For a
|
|
39
|
+
* reference the reader is meant to follow — a ticket key, a repo — leave the
|
|
40
|
+
* text dim: a fill gives a breadcrumb a weight it has not earned, and once
|
|
41
|
+
* everything is a pill none of them is.
|
|
42
|
+
*
|
|
43
|
+
* The WORD carries the meaning and the colour only reinforces it, so a pill
|
|
44
|
+
* survives being read in monochrome, piped, or by someone who cannot separate
|
|
45
|
+
* the hues.
|
|
46
|
+
*
|
|
47
|
+
* Powerline half-circles, like `ToggleSwitch` and for the same reason: they are
|
|
48
|
+
* the only way to get a genuinely rounded end out of a terminal cell, and a font
|
|
49
|
+
* without them renders blanks, which degrades to a square pill rather than
|
|
50
|
+
* breaking the layout. Not gated on `getIconMode` — that switch chooses between
|
|
51
|
+
* two glyph SETS, and this is one glyph with a graceful absence.
|
|
52
|
+
*
|
|
53
|
+
* `NO_COLOR` is the one case that does fall back to brackets: with the fill
|
|
54
|
+
* stripped, the caps would be drawing the outline of a pill that is not there.
|
|
55
|
+
*/
|
|
56
|
+
declare const Pill: ({ children, variant, color }: PillProps) => React__default.JSX.Element;
|
|
57
|
+
/**
|
|
58
|
+
* How many columns `<Pill>` occupies for `text` — the label plus its two caps.
|
|
59
|
+
*
|
|
60
|
+
* Exported because a caller laying out a fixed-width row has to price the pill
|
|
61
|
+
* BEFORE rendering it, and measuring the rendered output is not available at
|
|
62
|
+
* that point. A row that budgets for the label alone overflows by exactly two
|
|
63
|
+
* columns, which in a frame sized to fill the terminal scrolls the whole panel
|
|
64
|
+
* instead of clipping.
|
|
65
|
+
*/
|
|
66
|
+
declare const pillWidth: (text: string) => number;
|
|
67
|
+
|
|
18
68
|
type HeaderProps = {
|
|
19
69
|
children: string;
|
|
20
70
|
subtitle?: string;
|
|
@@ -321,4 +371,4 @@ declare const spacing: {
|
|
|
321
371
|
};
|
|
322
372
|
type Spacing = (typeof spacing)[keyof typeof spacing];
|
|
323
373
|
|
|
324
|
-
export { Alert, Badge, type BadgeVariant, Banner, type Color, type Column, type ColumnAlign, type ColumnOverflow, Columns, ConfirmInput, EmailInput, FooterHints, Header, type Hint, type IconMode, KeyValue, LoadingScreen, MultiSelect, type NotifyOptions, OrderedList, Panel, 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, ToggleSwitch, UnorderedList, UpdateBanner, colors, getIconMode, notify, setIconMode, spacing, useListCursor, useTabs };
|
|
374
|
+
export { Alert, Badge, type BadgeVariant, Banner, type Color, type Column, type ColumnAlign, type ColumnOverflow, Columns, ConfirmInput, EmailInput, FooterHints, Header, type Hint, type IconMode, KeyValue, LoadingScreen, MultiSelect, type NotifyOptions, OrderedList, Panel, PasswordInput, Pill, type PillVariant, 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, ToggleSwitch, UnorderedList, UpdateBanner, colors, getIconMode, notify, pillWidth, setIconMode, spacing, useListCursor, useTabs };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Box, Text, useStdout, useInput, measureElement } from 'ink';
|
|
2
2
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { glyphs } from '@kud/glyphs';
|
|
3
4
|
import React8, { createContext, useContext, useState, useEffect, useRef, useLayoutEffect } from 'react';
|
|
4
5
|
import cliSpinners from 'cli-spinners';
|
|
5
6
|
import stringWidth from 'string-width';
|
|
6
|
-
import { glyphs } from '@kud/glyphs';
|
|
7
7
|
import { spawn } from 'child_process';
|
|
8
8
|
|
|
9
9
|
// src/components/Banner.tsx
|
|
@@ -39,6 +39,47 @@ var Badge = ({ children, variant = "info" }) => /* @__PURE__ */ jsxs(Text, { col
|
|
|
39
39
|
children,
|
|
40
40
|
"]"
|
|
41
41
|
] });
|
|
42
|
+
var FILL = {
|
|
43
|
+
success: colors.success,
|
|
44
|
+
error: colors.error,
|
|
45
|
+
warning: colors.warning,
|
|
46
|
+
info: colors.info,
|
|
47
|
+
accent: colors.accent,
|
|
48
|
+
muted: colors.muted
|
|
49
|
+
};
|
|
50
|
+
var INK = {
|
|
51
|
+
success: "black",
|
|
52
|
+
error: "white",
|
|
53
|
+
warning: "black",
|
|
54
|
+
info: "black",
|
|
55
|
+
accent: "black",
|
|
56
|
+
muted: "white"
|
|
57
|
+
};
|
|
58
|
+
var inkFor = (fill) => {
|
|
59
|
+
const hex = /^#([0-9a-f]{6})$/i.exec(fill)?.[1];
|
|
60
|
+
if (!hex) return "white";
|
|
61
|
+
const channel = (i) => {
|
|
62
|
+
const c = parseInt(hex.slice(i, i + 2), 16) / 255;
|
|
63
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
64
|
+
};
|
|
65
|
+
const luminance = 0.2126 * channel(0) + 0.7152 * channel(2) + 0.0722 * channel(4);
|
|
66
|
+
return (luminance + 0.05) / 0.05 >= 1.05 / (luminance + 0.05) ? "black" : "white";
|
|
67
|
+
};
|
|
68
|
+
var Pill = ({ children, variant = "muted", color }) => {
|
|
69
|
+
const fill = color ?? FILL[variant];
|
|
70
|
+
const ink = color ? inkFor(color) : INK[variant];
|
|
71
|
+
if (process.env["NO_COLOR"]) return /* @__PURE__ */ jsxs(Text, { color: fill, children: [
|
|
72
|
+
"[",
|
|
73
|
+
children,
|
|
74
|
+
"]"
|
|
75
|
+
] });
|
|
76
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
77
|
+
/* @__PURE__ */ jsx(Text, { color: fill, children: glyphs.plCapLeft }),
|
|
78
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor: fill, color: ink, children }),
|
|
79
|
+
/* @__PURE__ */ jsx(Text, { color: fill, children: glyphs.plCapRight })
|
|
80
|
+
] });
|
|
81
|
+
};
|
|
82
|
+
var pillWidth = (text) => text.length + 2;
|
|
42
83
|
var Header = ({ children, subtitle }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
43
84
|
/* @__PURE__ */ jsx(Text, { bold: true, underline: true, children }),
|
|
44
85
|
subtitle && /* @__PURE__ */ jsx(Text, { dimColor: true, children: subtitle })
|
|
@@ -893,4 +934,4 @@ var notify = (message, options = {}) => {
|
|
|
893
934
|
child.unref();
|
|
894
935
|
};
|
|
895
936
|
|
|
896
|
-
export { Alert, Badge, Banner, Columns, ConfirmInput, EmailInput, FooterHints, Header, KeyValue, LoadingScreen, MultiSelect, OrderedList, Panel, PasswordInput, ProgressBar, ScrollView, Select, SelectableRow, Spinner, StatusMessage, Switch, Table, Tabs, TextInput, Toast, Toggle, ToggleSwitch, UnorderedList, UpdateBanner, colors, getIconMode, notify, setIconMode, spacing, useListCursor, useTabs };
|
|
937
|
+
export { Alert, Badge, Banner, Columns, ConfirmInput, EmailInput, FooterHints, Header, KeyValue, LoadingScreen, MultiSelect, OrderedList, Panel, PasswordInput, Pill, ProgressBar, ScrollView, Select, SelectableRow, Spinner, StatusMessage, Switch, Table, Tabs, TextInput, Toast, Toggle, ToggleSwitch, UnorderedList, UpdateBanner, colors, getIconMode, notify, pillWidth, setIconMode, spacing, useListCursor, useTabs };
|