@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
package/src/navigation/menu.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BoxProps, SemanticVariant } from '@textui/core';
|
|
2
|
-
import { defineComponent, h, stringWidth, useFocus, useInput, useState, useTheme } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, stringWidth, useFocus, useI18n, useInput, useState, useTheme } from '@textui/core';
|
|
3
3
|
import { Marquee } from '../display/index.js';
|
|
4
4
|
import { TONE } from '../tone.js';
|
|
5
5
|
|
|
@@ -61,8 +61,12 @@ export interface MenuProps extends BoxProps {
|
|
|
61
61
|
interactive?: boolean;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/** The name a theme styles a menu row under. See `LIST`. */
|
|
65
|
+
export const MENU = 'Menu';
|
|
66
|
+
|
|
64
67
|
export const Menu = defineComponent<MenuProps>('Menu', (props) => {
|
|
65
68
|
const theme = useTheme();
|
|
69
|
+
const i18n = useI18n();
|
|
66
70
|
const {
|
|
67
71
|
items, onSelect, visibleRows, activeId, autoFocus, interactive = true,
|
|
68
72
|
descriptions = 'inline', ...rest
|
|
@@ -125,15 +129,20 @@ export const Menu = defineComponent<MenuProps>('Menu', (props) => {
|
|
|
125
129
|
// The description yields first, and by a lot. It is the elaboration;
|
|
126
130
|
// the label is the thing being chosen, and a row reading "Accept ed…"
|
|
127
131
|
// beside a full sentence has given up the wrong half.
|
|
132
|
+
//
|
|
133
|
+
// On the row under the cursor the foreground is `undefined`, not a
|
|
134
|
+
// token: what is written on a selection is the theme's, and a hard
|
|
135
|
+
// `onSelected` here would be a second answer to the same question that
|
|
136
|
+
// a theme restating `Menu.focused.fg` could not reach.
|
|
128
137
|
item.description && !below
|
|
129
138
|
? h(Marquee, {
|
|
130
139
|
content: item.description,
|
|
131
140
|
active,
|
|
132
|
-
fg: active ?
|
|
141
|
+
fg: active ? undefined : 'muted',
|
|
133
142
|
shrink: 8,
|
|
134
143
|
})
|
|
135
144
|
: null,
|
|
136
|
-
item.shortcut ? h('text', { content: item.shortcut, fg: active ?
|
|
145
|
+
item.shortcut ? h('text', { content: item.shortcut, fg: active ? undefined : 'subtle' }) : null,
|
|
137
146
|
// Present but empty means "this opens something, contents unknown" -
|
|
138
147
|
// which is what the command palette knows about an argument whose
|
|
139
148
|
// choices it has not resolved yet.
|
|
@@ -144,12 +153,18 @@ export const Menu = defineComponent<MenuProps>('Menu', (props) => {
|
|
|
144
153
|
key: item.id,
|
|
145
154
|
role: 'menuitem',
|
|
146
155
|
label: item.label,
|
|
156
|
+
styleAs: MENU,
|
|
147
157
|
selected: active,
|
|
158
|
+
// The keyboard is the menu's, so the row is told - which is what lets
|
|
159
|
+
// `Menu.selected` be the dimmer fill a menu shows when something else
|
|
160
|
+
// has taken the keyboard, and `Menu.focused` the one it shows while
|
|
161
|
+
// the reader is moving through it.
|
|
162
|
+
focused: active && focus.focused,
|
|
148
163
|
direction: 'column',
|
|
149
164
|
// One background over both lines: a highlight that stopped after the
|
|
150
|
-
// label would split the row it is highlighting in two.
|
|
151
|
-
|
|
152
|
-
fg: item.disabled ? 'disabled' : active ?
|
|
165
|
+
// label would split the row it is highlighting in two. What it is
|
|
166
|
+
// filled with is the theme's to say.
|
|
167
|
+
fg: item.disabled ? 'disabled' : active ? undefined : item.tone ? TONE[item.tone] : undefined,
|
|
153
168
|
onClick: () => { if (!item.disabled) onSelect?.(item.id, item); },
|
|
154
169
|
},
|
|
155
170
|
head,
|
|
@@ -165,7 +180,7 @@ export const Menu = defineComponent<MenuProps>('Menu', (props) => {
|
|
|
165
180
|
h(Marquee, {
|
|
166
181
|
content: item.description as string,
|
|
167
182
|
active,
|
|
168
|
-
fg: active ?
|
|
183
|
+
fg: active ? undefined : 'muted',
|
|
169
184
|
flex: 1,
|
|
170
185
|
}))
|
|
171
186
|
: null,
|
|
@@ -188,7 +203,7 @@ export const Menu = defineComponent<MenuProps>('Menu', (props) => {
|
|
|
188
203
|
: [row];
|
|
189
204
|
}),
|
|
190
205
|
selectable.length === 0
|
|
191
|
-
? h('text', { content: '
|
|
206
|
+
? h('text', { content: ` ${i18n.t('textui.menu.noMatch', undefined, 'no matches')}`, fg: 'subtle' })
|
|
192
207
|
: null,
|
|
193
208
|
);
|
|
194
209
|
});
|
package/src/navigation/tabs.ts
CHANGED
|
@@ -21,6 +21,9 @@ export interface TabsProps extends BoxProps {
|
|
|
21
21
|
autoFocus?: boolean;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** The name a theme styles a tab under. See `LIST`. */
|
|
25
|
+
export const TABS = 'Tabs';
|
|
26
|
+
|
|
24
27
|
export const Tabs = defineComponent<TabsProps>('Tabs', (props) => {
|
|
25
28
|
const { items, activeId, onChange, variant = 'underline', separator, autoFocus, ...rest } = props;
|
|
26
29
|
const focus = useFocus({ autoFocus });
|
|
@@ -46,12 +49,20 @@ export const Tabs = defineComponent<TabsProps>('Tabs', (props) => {
|
|
|
46
49
|
key: item.id,
|
|
47
50
|
role: 'tab',
|
|
48
51
|
label: item.label,
|
|
52
|
+
styleAs: TABS,
|
|
53
|
+
// Carried so the theme can tell a filled tab from an unfilled one: a
|
|
54
|
+
// solid tab is `Tabs.solid.selected`, an underline one is not filled
|
|
55
|
+
// at all. See `Tabs` in the built-in themes.
|
|
56
|
+
variant,
|
|
57
|
+
// A tab is open, not selected, and it says so with one state. There is
|
|
58
|
+
// no second name for it because there is nothing to dim: the document
|
|
59
|
+
// a tab names is open whether or not the strip has the keyboard, and
|
|
60
|
+
// dimming the tab on losing focus would claim none is.
|
|
49
61
|
selected: active,
|
|
50
62
|
direction: 'row',
|
|
51
63
|
gap: 1,
|
|
52
64
|
padding: variant === 'solid' ? [0, 1] : 0,
|
|
53
|
-
|
|
54
|
-
fg: item.disabled ? 'disabled' : active ? (variant === 'solid' ? 'inverted' : 'accent') : 'muted',
|
|
65
|
+
fg: item.disabled ? 'disabled' : active ? (variant === 'solid' ? undefined : 'accent') : 'muted',
|
|
55
66
|
bold: active,
|
|
56
67
|
underline: variant === 'underline' && active,
|
|
57
68
|
onClick: () => { if (!item.disabled) onChange?.(item.id); },
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
stringWidth,
|
|
6
6
|
useEffect,
|
|
7
7
|
useFocusScope,
|
|
8
|
+
useI18n,
|
|
8
9
|
useInput,
|
|
9
10
|
useRuntime,
|
|
10
11
|
useState,
|
|
@@ -100,6 +101,8 @@ interface Pending {
|
|
|
100
101
|
|
|
101
102
|
export const CommandPalette = defineComponent<CommandPaletteProps>('CommandPalette', (props) => {
|
|
102
103
|
const theme = useTheme();
|
|
104
|
+
const i18n = useI18n();
|
|
105
|
+
const ellipsis = theme.glyphs.ellipsis;
|
|
103
106
|
const runtime = useRuntime();
|
|
104
107
|
const {
|
|
105
108
|
commands, placeholder, onRun, onClose, execute = true,
|
|
@@ -131,7 +134,9 @@ export const CommandPalette = defineComponent<CommandPaletteProps>('CommandPalet
|
|
|
131
134
|
const given = typeof commands === 'function' ? commands() : commands;
|
|
132
135
|
const all = given ?? app?.commands.list({ slot: 'palette', enabledOnly: true }) ?? [];
|
|
133
136
|
|
|
134
|
-
const matches = pending
|
|
137
|
+
const matches = pending
|
|
138
|
+
? []
|
|
139
|
+
: grouped && query.trim() === '' ? byCategory(all) : filterCommands(all, query);
|
|
135
140
|
const offered = pending ? filterChoices(choices, query) : [];
|
|
136
141
|
const rows = pending ? offered.map((choice) => choice.value) : matches.map((c) => c.id);
|
|
137
142
|
const index = Math.max(0, Math.min(highlight, rows.length - 1));
|
|
@@ -146,7 +151,9 @@ export const CommandPalette = defineComponent<CommandPaletteProps>('CommandPalet
|
|
|
146
151
|
// the only thing the palette itself knows about a value.
|
|
147
152
|
...(choice.description
|
|
148
153
|
? { description: choice.description }
|
|
149
|
-
: pending.arg.default === choice.value
|
|
154
|
+
: pending.arg.default === choice.value
|
|
155
|
+
? { description: i18n.t('textui.palette.default', undefined, 'default') }
|
|
156
|
+
: {}),
|
|
150
157
|
}))
|
|
151
158
|
// One row, saying which kind of nothing this is. A disabled item rather
|
|
152
159
|
// than no items: the panel keeps its height, so the answer arriving does
|
|
@@ -154,8 +161,10 @@ export const CommandPalette = defineComponent<CommandPaletteProps>('CommandPalet
|
|
|
154
161
|
: [{
|
|
155
162
|
id: '',
|
|
156
163
|
label: asking
|
|
157
|
-
?
|
|
158
|
-
: (query === ''
|
|
164
|
+
? i18n.t('textui.palette.asking', { ellipsis }, 'Asking{ellipsis}')
|
|
165
|
+
: (query === ''
|
|
166
|
+
? i18n.t('textui.palette.nothingToChoose', undefined, 'Nothing to choose')
|
|
167
|
+
: i18n.t('textui.palette.noMatch', undefined, 'No match')),
|
|
159
168
|
disabled: true,
|
|
160
169
|
}])
|
|
161
170
|
: matches.map((command, i) => ({
|
|
@@ -415,7 +424,12 @@ export const CommandPalette = defineComponent<CommandPaletteProps>('CommandPalet
|
|
|
415
424
|
);
|
|
416
425
|
|
|
417
426
|
const highlighted = pending ? undefined : matches[index];
|
|
418
|
-
const move =
|
|
427
|
+
const move = i18n.t(
|
|
428
|
+
'textui.palette.hint.move',
|
|
429
|
+
{ arrows: `${theme.glyphs.arrowUp}${theme.glyphs.arrowDown}` },
|
|
430
|
+
'{arrows} move',
|
|
431
|
+
);
|
|
432
|
+
const escBack = i18n.t('textui.palette.hint.back', undefined, 'esc back');
|
|
419
433
|
// What the highlighted row is, in full.
|
|
420
434
|
//
|
|
421
435
|
// The rows themselves have to fit, so a choice's own sentence is truncated
|
|
@@ -427,7 +441,12 @@ export const CommandPalette = defineComponent<CommandPaletteProps>('CommandPalet
|
|
|
427
441
|
const chosen = pending ? offered[index] : undefined;
|
|
428
442
|
const detail = pending
|
|
429
443
|
? chosen?.description
|
|
430
|
-
?? pending.arg.description
|
|
444
|
+
?? pending.arg.description
|
|
445
|
+
?? i18n.t(
|
|
446
|
+
'textui.palette.needs',
|
|
447
|
+
{ command: pending.command.title, arg: pending.arg.name },
|
|
448
|
+
'{command} needs a {arg}',
|
|
449
|
+
)
|
|
431
450
|
: highlighted?.description ?? highlighted?.id ?? '';
|
|
432
451
|
|
|
433
452
|
/*
|
|
@@ -457,7 +476,7 @@ export const CommandPalette = defineComponent<CommandPaletteProps>('CommandPalet
|
|
|
457
476
|
|
|
458
477
|
return h('box', {
|
|
459
478
|
role: 'dialog',
|
|
460
|
-
label: 'Commands',
|
|
479
|
+
label: i18n.t('textui.palette.label', undefined, 'Commands'),
|
|
461
480
|
border: theme.border,
|
|
462
481
|
bg: 'overlay',
|
|
463
482
|
// A stated width is a width. Left off, it fits what it holds.
|
|
@@ -473,8 +492,8 @@ export const CommandPalette = defineComponent<CommandPaletteProps>('CommandPalet
|
|
|
473
492
|
// where you are; "commands › id" is what the parameter happens to be
|
|
474
493
|
// called, which is the author's business rather than the reader's.
|
|
475
494
|
title: pending
|
|
476
|
-
? ` commands ${theme.glyphs.breadcrumb} ${pending.command.title} `
|
|
477
|
-
: ' commands
|
|
495
|
+
? ` ${i18n.t('textui.palette.title', undefined, 'commands')} ${theme.glyphs.breadcrumb} ${pending.command.title} `
|
|
496
|
+
: ` ${i18n.t('textui.palette.title', undefined, 'commands')} `,
|
|
478
497
|
...rest,
|
|
479
498
|
},
|
|
480
499
|
h(TextInput, {
|
|
@@ -511,8 +530,12 @@ export const CommandPalette = defineComponent<CommandPaletteProps>('CommandPalet
|
|
|
511
530
|
? pending.arg.description ?? `${pending.command.title}${theme.glyphs.ellipsis}`
|
|
512
531
|
: offered.some((choice) => choice.description) && pending.arg.description
|
|
513
532
|
? pending.arg.description
|
|
514
|
-
:
|
|
515
|
-
|
|
533
|
+
: i18n.t(
|
|
534
|
+
'textui.palette.choose',
|
|
535
|
+
{ command: pending.command.title.toLowerCase(), ellipsis },
|
|
536
|
+
'Choose {command}{ellipsis}',
|
|
537
|
+
))
|
|
538
|
+
: (placeholder ?? i18n.t('textui.palette.placeholder', { ellipsis }, 'Type a command{ellipsis}')),
|
|
516
539
|
search: true,
|
|
517
540
|
autoFocus: true,
|
|
518
541
|
border: 'none',
|
|
@@ -542,11 +565,22 @@ export const CommandPalette = defineComponent<CommandPaletteProps>('CommandPalet
|
|
|
542
565
|
h('text', {
|
|
543
566
|
content: pending
|
|
544
567
|
? (pending.arg.choices === undefined
|
|
545
|
-
? hint(theme, [
|
|
568
|
+
? hint(theme, [
|
|
569
|
+
i18n.t('textui.palette.hint.type', undefined, 'type it'),
|
|
570
|
+
i18n.t('textui.palette.hint.confirm', undefined, 'enter confirm'),
|
|
571
|
+
escBack,
|
|
572
|
+
])
|
|
546
573
|
// Nothing to choose is nothing to press enter on, and offering it
|
|
547
574
|
// is how a panel reads as broken rather than as empty.
|
|
548
|
-
: hint(theme, rows.length > 0
|
|
549
|
-
|
|
575
|
+
: hint(theme, rows.length > 0
|
|
576
|
+
? [move, i18n.t('textui.palette.hint.choose', undefined, 'enter choose'), escBack]
|
|
577
|
+
: [escBack]))
|
|
578
|
+
: hint(theme, [
|
|
579
|
+
move,
|
|
580
|
+
i18n.t('textui.palette.hint.run', undefined, 'enter run'),
|
|
581
|
+
i18n.t('textui.palette.hint.subItems', { glyph: theme.glyphs.chevronRight }, '{glyph} sub-items'),
|
|
582
|
+
i18n.t('textui.palette.hint.close', undefined, 'esc close'),
|
|
583
|
+
]),
|
|
550
584
|
fg: 'subtle',
|
|
551
585
|
truncate: 'end',
|
|
552
586
|
})),
|
|
@@ -618,6 +652,24 @@ function filterChoices(choices: ArgChoice[], query: string): ArgChoice[] {
|
|
|
618
652
|
.toLowerCase().includes(q));
|
|
619
653
|
}
|
|
620
654
|
|
|
655
|
+
/**
|
|
656
|
+
* Each category's commands together, the categories in the order they first
|
|
657
|
+
* appear and the commands in the order they came.
|
|
658
|
+
*
|
|
659
|
+
* Registration order is whatever order the code happened to run in, so a
|
|
660
|
+
* category registered in two places would be two groups with the same name
|
|
661
|
+
* over them.
|
|
662
|
+
*/
|
|
663
|
+
function byCategory(commands: CommandDefinition[]): CommandDefinition[] {
|
|
664
|
+
const groups = new Map<string | undefined, CommandDefinition[]>();
|
|
665
|
+
for (const command of commands) {
|
|
666
|
+
const group = groups.get(command.category);
|
|
667
|
+
if (group) group.push(command);
|
|
668
|
+
else groups.set(command.category, [command]);
|
|
669
|
+
}
|
|
670
|
+
return [...groups.values()].flat();
|
|
671
|
+
}
|
|
672
|
+
|
|
621
673
|
export function filterCommands(commands: CommandDefinition[], query: string): CommandDefinition[] {
|
|
622
674
|
const q = query.trim().toLowerCase();
|
|
623
675
|
if (q === '') return commands;
|
package/src/overlay/dialog.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BoxProps, SemanticVariant } from '@textui/core';
|
|
2
|
-
import { defineComponent, h, useFocusScope, useInput, useTheme } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, useFocusScope, useI18n, useInput, useTheme } from '@textui/core';
|
|
3
3
|
import { Button } from '../control/index.js';
|
|
4
4
|
|
|
5
5
|
export interface DialogProps extends BoxProps {
|
|
@@ -63,3 +63,39 @@ export const Dialog = defineComponent<DialogProps>('Dialog', (props) => {
|
|
|
63
63
|
: null,
|
|
64
64
|
);
|
|
65
65
|
});
|
|
66
|
+
|
|
67
|
+
export interface ConfirmDialogProps extends BoxProps {
|
|
68
|
+
title?: string;
|
|
69
|
+
message: string;
|
|
70
|
+
confirmLabel?: string;
|
|
71
|
+
cancelLabel?: string;
|
|
72
|
+
tone?: SemanticVariant;
|
|
73
|
+
onConfirm?(): void;
|
|
74
|
+
onCancel?(): void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Two-button dialog behind the `confirm` helper. */
|
|
78
|
+
export const ConfirmDialog = defineComponent<ConfirmDialogProps>('ConfirmDialog', (props) => {
|
|
79
|
+
const i18n = useI18n();
|
|
80
|
+
const { title, message, confirmLabel, cancelLabel, tone, onConfirm, onCancel, ...rest } = props;
|
|
81
|
+
return h(Dialog, {
|
|
82
|
+
title: title ?? i18n.t('textui.confirmDialog.title', undefined, 'Confirm'),
|
|
83
|
+
width: 50,
|
|
84
|
+
actions: [
|
|
85
|
+
{
|
|
86
|
+
id: 'confirm',
|
|
87
|
+
label: confirmLabel ?? i18n.t('textui.confirmDialog.confirm', undefined, 'Confirm'),
|
|
88
|
+
tone: tone ?? 'primary',
|
|
89
|
+
onPress: onConfirm,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'cancel',
|
|
93
|
+
label: cancelLabel ?? i18n.t('textui.confirmDialog.cancel', undefined, 'Cancel'),
|
|
94
|
+
onPress: onCancel,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
...rest,
|
|
98
|
+
},
|
|
99
|
+
h('text', { content: message, wrap: 'word' }),
|
|
100
|
+
);
|
|
101
|
+
});
|
package/src/overlay/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
TextUIApp,
|
|
8
8
|
} from '@textui/core';
|
|
9
9
|
import { CommandPalette } from './command-palette.js';
|
|
10
|
-
import { Dialog } from './dialog.js';
|
|
10
|
+
import { ConfirmDialog, Dialog } from './dialog.js';
|
|
11
11
|
import { LayerScope } from './layer-scope.js';
|
|
12
12
|
import { PathPicker } from './path-picker.js';
|
|
13
13
|
import { PromptDialog } from './prompt-dialog.js';
|
|
@@ -68,23 +68,14 @@ export function confirm(
|
|
|
68
68
|
dismissOnEscape: true,
|
|
69
69
|
onClose: () => finish(false),
|
|
70
70
|
node: {
|
|
71
|
-
component: '
|
|
72
|
-
title: options.title
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
tone: options.tone ?? 'primary',
|
|
80
|
-
onPress: () => finish(true),
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
id: 'cancel',
|
|
84
|
-
label: options.cancelLabel ?? 'Cancel',
|
|
85
|
-
onPress: () => finish(false),
|
|
86
|
-
},
|
|
87
|
-
],
|
|
71
|
+
component: 'ConfirmDialog',
|
|
72
|
+
title: options.title,
|
|
73
|
+
message: options.message,
|
|
74
|
+
confirmLabel: options.confirmLabel,
|
|
75
|
+
cancelLabel: options.cancelLabel,
|
|
76
|
+
tone: options.tone,
|
|
77
|
+
onConfirm: { handler: () => finish(true) },
|
|
78
|
+
onCancel: { handler: () => finish(false) },
|
|
88
79
|
},
|
|
89
80
|
});
|
|
90
81
|
});
|
|
@@ -120,7 +111,7 @@ export function prompt(
|
|
|
120
111
|
onClose: () => finish(null),
|
|
121
112
|
node: {
|
|
122
113
|
component: 'PromptDialog',
|
|
123
|
-
title: options.title
|
|
114
|
+
title: options.title,
|
|
124
115
|
message: options.message,
|
|
125
116
|
placeholder: options.placeholder,
|
|
126
117
|
initialValue: options.initialValue,
|
|
@@ -222,6 +213,7 @@ export function toastPosition(anchor: ToastHostProps['anchor']): LayerPosition {
|
|
|
222
213
|
|
|
223
214
|
export const OVERLAY_COMPONENTS: ComponentDefinition[] = [
|
|
224
215
|
{ component: 'Dialog', category: 'overlay', renderer: { kind: 'function', render: Dialog }, role: 'dialog', description: 'Modal box with actions; traps focus and restores it.' },
|
|
216
|
+
{ component: 'ConfirmDialog', category: 'overlay', renderer: { kind: 'function', render: ConfirmDialog }, role: 'dialog', description: 'Two-button dialog behind the `confirm` helper.' },
|
|
225
217
|
{ component: 'PromptDialog', category: 'overlay', renderer: { kind: 'function', render: PromptDialog }, role: 'dialog', description: 'One-field dialog behind the `prompt` helper.' },
|
|
226
218
|
{ component: 'PathPicker', category: 'overlay', renderer: { kind: 'function', render: PathPicker }, role: 'dialog', description: 'Walk the resource tree and pick a file or a folder.' },
|
|
227
219
|
{ component: 'Tooltip', category: 'overlay', renderer: { kind: 'function', render: Tooltip }, role: 'tooltip', description: 'Small anchored hint.' },
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
h,
|
|
5
5
|
nameOf,
|
|
6
6
|
useEffect,
|
|
7
|
+
useI18n,
|
|
7
8
|
useInput,
|
|
8
9
|
useRuntime,
|
|
9
10
|
useState,
|
|
@@ -41,6 +42,8 @@ export const PathPicker = defineComponent<PathPickerProps>('PathPicker', (props)
|
|
|
41
42
|
width = 60, onPick, onCancel, ...rest
|
|
42
43
|
} = props;
|
|
43
44
|
const theme = useTheme();
|
|
45
|
+
const i18n = useI18n();
|
|
46
|
+
const ellipsis = theme.glyphs.ellipsis;
|
|
44
47
|
const runtime = useRuntime();
|
|
45
48
|
const app = runtime.app();
|
|
46
49
|
|
|
@@ -90,7 +93,7 @@ export const PathPicker = defineComponent<PathPickerProps>('PathPicker', (props)
|
|
|
90
93
|
// child to press enter on and a hidden chord for it is a chord nobody
|
|
91
94
|
// finds. It is first, since it is the answer most often wanted.
|
|
92
95
|
...(wants === 'directory'
|
|
93
|
-
? [{ id: '.', label: 'Use this folder', icon: theme.glyphs.check }]
|
|
96
|
+
? [{ id: '.', label: i18n.t('textui.pathPicker.useFolder', undefined, 'Use this folder'), icon: theme.glyphs.check }]
|
|
94
97
|
: []),
|
|
95
98
|
...(parent !== null ? [{ id: '..', label: '..', icon: theme.glyphs.chevronUp }] : []),
|
|
96
99
|
...shown.map((e) => ({
|
|
@@ -139,17 +142,23 @@ export const PathPicker = defineComponent<PathPickerProps>('PathPicker', (props)
|
|
|
139
142
|
}, { global: true });
|
|
140
143
|
|
|
141
144
|
|
|
142
|
-
const move =
|
|
145
|
+
const move = i18n.t(
|
|
146
|
+
'textui.pathPicker.hint.move',
|
|
147
|
+
{ arrows: `${theme.glyphs.arrowUp}${theme.glyphs.arrowDown}` },
|
|
148
|
+
'{arrows} move',
|
|
149
|
+
);
|
|
143
150
|
|
|
144
151
|
return h('box', {
|
|
145
152
|
role: 'dialog',
|
|
146
|
-
label: title ?? 'Choose',
|
|
153
|
+
label: title ?? i18n.t('textui.pathPicker.label', undefined, 'Choose'),
|
|
147
154
|
border: theme.border,
|
|
148
155
|
bg: 'overlay',
|
|
149
156
|
width,
|
|
150
157
|
direction: 'column',
|
|
151
158
|
...(theme.border === 'none' ? { padding: { left: 1, right: 1 } } : {}),
|
|
152
|
-
title: ` ${title ?? (wants === 'directory'
|
|
159
|
+
title: ` ${title ?? (wants === 'directory'
|
|
160
|
+
? i18n.t('textui.pathPicker.titleFolder', undefined, 'choose a folder')
|
|
161
|
+
: i18n.t('textui.pathPicker.titleFile', undefined, 'choose a file'))} `,
|
|
153
162
|
...rest,
|
|
154
163
|
},
|
|
155
164
|
// Where you are. A column of bare names with no header is a list you can
|
|
@@ -171,7 +180,7 @@ export const PathPicker = defineComponent<PathPickerProps>('PathPicker', (props)
|
|
|
171
180
|
* what you want while editing one.
|
|
172
181
|
*/
|
|
173
182
|
onEdge: (edge: 'start' | 'end') => { if (edge === 'start') up(); },
|
|
174
|
-
placeholder: placeholder ??
|
|
183
|
+
placeholder: placeholder ?? i18n.t('textui.pathPicker.placeholder', { ellipsis }, 'Filter{ellipsis}'),
|
|
175
184
|
search: true,
|
|
176
185
|
autoFocus: true,
|
|
177
186
|
border: 'none',
|
|
@@ -195,8 +204,10 @@ export const PathPicker = defineComponent<PathPickerProps>('PathPicker', (props)
|
|
|
195
204
|
shown.length === 0
|
|
196
205
|
? h('text', {
|
|
197
206
|
content: loading
|
|
198
|
-
?
|
|
199
|
-
: (q === ''
|
|
207
|
+
? i18n.t('textui.pathPicker.reading', { ellipsis }, 'Reading{ellipsis}')
|
|
208
|
+
: (q === ''
|
|
209
|
+
? i18n.t('textui.pathPicker.empty', undefined, 'Nothing here.')
|
|
210
|
+
: i18n.t('textui.pathPicker.noMatch', undefined, 'Nothing matches.')),
|
|
200
211
|
fg: 'muted',
|
|
201
212
|
})
|
|
202
213
|
: null,
|
|
@@ -204,9 +215,11 @@ export const PathPicker = defineComponent<PathPickerProps>('PathPicker', (props)
|
|
|
204
215
|
h('text', {
|
|
205
216
|
content: hint(theme, [
|
|
206
217
|
move,
|
|
207
|
-
wants === 'directory'
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
wants === 'directory'
|
|
219
|
+
? i18n.t('textui.pathPicker.hint.open', undefined, 'enter open')
|
|
220
|
+
: i18n.t('textui.pathPicker.hint.choose', undefined, 'enter choose'),
|
|
221
|
+
i18n.t('textui.pathPicker.hint.up', { glyph: theme.glyphs.chevronLeft }, '{glyph} up'),
|
|
222
|
+
i18n.t('textui.pathPicker.hint.cancel', undefined, 'esc cancel'),
|
|
210
223
|
]),
|
|
211
224
|
fg: 'subtle',
|
|
212
225
|
truncate: 'end',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BoxProps } from '@textui/core';
|
|
2
|
-
import { defineComponent, h, useState } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, useI18n, useState } from '@textui/core';
|
|
3
3
|
import { TextInput } from '../control/index.js';
|
|
4
4
|
import { Dialog } from './dialog.js';
|
|
5
5
|
|
|
@@ -15,15 +15,16 @@ export interface PromptDialogProps extends BoxProps {
|
|
|
15
15
|
|
|
16
16
|
export const PromptDialog = defineComponent<PromptDialogProps>('PromptDialog', (props) => {
|
|
17
17
|
const { title, message, placeholder, initialValue = '', mask, onSubmit, onCancel, ...rest } = props;
|
|
18
|
+
const i18n = useI18n();
|
|
18
19
|
const [value, setValue] = useState(initialValue);
|
|
19
20
|
|
|
20
21
|
return h(Dialog, {
|
|
21
|
-
title,
|
|
22
|
+
title: title ?? i18n.t('textui.promptDialog.title', undefined, 'Input'),
|
|
22
23
|
width: 50,
|
|
23
24
|
onClose: onCancel,
|
|
24
25
|
actions: [
|
|
25
|
-
{ id: 'ok', label: 'OK', tone: 'primary', onPress: () => onSubmit?.(value) },
|
|
26
|
-
{ id: 'cancel', label: 'Cancel', onPress: onCancel },
|
|
26
|
+
{ id: 'ok', label: i18n.t('textui.promptDialog.ok', undefined, 'OK'), tone: 'primary', onPress: () => onSubmit?.(value) },
|
|
27
|
+
{ id: 'cancel', label: i18n.t('textui.promptDialog.cancel', undefined, 'Cancel'), onPress: onCancel },
|
|
27
28
|
],
|
|
28
29
|
...rest,
|
|
29
30
|
},
|
package/src/panel/index.ts
CHANGED
|
@@ -244,18 +244,20 @@ export function panelCommands(app: TextUIApp): CommandDefinition[] {
|
|
|
244
244
|
const targetUri = (args: Record<string, unknown>, id: string | null): string | null =>
|
|
245
245
|
(args.uri as string | undefined) ?? (id === null ? null : panelUri(app.store, id));
|
|
246
246
|
|
|
247
|
+
const category = app.i18n.t('textui.panel.category', undefined, 'View');
|
|
248
|
+
|
|
247
249
|
return [
|
|
248
250
|
{
|
|
249
251
|
id: 'panel.openWith',
|
|
250
|
-
title: 'Open With…',
|
|
251
|
-
category
|
|
252
|
+
title: app.i18n.t('textui.panel.openWith', undefined, 'Open With…'),
|
|
253
|
+
category,
|
|
252
254
|
slots: ['palette'],
|
|
253
255
|
when: PANEL_PATH,
|
|
254
256
|
args: [{
|
|
255
257
|
name: 'renderer',
|
|
256
258
|
type: 'string' as const,
|
|
257
259
|
required: true,
|
|
258
|
-
description: 'How to show this resource',
|
|
260
|
+
description: app.i18n.t('textui.panel.renderer', undefined, 'How to show this resource'),
|
|
259
261
|
choices: async () => {
|
|
260
262
|
const id = activePanel(app.store);
|
|
261
263
|
return (await choicesFor(app, id === null ? null : panelUri(app.store, id)))
|
|
@@ -272,8 +274,8 @@ export function panelCommands(app: TextUIApp): CommandDefinition[] {
|
|
|
272
274
|
},
|
|
273
275
|
{
|
|
274
276
|
id: 'panel.nextRenderer',
|
|
275
|
-
title: 'Next View',
|
|
276
|
-
category
|
|
277
|
+
title: app.i18n.t('textui.panel.nextView', undefined, 'Next View'),
|
|
278
|
+
category,
|
|
277
279
|
slots: ['palette'],
|
|
278
280
|
when: PANEL_PATH,
|
|
279
281
|
run: async (args: Record<string, unknown>, ctx: CommandContext) => {
|
|
@@ -291,8 +293,8 @@ export function panelCommands(app: TextUIApp): CommandDefinition[] {
|
|
|
291
293
|
},
|
|
292
294
|
{
|
|
293
295
|
id: 'panel.toggleEdit',
|
|
294
|
-
title: 'Edit / View',
|
|
295
|
-
category
|
|
296
|
+
title: app.i18n.t('textui.panel.toggleEdit', undefined, 'Edit / View'),
|
|
297
|
+
category,
|
|
296
298
|
slots: ['palette'],
|
|
297
299
|
when: PANEL_PATH,
|
|
298
300
|
run: async (args: Record<string, unknown>, ctx: CommandContext) => {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
h,
|
|
13
13
|
useEffect,
|
|
14
14
|
useFocusScope,
|
|
15
|
+
useI18n,
|
|
15
16
|
useRef,
|
|
16
17
|
useRuntime,
|
|
17
18
|
useStoreValue,
|
|
@@ -144,6 +145,7 @@ export interface ResourcePanelProps extends BoxProps {
|
|
|
144
145
|
*/
|
|
145
146
|
export const ResourcePanel = defineComponent<ResourcePanelProps>('ResourcePanel', (props) => {
|
|
146
147
|
const runtime = useRuntime();
|
|
148
|
+
const i18n = useI18n();
|
|
147
149
|
const {
|
|
148
150
|
id: named, uri, renderer, mode, rendererProps, fallbackComponent, autoFocus,
|
|
149
151
|
emptyTitle, ...rest
|
|
@@ -265,19 +267,22 @@ export const ResourcePanel = defineComponent<ResourcePanelProps>('ResourcePanel'
|
|
|
265
267
|
if (claim > 0 && chosen) runtime.store.set(panelClaimPath(id), 0);
|
|
266
268
|
}, [id, claim, chosen?.id ?? null]);
|
|
267
269
|
|
|
268
|
-
if (!uri) return h(EmptyState, { title: emptyTitle ?? 'Nothing selected', ...rest });
|
|
270
|
+
if (!uri) return h(EmptyState, { title: emptyTitle ?? i18n.t('textui.resourcePanel.empty', undefined, 'Nothing selected'), ...rest });
|
|
269
271
|
if (!app) return null;
|
|
270
272
|
if (task.status === 'running' || task.status === 'idle') {
|
|
271
|
-
return h(Spinner, { label: 'Loading…', ...rest });
|
|
273
|
+
return h(Spinner, { label: i18n.t('textui.resourcePanel.loading', undefined, 'Loading…'), ...rest });
|
|
272
274
|
}
|
|
273
275
|
if (task.status === 'error') return h(ErrorState, { error: task.error, ...rest });
|
|
274
|
-
if (!resource) return h(EmptyState, { title: 'Not found', message: uri, ...rest });
|
|
276
|
+
if (!resource) return h(EmptyState, { title: i18n.t('textui.resourcePanel.notFound', undefined, 'Not found'), message: uri, ...rest });
|
|
275
277
|
|
|
276
278
|
if (!chosen) {
|
|
277
279
|
return h('box', { direction: 'column', flex: 1, id: scope, ...rest },
|
|
278
280
|
fallbackComponent
|
|
279
281
|
? { component: fallbackComponent, resource, uri }
|
|
280
|
-
: h(EmptyState, {
|
|
282
|
+
: h(EmptyState, {
|
|
283
|
+
title: i18n.t('textui.resourcePanel.noRenderer', undefined, 'No renderer'),
|
|
284
|
+
message: resource.kind,
|
|
285
|
+
}));
|
|
281
286
|
}
|
|
282
287
|
|
|
283
288
|
const node: ComponentNode = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BindingPath, BoxProps, ComponentNode, Mount } from '@textui/core';
|
|
2
|
-
import { defineComponent, h, useRuntime } from '@textui/core';
|
|
2
|
+
import { defineComponent, h, useI18n, useRuntime } from '@textui/core';
|
|
3
3
|
import { EmptyState } from '../display/index.js';
|
|
4
4
|
|
|
5
5
|
export interface MountViewProps extends BoxProps {
|
|
@@ -9,6 +9,7 @@ export interface MountViewProps extends BoxProps {
|
|
|
9
9
|
/** Renders one mount's target - an inline node, or a node read from a path. */
|
|
10
10
|
export const MountView = defineComponent<MountViewProps>('MountView', ({ mount, ...rest }) => {
|
|
11
11
|
const runtime = useRuntime();
|
|
12
|
+
const i18n = useI18n();
|
|
12
13
|
|
|
13
14
|
const target = mount.target;
|
|
14
15
|
const node: ComponentNode | null =
|
|
@@ -17,7 +18,7 @@ export const MountView = defineComponent<MountViewProps>('MountView', ({ mount,
|
|
|
17
18
|
: (runtime.store.get<ComponentNode>((target as { path: BindingPath }).path) ?? null);
|
|
18
19
|
|
|
19
20
|
if (!node) {
|
|
20
|
-
return h(EmptyState, { title: 'Nothing mounted', ...rest });
|
|
21
|
+
return h(EmptyState, { title: i18n.t('textui.mountView.empty', undefined, 'Nothing mounted'), ...rest });
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
// A mount's data context makes relative paths inside it resolve against the
|