@textui/widgets 0.6.1 → 0.8.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/dist/chart/line-chart.d.ts.map +1 -1
- package/dist/chart/line-chart.js +3 -2
- package/dist/control/select.d.ts.map +1 -1
- package/dist/control/select.js +4 -2
- package/dist/control/switch.d.ts.map +1 -1
- package/dist/control/switch.js +7 -2
- package/dist/control/text-area.d.ts +2 -0
- package/dist/control/text-area.d.ts.map +1 -1
- package/dist/control/text-area.js +16 -8
- package/dist/data/code-viewer.d.ts +2 -0
- package/dist/data/code-viewer.d.ts.map +1 -1
- package/dist/data/code-viewer.js +9 -1
- package/dist/data/index.d.ts +9 -0
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +11 -4
- package/dist/data/list.d.ts +9 -0
- package/dist/data/list.d.ts.map +1 -1
- package/dist/data/list.js +23 -5
- package/dist/data/log-viewer.d.ts.map +1 -1
- package/dist/data/log-viewer.js +7 -2
- package/dist/data/markdown-view.d.ts.map +1 -1
- package/dist/data/markdown-view.js +6 -2
- package/dist/data/pagination.d.ts.map +1 -1
- package/dist/data/pagination.js +6 -2
- package/dist/data/tree.d.ts +2 -0
- package/dist/data/tree.d.ts.map +1 -1
- package/dist/data/tree.js +8 -2
- package/dist/display/card.d.ts.map +1 -1
- package/dist/display/card.js +12 -0
- package/dist/display/color-text.d.ts +13 -1
- package/dist/display/color-text.d.ts.map +1 -1
- package/dist/display/color-text.js +28 -6
- package/dist/display/error-state.d.ts.map +1 -1
- package/dist/display/error-state.js +4 -3
- package/dist/display/font-text.d.ts +72 -0
- package/dist/display/font-text.d.ts.map +1 -0
- package/dist/display/font-text.js +35 -0
- package/dist/display/fonts.d.ts +138 -0
- package/dist/display/fonts.d.ts.map +1 -0
- package/dist/display/fonts.js +893 -0
- package/dist/display/gard.d.ts +26 -0
- package/dist/display/gard.d.ts.map +1 -0
- package/dist/display/gard.js +479 -0
- package/dist/display/index.d.ts +3 -0
- package/dist/display/index.d.ts.map +1 -1
- package/dist/display/index.js +7 -0
- package/dist/display/key-value.d.ts +25 -5
- package/dist/display/key-value.d.ts.map +1 -1
- package/dist/display/key-value.js +13 -3
- package/dist/display/pagga.d.ts +29 -0
- package/dist/display/pagga.d.ts.map +1 -0
- package/dist/display/pagga.js +101 -0
- package/dist/display/pattern.d.ts +117 -0
- package/dist/display/pattern.d.ts.map +1 -0
- package/dist/display/pattern.js +257 -0
- package/dist/display/spinner.d.ts +2 -0
- package/dist/display/spinner.d.ts.map +1 -1
- package/dist/display/spinner.js +3 -2
- package/dist/form/danger-zone.d.ts.map +1 -1
- package/dist/form/danger-zone.js +7 -3
- package/dist/form/form-actions.d.ts.map +1 -1
- package/dist/form/form-actions.js +3 -2
- package/dist/navigation/menu.d.ts +2 -0
- package/dist/navigation/menu.d.ts.map +1 -1
- package/dist/navigation/menu.js +22 -8
- package/dist/navigation/tabs.d.ts +2 -0
- package/dist/navigation/tabs.d.ts.map +1 -1
- package/dist/navigation/tabs.js +12 -2
- package/dist/overlay/command-palette.d.ts.map +1 -1
- package/dist/overlay/command-palette.js +55 -15
- package/dist/overlay/dialog.d.ts +11 -0
- package/dist/overlay/dialog.d.ts.map +1 -1
- package/dist/overlay/dialog.js +24 -1
- package/dist/overlay/index.d.ts.map +1 -1
- package/dist/overlay/index.js +11 -19
- package/dist/overlay/path-picker.d.ts.map +1 -1
- package/dist/overlay/path-picker.js +19 -11
- package/dist/overlay/prompt-dialog.d.ts.map +1 -1
- package/dist/overlay/prompt-dialog.js +5 -4
- package/dist/panel/index.d.ts.map +1 -1
- package/dist/panel/index.js +8 -7
- package/dist/panel/resource-panel.d.ts.map +1 -1
- package/dist/panel/resource-panel.js +9 -5
- package/dist/surface/mount-view.d.ts.map +1 -1
- package/dist/surface/mount-view.js +3 -2
- package/package.json +2 -2
- package/src/chart/line-chart.ts +3 -2
- package/src/control/select.ts +4 -2
- package/src/control/switch.ts +9 -2
- package/src/control/text-area.ts +18 -7
- package/src/data/code-viewer.ts +10 -1
- package/src/data/index.ts +12 -3
- package/src/data/list.ts +24 -4
- package/src/data/log-viewer.ts +7 -1
- package/src/data/markdown-view.ts +10 -1
- package/src/data/pagination.ts +6 -2
- package/src/data/tree.ts +9 -2
- package/src/display/card.ts +12 -0
- package/src/display/color-text.ts +33 -10
- package/src/display/error-state.ts +4 -3
- package/src/display/font-text.tsx +94 -0
- package/src/display/fonts.ts +990 -0
- package/src/display/gard.ts +475 -0
- package/src/display/index.ts +7 -0
- package/src/display/key-value.ts +44 -6
- package/src/display/pagga.ts +102 -0
- package/src/display/pattern.tsx +427 -0
- package/src/display/spinner.ts +5 -2
- package/src/form/danger-zone.ts +7 -3
- package/src/form/form-actions.ts +5 -2
- package/src/navigation/menu.ts +23 -8
- package/src/navigation/tabs.ts +13 -2
- package/src/overlay/command-palette.ts +66 -14
- package/src/overlay/dialog.ts +37 -1
- package/src/overlay/index.ts +11 -19
- package/src/overlay/path-picker.ts +23 -10
- package/src/overlay/prompt-dialog.ts +5 -4
- package/src/panel/index.ts +9 -7
- package/src/panel/resource-panel.ts +9 -4
- package/src/surface/mount-view.ts +3 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineComponent, escapeSegment, h, useEffect, useFocusScope, useRef, useRuntime, useStoreValue, useTask, } from '@textui/core';
|
|
1
|
+
import { defineComponent, escapeSegment, h, useEffect, useFocusScope, useI18n, useRef, useRuntime, useStoreValue, useTask, } from '@textui/core';
|
|
2
2
|
import { EmptyState, ErrorState, Spinner } from '../display/index.js';
|
|
3
3
|
import { LOOSE, PanelContext } from './shared.js';
|
|
4
4
|
/** Every panel's remembered state. */
|
|
@@ -67,6 +67,7 @@ export function defaultRenderer(renderers) {
|
|
|
67
67
|
*/
|
|
68
68
|
export const ResourcePanel = defineComponent('ResourcePanel', (props) => {
|
|
69
69
|
const runtime = useRuntime();
|
|
70
|
+
const i18n = useI18n();
|
|
70
71
|
const { id: named, uri, renderer, mode, rendererProps, fallbackComponent, autoFocus, emptyTitle, ...rest } = props;
|
|
71
72
|
const app = runtime.app();
|
|
72
73
|
const scope = useFocusScope({
|
|
@@ -178,20 +179,23 @@ export const ResourcePanel = defineComponent('ResourcePanel', (props) => {
|
|
|
178
179
|
runtime.store.set(panelClaimPath(id), 0);
|
|
179
180
|
}, [id, claim, chosen?.id ?? null]);
|
|
180
181
|
if (!uri)
|
|
181
|
-
return h(EmptyState, { title: emptyTitle ?? 'Nothing selected', ...rest });
|
|
182
|
+
return h(EmptyState, { title: emptyTitle ?? i18n.t('textui.resourcePanel.empty', undefined, 'Nothing selected'), ...rest });
|
|
182
183
|
if (!app)
|
|
183
184
|
return null;
|
|
184
185
|
if (task.status === 'running' || task.status === 'idle') {
|
|
185
|
-
return h(Spinner, { label: 'Loading…', ...rest });
|
|
186
|
+
return h(Spinner, { label: i18n.t('textui.resourcePanel.loading', undefined, 'Loading…'), ...rest });
|
|
186
187
|
}
|
|
187
188
|
if (task.status === 'error')
|
|
188
189
|
return h(ErrorState, { error: task.error, ...rest });
|
|
189
190
|
if (!resource)
|
|
190
|
-
return h(EmptyState, { title: 'Not found', message: uri, ...rest });
|
|
191
|
+
return h(EmptyState, { title: i18n.t('textui.resourcePanel.notFound', undefined, 'Not found'), message: uri, ...rest });
|
|
191
192
|
if (!chosen) {
|
|
192
193
|
return h('box', { direction: 'column', flex: 1, id: scope, ...rest }, fallbackComponent
|
|
193
194
|
? { component: fallbackComponent, resource, uri }
|
|
194
|
-
: h(EmptyState, {
|
|
195
|
+
: h(EmptyState, {
|
|
196
|
+
title: i18n.t('textui.resourcePanel.noRenderer', undefined, 'No renderer'),
|
|
197
|
+
message: resource.kind,
|
|
198
|
+
}));
|
|
195
199
|
}
|
|
196
200
|
const node = {
|
|
197
201
|
component: chosen.component,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount-view.d.ts","sourceRoot":"","sources":["../../src/surface/mount-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,QAAQ,EAAiB,KAAK,EAAE,MAAM,cAAc,CAAC;AAIhF,MAAM,WAAW,cAAe,SAAQ,QAAQ;IAC9C,KAAK,EAAE,KAAK,CAAC;CACd;AAED,+EAA+E;AAC/E,eAAO,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"mount-view.d.ts","sourceRoot":"","sources":["../../src/surface/mount-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,QAAQ,EAAiB,KAAK,EAAE,MAAM,cAAc,CAAC;AAIhF,MAAM,WAAW,cAAe,SAAQ,QAAQ;IAC9C,KAAK,EAAE,KAAK,CAAC;CACd;AAED,+EAA+E;AAC/E,eAAO,MAAM,SAAS,gEAqBpB,CAAC"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { defineComponent, h, useRuntime } from '@textui/core';
|
|
1
|
+
import { defineComponent, h, useI18n, useRuntime } from '@textui/core';
|
|
2
2
|
import { EmptyState } from '../display/index.js';
|
|
3
3
|
/** Renders one mount's target - an inline node, or a node read from a path. */
|
|
4
4
|
export const MountView = defineComponent('MountView', ({ mount, ...rest }) => {
|
|
5
5
|
const runtime = useRuntime();
|
|
6
|
+
const i18n = useI18n();
|
|
6
7
|
const target = mount.target;
|
|
7
8
|
const node = 'component' in target
|
|
8
9
|
? target
|
|
9
10
|
: (runtime.store.get(target.path) ?? null);
|
|
10
11
|
if (!node) {
|
|
11
|
-
return h(EmptyState, { title: 'Nothing mounted', ...rest });
|
|
12
|
+
return h(EmptyState, { title: i18n.t('textui.mountView.empty', undefined, 'Nothing mounted'), ...rest });
|
|
12
13
|
}
|
|
13
14
|
// A mount's data context makes relative paths inside it resolve against the
|
|
14
15
|
// record it was opened for.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@textui/widgets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "The TextUI component catalog - layout, display, controls, data, overlays and charts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"terminal",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"test": "vitest run"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@textui/core": "^0.
|
|
49
|
+
"@textui/core": "^0.8.0"
|
|
50
50
|
},
|
|
51
51
|
"publishConfig": {
|
|
52
52
|
"access": "public"
|
package/src/chart/line-chart.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PaintSurface, RenderContext } from '@textui/core';
|
|
2
|
-
import { defineComponent, fitTo, h, stringWidth, useTheme } from '@textui/core';
|
|
2
|
+
import { defineComponent, fitTo, h, stringWidth, useI18n, useTheme } from '@textui/core';
|
|
3
3
|
import type { LineChartProps } from './shared.js';
|
|
4
4
|
import { TONE, bounds, resample } from './shared.js';
|
|
5
5
|
|
|
@@ -9,6 +9,7 @@ import { TONE, bounds, resample } from './shared.js';
|
|
|
9
9
|
*/
|
|
10
10
|
export const LineChart = defineComponent<LineChartProps>('LineChart', (props) => {
|
|
11
11
|
const theme = useTheme();
|
|
12
|
+
const i18n = useI18n();
|
|
12
13
|
const {
|
|
13
14
|
series, min, max, chartWidth, chartHeight = 8,
|
|
14
15
|
axis = true, area = false, format, ...rest
|
|
@@ -83,7 +84,7 @@ export const LineChart = defineComponent<LineChartProps>('LineChart', (props) =>
|
|
|
83
84
|
...series.map((s, i) =>
|
|
84
85
|
h('box', { key: i, direction: 'row', gap: 1 },
|
|
85
86
|
h('text', { content: theme.glyphs.bulletFilled, fg: TONE[s.tone ?? (i === 0 ? 'accent' : 'secondary')] }),
|
|
86
|
-
h('text', { content: s.label ??
|
|
87
|
+
h('text', { content: s.label ?? i18n.t('textui.lineChart.series', { n: i + 1 }, 'series {n}'), fg: 'muted' }))))
|
|
87
88
|
: null,
|
|
88
89
|
);
|
|
89
90
|
});
|
package/src/control/select.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BoxProps, ComponentNode, RenderOutput } from '@textui/core';
|
|
2
2
|
import {
|
|
3
|
-
defineComponent, h, useApp, useEffect, useFocus, useInput, useMeasure, useState, useTheme,
|
|
3
|
+
defineComponent, h, useApp, useEffect, useFocus, useI18n, useInput, useMeasure, useState, useTheme,
|
|
4
4
|
} from '@textui/core';
|
|
5
5
|
|
|
6
6
|
export interface SelectOption {
|
|
@@ -46,6 +46,7 @@ export interface SelectProps extends BoxProps {
|
|
|
46
46
|
|
|
47
47
|
export const Select = defineComponent<SelectProps>('Select', (props) => {
|
|
48
48
|
const theme = useTheme();
|
|
49
|
+
const i18n = useI18n();
|
|
49
50
|
const {
|
|
50
51
|
options, value, onChange, label, placeholder,
|
|
51
52
|
open: openProp, visibleRows = 6, disabled, mode = 'inline', ...rest
|
|
@@ -54,7 +55,8 @@ export const Select = defineComponent<SelectProps>('Select', (props) => {
|
|
|
54
55
|
const app = useApp();
|
|
55
56
|
const focus = useFocus({ disabled });
|
|
56
57
|
const measured = useMeasure();
|
|
57
|
-
const empty = placeholder
|
|
58
|
+
const empty = placeholder
|
|
59
|
+
?? i18n.t('textui.select.placeholder', { ellipsis: theme.glyphs.ellipsis }, 'Select{ellipsis}');
|
|
58
60
|
const [open, setOpen] = useState(openProp ?? false);
|
|
59
61
|
const index = Math.max(0, options.findIndex((o) => o.value === value));
|
|
60
62
|
const [highlight, setHighlight] = useState(index);
|
package/src/control/switch.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BoxProps } from '@textui/core';
|
|
2
|
-
import { defineComponent, h, useFocus, useInput } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, useFocus, useI18n, useInput } from '@textui/core';
|
|
3
3
|
|
|
4
4
|
export interface SwitchProps extends BoxProps {
|
|
5
5
|
label?: string;
|
|
@@ -10,7 +10,14 @@ export interface SwitchProps extends BoxProps {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export const Switch = defineComponent<SwitchProps>('Switch', (props) => {
|
|
13
|
-
const
|
|
13
|
+
const i18n = useI18n();
|
|
14
|
+
const {
|
|
15
|
+
label, value = false, onChange, disabled, ...rest
|
|
16
|
+
} = props;
|
|
17
|
+
const labels = props.labels ?? [
|
|
18
|
+
i18n.t('textui.switch.off', undefined, 'off'),
|
|
19
|
+
i18n.t('textui.switch.on', undefined, 'on'),
|
|
20
|
+
];
|
|
14
21
|
const focus = useFocus({ disabled });
|
|
15
22
|
|
|
16
23
|
useInput(
|
package/src/control/text-area.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { BoxProps, KeyEvent, MouseEvent, SemanticVariant, Style } from '@textui/core';
|
|
2
2
|
import {
|
|
3
|
+
NO_INTERACTION,
|
|
3
4
|
defineComponent,
|
|
4
5
|
graphemes,
|
|
5
6
|
h,
|
|
7
|
+
stateVariants,
|
|
6
8
|
stringWidth,
|
|
7
9
|
useClipboard,
|
|
8
10
|
useEffect,
|
|
@@ -122,6 +124,9 @@ export interface TextAreaProps extends BoxProps {
|
|
|
122
124
|
* is what typing is. That single fact is why an application with one of these
|
|
123
125
|
* cannot have single-letter global commands, and why it does not need to.
|
|
124
126
|
*/
|
|
127
|
+
/** The name a theme styles this field's selection under. See `LIST`. */
|
|
128
|
+
export const TEXT_AREA = 'TextArea';
|
|
129
|
+
|
|
125
130
|
export const TextArea = defineComponent<TextAreaProps>('TextArea', (props) => {
|
|
126
131
|
const {
|
|
127
132
|
value, onChange, onSubmit, onCancel, onOverflow, onEdge, placeholder, maxRows = 6,
|
|
@@ -222,14 +227,20 @@ export const TextArea = defineComponent<TextAreaProps>('TextArea', (props) => {
|
|
|
222
227
|
/**
|
|
223
228
|
* How a selected cell is drawn.
|
|
224
229
|
*
|
|
225
|
-
* The same pair a selected list row uses
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
230
|
+
* The theme's to say, and the same pair a selected list row uses: the
|
|
231
|
+
* `TextArea.focused` fill while the field has the keyboard and the dimmer
|
|
232
|
+
* `TextArea.selected` one once it does not, because a selection left
|
|
233
|
+
* visible in an unfocused field says what is on the clipboard, and saying it
|
|
234
|
+
* as loudly as the live one would put two selections on the screen.
|
|
235
|
+
*
|
|
236
|
+
* Asked for here rather than read off a node, because the field's selection
|
|
237
|
+
* is painted across cells that are not themselves selected nodes.
|
|
229
238
|
*/
|
|
230
|
-
const selectionStyle: Style =
|
|
231
|
-
|
|
232
|
-
:
|
|
239
|
+
const selectionStyle: Style = theme.styleFor(TEXT_AREA, stateVariants({
|
|
240
|
+
...NO_INTERACTION,
|
|
241
|
+
selected: true,
|
|
242
|
+
focused: focus.focused,
|
|
243
|
+
}));
|
|
233
244
|
|
|
234
245
|
const before = chars.slice(0, position).join('');
|
|
235
246
|
const lines = value === '' ? [''] : value.split('\n');
|
package/src/data/code-viewer.ts
CHANGED
|
@@ -92,6 +92,9 @@ export const HORIZONTAL_STEP = 4;
|
|
|
92
92
|
* around it. And it colours by asking the registry for a highlighter, so a new
|
|
93
93
|
* file type arrives coloured without this component learning what it is.
|
|
94
94
|
*/
|
|
95
|
+
/** The name a theme styles a code row under. See `LIST`. */
|
|
96
|
+
export const CODE_VIEWER = 'CodeViewer';
|
|
97
|
+
|
|
95
98
|
export const CodeViewer = defineComponent<CodeViewerProps>('CodeViewer', (props) => {
|
|
96
99
|
const {
|
|
97
100
|
content, lineNumbers = true, startLine = 1, visibleRows, highlight = [],
|
|
@@ -242,7 +245,13 @@ export const CodeViewer = defineComponent<CodeViewerProps>('CodeViewer', (props)
|
|
|
242
245
|
key: number,
|
|
243
246
|
direction: 'row',
|
|
244
247
|
height: 1,
|
|
245
|
-
|
|
248
|
+
styleAs: CODE_VIEWER,
|
|
249
|
+
// A marked line is a selection; the caret line is that selection with
|
|
250
|
+
// the keyboard on it. Both fills are the theme's - `CodeViewer.selected`
|
|
251
|
+
// and `CodeViewer.focused` - and both carry the colour that reads on
|
|
252
|
+
// the fill, so a token inside the row that states its own keeps it.
|
|
253
|
+
selected: marked,
|
|
254
|
+
focused: onCaret,
|
|
246
255
|
},
|
|
247
256
|
lineNumbers
|
|
248
257
|
? h('text', {
|
package/src/data/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
h,
|
|
7
7
|
stringWidth,
|
|
8
8
|
useFocus,
|
|
9
|
+
useI18n,
|
|
9
10
|
useInput,
|
|
10
11
|
useMeasure,
|
|
11
12
|
useState,
|
|
@@ -77,11 +78,15 @@ export interface TableProps<T extends object = Record<string, unknown>> extends
|
|
|
77
78
|
* table narrows it drops the lowest-priority columns rather than squeezing
|
|
78
79
|
* every column until none of them is readable.
|
|
79
80
|
*/
|
|
81
|
+
/** The name a theme styles a table row under. See `LIST` in `./list.js`. */
|
|
82
|
+
export const TABLE = 'Table';
|
|
83
|
+
|
|
80
84
|
const TableImpl = defineComponent<TableProps<Record<string, unknown>>>('Table', (props) => {
|
|
81
85
|
const theme = useTheme();
|
|
86
|
+
const i18n = useI18n();
|
|
82
87
|
const {
|
|
83
88
|
columns, rows, rowKey = 'id', selectedKey, onSelect, onActivate,
|
|
84
|
-
visibleRows, showHeader = true, emptyMessage = 'No rows',
|
|
89
|
+
visibleRows, showHeader = true, emptyMessage = i18n.t('textui.table.empty', undefined, 'No rows'),
|
|
85
90
|
focusable = true, responsive = true, width, ...rest
|
|
86
91
|
} = props;
|
|
87
92
|
|
|
@@ -204,11 +209,15 @@ const TableImpl = defineComponent<TableProps<Record<string, unknown>>>('Table',
|
|
|
204
209
|
return h('box', {
|
|
205
210
|
key,
|
|
206
211
|
role: 'row',
|
|
212
|
+
styleAs: TABLE,
|
|
207
213
|
selected: active,
|
|
214
|
+
// The keyboard is the table's; the row is told which row it lands on.
|
|
215
|
+
focused: active && focus.focused,
|
|
208
216
|
direction: 'row',
|
|
209
217
|
gap,
|
|
210
|
-
|
|
211
|
-
|
|
218
|
+
// What the row is filled with is the theme's to say - `Table.selected`
|
|
219
|
+
// for the current row and `Table.focused` over it while the table has
|
|
220
|
+
// the keyboard - as it is for a list, a tree and a menu.
|
|
212
221
|
onClick: () => {
|
|
213
222
|
if (selectedKey === undefined) setInternalKey(key);
|
|
214
223
|
onSelect?.(key, row);
|
package/src/data/list.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
defineComponent,
|
|
5
5
|
h,
|
|
6
6
|
useFocus,
|
|
7
|
+
useI18n,
|
|
7
8
|
useInput,
|
|
8
9
|
useMeasure,
|
|
9
10
|
useState,
|
|
@@ -24,6 +25,16 @@ export interface ListItem {
|
|
|
24
25
|
disabled?: boolean;
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* The name a theme styles a row under.
|
|
30
|
+
*
|
|
31
|
+
* A row is a plain `box`, so without this `components.List` in a theme is a
|
|
32
|
+
* key nothing reads. Stated once here because the row, the tree, the table
|
|
33
|
+
* and the menu all fill the same two colours and a theme author comparing
|
|
34
|
+
* them should be reading the same four names.
|
|
35
|
+
*/
|
|
36
|
+
export const LIST = 'List';
|
|
37
|
+
|
|
27
38
|
/** How a row stands at the moment it is asked to draw itself. */
|
|
28
39
|
export interface ListItemState {
|
|
29
40
|
/** The row the selection is on. */
|
|
@@ -98,9 +109,10 @@ export interface ListProps<T extends ListItem = ListItem> extends BoxProps {
|
|
|
98
109
|
|
|
99
110
|
function ListView<T extends ListItem>(props: ListProps<T>): RenderOutput {
|
|
100
111
|
const theme = useTheme();
|
|
112
|
+
const i18n = useI18n();
|
|
101
113
|
const {
|
|
102
114
|
items, selectedId, onSelect, onActivate, visibleRows,
|
|
103
|
-
emptyMessage = 'Nothing here', marker = true, focusable = true,
|
|
115
|
+
emptyMessage = i18n.t('textui.list.empty', undefined, 'Nothing here'), marker = true, focusable = true,
|
|
104
116
|
autoFocus, focusId, renderItem, itemHeight = 1, ...rest
|
|
105
117
|
} = props;
|
|
106
118
|
|
|
@@ -197,14 +209,22 @@ function ListView<T extends ListItem>(props: ListProps<T>): RenderOutput {
|
|
|
197
209
|
key: item.id,
|
|
198
210
|
role: 'listitem',
|
|
199
211
|
label: item.label,
|
|
212
|
+
styleAs: LIST,
|
|
200
213
|
selected: active,
|
|
214
|
+
// The keyboard is the list's, not the row's, so the row is told. It is
|
|
215
|
+
// the difference between "the current row" and "the current row you
|
|
216
|
+
// can type at", and a theme states the two fills separately.
|
|
217
|
+
focused: active && focus.focused,
|
|
201
218
|
direction: 'row',
|
|
202
219
|
gap: 1,
|
|
203
220
|
// One background over the whole row, however many lines it draws: a
|
|
204
221
|
// highlight that stopped after the first would split the row it is
|
|
205
|
-
// highlighting in two.
|
|
206
|
-
|
|
207
|
-
|
|
222
|
+
// highlighting in two. What it is filled with is the theme's to say -
|
|
223
|
+
// `List.selected` for the current row, `List.focused` over it while
|
|
224
|
+
// the list has the keyboard - and a background always comes with the
|
|
225
|
+
// colour that reads on it, which is why that pairing lives in the
|
|
226
|
+
// theme rather than here.
|
|
227
|
+
fg: item.disabled ? 'disabled' : undefined,
|
|
208
228
|
onClick: () => {
|
|
209
229
|
if (item.disabled) return;
|
|
210
230
|
if (selectedId === undefined) setInternalId(item.id);
|
package/src/data/log-viewer.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
defineComponent,
|
|
5
5
|
h,
|
|
6
6
|
useFocus,
|
|
7
|
+
useI18n,
|
|
7
8
|
useInput,
|
|
8
9
|
useMeasure,
|
|
9
10
|
useState,
|
|
@@ -45,6 +46,7 @@ export const LogViewer = defineComponent<LogViewerProps>('LogViewer', (props) =>
|
|
|
45
46
|
lines, visibleRows: visibleRowsProp, follow: followProp, showTime = true,
|
|
46
47
|
showLevel = true, onFollowChange, ...rest
|
|
47
48
|
} = props;
|
|
49
|
+
const i18n = useI18n();
|
|
48
50
|
|
|
49
51
|
const focus = useFocus({});
|
|
50
52
|
const measured = useMeasure();
|
|
@@ -99,7 +101,11 @@ export const LogViewer = defineComponent<LogViewerProps>('LogViewer', (props) =>
|
|
|
99
101
|
h('text', { content: line.message, flex: 1, truncate: 'end' }),
|
|
100
102
|
)),
|
|
101
103
|
!follow
|
|
102
|
-
? h('text', {
|
|
104
|
+
? h('text', {
|
|
105
|
+
content: ` ${i18n.t('textui.logViewer.paused', undefined, 'paused - end to follow')}`,
|
|
106
|
+
fg: 'warning',
|
|
107
|
+
dim: true,
|
|
108
|
+
})
|
|
103
109
|
: null,
|
|
104
110
|
);
|
|
105
111
|
});
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
h,
|
|
5
5
|
layoutMarkdown,
|
|
6
6
|
repeatToWidth,
|
|
7
|
+
useI18n,
|
|
7
8
|
useMeasure,
|
|
8
9
|
useMemo,
|
|
9
10
|
useTheme,
|
|
@@ -52,6 +53,7 @@ export const MarkdownView = defineComponent<MarkdownViewProps>('MarkdownView', (
|
|
|
52
53
|
// a document with no search over it carries no extra prop per run.
|
|
53
54
|
const hit = match ? { match } : {};
|
|
54
55
|
const theme = useTheme();
|
|
56
|
+
const i18n = useI18n();
|
|
55
57
|
const measured = useMeasure();
|
|
56
58
|
const width = measured.width > 0 ? measured.width : 0;
|
|
57
59
|
const ruled = theme.border !== 'none';
|
|
@@ -221,7 +223,14 @@ export const MarkdownView = defineComponent<MarkdownViewProps>('MarkdownView', (
|
|
|
221
223
|
return h('box', { role: 'document', direction: 'column', ...rest },
|
|
222
224
|
...out,
|
|
223
225
|
hidden > 0
|
|
224
|
-
? h('text', { content:
|
|
226
|
+
? h('text', { content: i18n.plural(
|
|
227
|
+
hidden,
|
|
228
|
+
{
|
|
229
|
+
one: i18n.t('textui.markdown.moreLines.one', undefined, '{glyph} {count} more line'),
|
|
230
|
+
other: i18n.t('textui.markdown.moreLines.other', undefined, '{glyph} {count} more lines'),
|
|
231
|
+
},
|
|
232
|
+
{ glyph: theme.glyphs.ellipsis },
|
|
233
|
+
), fg: 'subtle' })
|
|
225
234
|
: null,
|
|
226
235
|
);
|
|
227
236
|
});
|
package/src/data/pagination.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BoxProps } from '@textui/core';
|
|
2
|
-
import { defineComponent, h, useFocus, useInput, useTheme } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, useFocus, useI18n, useInput, useTheme } from '@textui/core';
|
|
3
3
|
|
|
4
4
|
export interface PaginationProps extends BoxProps {
|
|
5
5
|
page: number;
|
|
@@ -10,6 +10,7 @@ export interface PaginationProps extends BoxProps {
|
|
|
10
10
|
|
|
11
11
|
export const Pagination = defineComponent<PaginationProps>('Pagination', (props) => {
|
|
12
12
|
const theme = useTheme();
|
|
13
|
+
const i18n = useI18n();
|
|
13
14
|
const { page, pageCount, total, onChange, ...rest } = props;
|
|
14
15
|
const focus = useFocus({});
|
|
15
16
|
|
|
@@ -26,6 +27,9 @@ export const Pagination = defineComponent<PaginationProps>('Pagination', (props)
|
|
|
26
27
|
h('text', { content: theme.glyphs.chevronLeft, fg: page > 1 ? 'accent' : 'disabled' }),
|
|
27
28
|
h('text', { content: `${page} / ${pageCount}`, bold: focus.focused }),
|
|
28
29
|
h('text', { content: theme.glyphs.chevronRight, fg: page < pageCount ? 'accent' : 'disabled' }),
|
|
29
|
-
total !== undefined ? h('text', { content:
|
|
30
|
+
total !== undefined ? h('text', { content: i18n.plural(total, {
|
|
31
|
+
one: i18n.t('textui.pagination.total.one', undefined, '{count} item'),
|
|
32
|
+
other: i18n.t('textui.pagination.total.other', undefined, '{count} items'),
|
|
33
|
+
}), fg: 'muted' }) : null,
|
|
30
34
|
);
|
|
31
35
|
});
|
package/src/data/tree.ts
CHANGED
|
@@ -12,6 +12,9 @@ import { usePanelState } from '../panel/index.js';
|
|
|
12
12
|
import { TONE } from '../tone.js';
|
|
13
13
|
import { viewportRows } from '../viewport.js';
|
|
14
14
|
|
|
15
|
+
/** The name a theme styles a tree row under. See `LIST`. */
|
|
16
|
+
export const TREE = 'Tree';
|
|
17
|
+
|
|
15
18
|
export interface TreeNode {
|
|
16
19
|
id: string;
|
|
17
20
|
label: string;
|
|
@@ -187,11 +190,15 @@ export const Tree = defineComponent<TreeProps>('Tree', (props) => {
|
|
|
187
190
|
key: row.node.id,
|
|
188
191
|
role: 'treeitem',
|
|
189
192
|
label: row.node.label,
|
|
193
|
+
styleAs: TREE,
|
|
190
194
|
selected: active,
|
|
195
|
+
// The keyboard is the tree's; the row is told which row it lands on.
|
|
196
|
+
focused: active && focus.focused,
|
|
191
197
|
direction: 'row',
|
|
192
198
|
gap: 1,
|
|
193
|
-
|
|
194
|
-
|
|
199
|
+
// What the row is filled with is the theme's to say - `Tree.selected`
|
|
200
|
+
// for the current row and `Tree.focused` over it while the tree has
|
|
201
|
+
// the keyboard - as it is for a list, a table and a menu.
|
|
195
202
|
onClick: () => {
|
|
196
203
|
if (selectedId === undefined) setInternalSelected(row.node.id);
|
|
197
204
|
onSelect?.(row.node.id, row.node);
|
package/src/display/card.ts
CHANGED
|
@@ -12,6 +12,18 @@ export const Card = defineComponent<CardProps>('Card', (props) => {
|
|
|
12
12
|
const { title, subtitle, children, ...rest } = props;
|
|
13
13
|
return h('box', {
|
|
14
14
|
border: theme.border,
|
|
15
|
+
/*
|
|
16
|
+
* A card is a surface, so it states a background and is opaque.
|
|
17
|
+
*
|
|
18
|
+
* A bordered box that states none is a frame around whatever is already
|
|
19
|
+
* there, and what is already there can be a pattern, a scrim or a
|
|
20
|
+
* neighbouring block - a card dropped on a pattern showed the pattern
|
|
21
|
+
* through its own interior, which reads as a hole rather than as something
|
|
22
|
+
* laid on top. `Dialog` and `CommandPalette` have always stated `overlay`
|
|
23
|
+
* for the same reason; this is the page-level one. `...rest` still wins, so
|
|
24
|
+
* a caller that wants a different colour names it.
|
|
25
|
+
*/
|
|
26
|
+
bg: 'surface',
|
|
15
27
|
padding: theme.density === 'compact' ? 0 : [0, 1],
|
|
16
28
|
direction: 'column',
|
|
17
29
|
title,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
CellStyle, Color, InteractionState, PaintSurface,
|
|
2
|
+
CellStyle, Color, InteractionState, PaintSurface, Style, StyleInput,
|
|
3
3
|
RenderContext, StyleColor, TextProps, TextWrap,
|
|
4
4
|
} from '@textui/core';
|
|
5
5
|
import {
|
|
@@ -177,15 +177,9 @@ export const ColorText = defineComponent<ColorTextProps>('ColorText', (props) =>
|
|
|
177
177
|
|
|
178
178
|
// The component's own style, resolved for the state it is in. Not the
|
|
179
179
|
// inherited one - a canvas is not told what it was nested in.
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
);
|
|
184
|
-
const rest: CellStyle = {
|
|
185
|
-
fg: resolveColor(own.fg, ctx.theme, 'default'),
|
|
186
|
-
attrs: attrsFromStyle(own),
|
|
187
|
-
link: typeof props.link === 'string' ? props.link : undefined,
|
|
188
|
-
};
|
|
180
|
+
const ownProps = props as Record<string, unknown>;
|
|
181
|
+
const own = inkOwnStyle(ownProps, ctx);
|
|
182
|
+
const rest = inkCellStyle(own, ctx, props.link);
|
|
189
183
|
|
|
190
184
|
const cut = props.truncate ?? truncateSideOf(wrap) ?? 'end';
|
|
191
185
|
const dots = ellipsis ?? ctx.theme.glyphs.ellipsis;
|
|
@@ -256,11 +250,40 @@ export function blend(a: Color, b: Color, t: number): Color {
|
|
|
256
250
|
* Exported so a component that paints its own cells - a chart, a viewer with a
|
|
257
251
|
* heat column - can take an `Ink` and mean the same thing by it.
|
|
258
252
|
*/
|
|
253
|
+
/**
|
|
254
|
+
* A component's own style, resolved for the state it is in.
|
|
255
|
+
*
|
|
256
|
+
* A canvas is not told what it was nested in, so a component that paints its
|
|
257
|
+
* own cells cannot inherit the way a `text` does: it resolves the props it was
|
|
258
|
+
* given against the theme, and hands the result to every cell the ink declines.
|
|
259
|
+
* Exported beside `painterOf` so a second painter means the same thing by it -
|
|
260
|
+
* `ColorText` and `Pattern` both paint their own cells and read the same way.
|
|
261
|
+
*/
|
|
262
|
+
export function inkOwnStyle(props: Record<string, unknown>, ctx: RenderContext): Style {
|
|
263
|
+
return mergeStyles(
|
|
264
|
+
styleFromProps(props),
|
|
265
|
+
flattenStyleInput(props.style as StyleInput | undefined, stateOf(ctx)),
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** The cell style a canvas painter starts from, given that own style. */
|
|
270
|
+
export function inkCellStyle(own: Style, ctx: RenderContext, link?: unknown): CellStyle {
|
|
271
|
+
return {
|
|
272
|
+
fg: resolveColor(own.fg, ctx.theme, 'default'),
|
|
273
|
+
attrs: attrsFromStyle(own),
|
|
274
|
+
link: typeof link === 'string' ? link : undefined,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
259
278
|
export function painterOf(
|
|
260
279
|
ink: Ink | undefined,
|
|
261
280
|
ctx: RenderContext,
|
|
262
281
|
): (cell: InkCell) => CellStyle | undefined {
|
|
263
282
|
if (ink === undefined) return () => undefined;
|
|
283
|
+
// A theme with no colour has no inks, and this is not only faster: a ramp
|
|
284
|
+
// whose stops all resolve to the terminal's own still has to be *mixed*, and
|
|
285
|
+
// mixing "no colour" with itself lands on black - which is a colour.
|
|
286
|
+
if (ctx.theme.monochrome) return () => undefined;
|
|
264
287
|
|
|
265
288
|
if (typeof ink === 'function') {
|
|
266
289
|
return (cell) => styleOf(ink(cell, ctx), ctx);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BoxProps } from '@textui/core';
|
|
2
|
-
import { defineComponent, h, useTheme } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, useI18n, useTheme } from '@textui/core';
|
|
3
3
|
|
|
4
4
|
export interface ErrorStateProps extends BoxProps {
|
|
5
5
|
title?: string;
|
|
@@ -10,7 +10,8 @@ export interface ErrorStateProps extends BoxProps {
|
|
|
10
10
|
|
|
11
11
|
export const ErrorState = defineComponent<ErrorStateProps>('ErrorState', (props) => {
|
|
12
12
|
const theme = useTheme();
|
|
13
|
-
const
|
|
13
|
+
const i18n = useI18n();
|
|
14
|
+
const { title = i18n.t('textui.errorState.title', undefined, 'Something went wrong'), error, onRetry, ...rest } = props;
|
|
14
15
|
const message = error instanceof Error ? error.message : String(error);
|
|
15
16
|
|
|
16
17
|
return h('box', { role: 'alert', direction: 'column', gap: 1, padding: 1, ...rest },
|
|
@@ -18,6 +19,6 @@ export const ErrorState = defineComponent<ErrorStateProps>('ErrorState', (props)
|
|
|
18
19
|
h('text', { content: theme.glyphs.cross, fg: 'danger' }),
|
|
19
20
|
h('text', { content: title, bold: true, fg: 'danger' })),
|
|
20
21
|
h('text', { content: message, fg: 'muted', wrap: 'word' }),
|
|
21
|
-
onRetry ? h('text', { content: 'r retry', fg: 'subtle' }) : null,
|
|
22
|
+
onRetry ? h('text', { content: i18n.t('textui.errorState.retry', undefined, 'r retry'), fg: 'subtle' }) : null,
|
|
22
23
|
);
|
|
23
24
|
});
|