paseo-tab-namer 0.1.0 → 0.2.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/OVERVIEW.md +1 -1
- package/OVERVIEW.zh-CN.md +1 -1
- package/README.md +5 -5
- package/README.zh-CN.md +5 -5
- package/client/rename.tsx +145 -108
- package/client/ui.tsx +63 -29
- package/docs/images/popover.png +0 -0
- package/index.client.tsx +10 -5
- package/package.json +1 -1
package/OVERVIEW.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Tab Namer
|
|
2
2
|
|
|
3
|
-
Tab Namer gives an existing workspace a new name based on what its Tabs are about. Open it from the ✨ button in the workspace header or from the command center. You can preview the input, choose which Tabs to use, and generate again. A generated name is applied right away. To apply a name you edited by hand, click "
|
|
3
|
+
Tab Namer gives an existing workspace a new name based on what its Tabs are about. Open it from the ✨ button in the workspace header or from the command center. You can preview the input, choose which Tabs to use, and generate again. A generated name is applied right away. To apply a name you edited by hand, click "应用" (Apply) next to the name field.
|
|
4
4
|
|
|
5
5
|
The settings screen holds one naming prompt, model, maximum input budget, name length, and timeout for all workspaces. You pick the provider and model from the providers already enabled on this Paseo host. The plugin has no endpoint of its own and never handles API keys.
|
|
6
6
|
|
package/OVERVIEW.zh-CN.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Tab Namer
|
|
2
2
|
|
|
3
|
-
根据 Tab 内容为已有 workspace 生成新名称。通过顶部 ✨ 按钮或命令中心打开。可以预览输入、选择 Tab
|
|
3
|
+
根据 Tab 内容为已有 workspace 生成新名称。通过顶部 ✨ 按钮或命令中心打开。可以预览输入、选择 Tab 范围和反复生成;生成成功后立即自动应用。手动编辑后可点击输入框旁的“应用”。
|
|
4
4
|
|
|
5
5
|
设置页统一配置命名提示词、模型、最大输入预算、名称长度和超时。服务和模型从此主机 Paseo 已启用的服务列表中选择,不绑定特定接口,插件不接触任何密钥。
|
|
6
6
|
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://github.com/stv1024/paseo-tab-namer/releases"><img alt="Version" src="https://img.shields.io/badge/version-0.
|
|
6
|
+
<a href="https://github.com/stv1024/paseo-tab-namer/releases"><img alt="Version" src="https://img.shields.io/badge/version-0.2.0-20744a?style=flat-square" /></a>
|
|
7
7
|
<img alt="Paseo" src="https://img.shields.io/badge/paseo-%E2%89%A5%200.11.2-7ccba0?style=flat-square" />
|
|
8
8
|
<img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-strict-3178c6?style=flat-square&logo=typescript&logoColor=white" />
|
|
9
9
|
<img alt="Tests" src="https://img.shields.io/badge/tests-node%3Atest-555?style=flat-square" />
|
|
@@ -57,11 +57,11 @@ On first use, open **Settings → Tab Namer**, pick a **provider** and a **model
|
|
|
57
57
|
## Usage
|
|
58
58
|
|
|
59
59
|
1. Click the **✨ Tab Namer** button in a workspace header.
|
|
60
|
-
2.
|
|
61
|
-
3.
|
|
62
|
-
4. To fine-tune by hand, edit the field and click
|
|
60
|
+
2. Click the large **生成名称** (Generate) button at the top of the popover, right below the header button. It works as soon as the popover opens. The name is applied right away. **再次生成** (Regenerate), in the same place, gives you another one.
|
|
61
|
+
3. To change what the name is based on, pick a scope first: **最近 Tab** (most recent), **全部 Tab** (all) or **指定 Tab** (a specific Tab).
|
|
62
|
+
4. To fine-tune by hand, edit the field and click **应用** (Apply) or press Enter.
|
|
63
63
|
|
|
64
|
-
Under **更多** (More), **预览输入** (Preview input) shows exactly what will be sent and how much of the budget it uses, without calling the model.
|
|
64
|
+
At the bottom of the popover, **设置** (Settings) opens the settings. Under **更多** (More), **预览输入** (Preview input) shows exactly what will be sent and how much of the budget it uses, without calling the model.
|
|
65
65
|
|
|
66
66
|
Other entry points:
|
|
67
67
|
|
package/README.zh-CN.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://github.com/stv1024/paseo-tab-namer/releases"><img alt="Version" src="https://img.shields.io/badge/version-0.
|
|
6
|
+
<a href="https://github.com/stv1024/paseo-tab-namer/releases"><img alt="Version" src="https://img.shields.io/badge/version-0.2.0-20744a?style=flat-square" /></a>
|
|
7
7
|
<img alt="Paseo" src="https://img.shields.io/badge/paseo-%E2%89%A5%200.11.2-7ccba0?style=flat-square" />
|
|
8
8
|
<img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-strict-3178c6?style=flat-square&logo=typescript&logoColor=white" />
|
|
9
9
|
<img alt="Tests" src="https://img.shields.io/badge/tests-node%3Atest-555?style=flat-square" />
|
|
@@ -54,11 +54,11 @@ paseo plugin install git:stv1024/paseo-tab-namer
|
|
|
54
54
|
## 使用
|
|
55
55
|
|
|
56
56
|
1. 点击 workspace 顶部的 **✨ Tab Namer** 按钮。
|
|
57
|
-
2.
|
|
58
|
-
3.
|
|
59
|
-
4. 如需手动微调,修改输入框后点击
|
|
57
|
+
2. 点击弹窗最上方的大按钮 **生成名称**(就在顶部按钮正下方,弹窗一打开就能点),名称会立即应用;在同一位置点 **再次生成** 可换一个。
|
|
58
|
+
3. 如需改变命名依据,先选择范围:**最近 Tab**、**全部 Tab** 或 **指定 Tab**。
|
|
59
|
+
4. 如需手动微调,修改输入框后点击 **应用** 或按回车。
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
弹窗底部的 **设置** 可打开设置。展开 **更多** 后点 **预览输入**,可查看将要发送的内容和预算占用,不调用模型。
|
|
62
62
|
|
|
63
63
|
其他入口:
|
|
64
64
|
|
package/client/rename.tsx
CHANGED
|
@@ -72,21 +72,32 @@ export function RenameForm({
|
|
|
72
72
|
const [prompt, setPrompt] = useState("");
|
|
73
73
|
const [previousTitle, setPreviousTitle] = useState("");
|
|
74
74
|
const [showMore, setShowMore] = useState(false);
|
|
75
|
+
// Until the user picks a scope, follow the saved default. Generating does
|
|
76
|
+
// not need the workspace detail, so a click only waits for these settings.
|
|
77
|
+
const scope =
|
|
78
|
+
scopeTouched || preferences.status !== "ready"
|
|
79
|
+
? selection.scope
|
|
80
|
+
: preferences.values.scope;
|
|
81
|
+
const scopeReady = scopeTouched || preferences.status !== "loading";
|
|
82
|
+
const [queued, setQueued] = useState(false);
|
|
75
83
|
useEffect(() => {
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
if (queued && scopeReady) {
|
|
85
|
+
setQueued(false);
|
|
86
|
+
void act("generate");
|
|
87
|
+
}
|
|
88
|
+
}, [queued, scopeReady]);
|
|
89
|
+
// A generation can finish before the first detail load does.
|
|
90
|
+
const generated = useRef<string | null>(null);
|
|
82
91
|
useEffect(() => {
|
|
83
92
|
let active = true;
|
|
84
93
|
void load({ workspaceId })
|
|
85
94
|
.then((d) => {
|
|
86
|
-
if (active)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
if (!active) return;
|
|
96
|
+
const title = generated.current;
|
|
97
|
+
setDetail(
|
|
98
|
+
title ? { ...d, workspace: { ...d.workspace, title } } : d,
|
|
99
|
+
);
|
|
100
|
+
setTitle(title ?? d.workspace.title ?? d.workspace.name);
|
|
90
101
|
})
|
|
91
102
|
.catch((e) => {
|
|
92
103
|
if (active) setError(e.message);
|
|
@@ -113,6 +124,7 @@ export function RenameForm({
|
|
|
113
124
|
if (kind === "preview" || kind === "generate") {
|
|
114
125
|
const { jobId } = await start({
|
|
115
126
|
...selection,
|
|
127
|
+
scope,
|
|
116
128
|
kind,
|
|
117
129
|
...(previousTitle ? { previousTitle } : {}),
|
|
118
130
|
});
|
|
@@ -129,6 +141,7 @@ export function RenameForm({
|
|
|
129
141
|
setUsage(`模型:${r.model}`);
|
|
130
142
|
setNotice("输入已准备好,尚未调用模型。");
|
|
131
143
|
} else {
|
|
144
|
+
generated.current = r.title!;
|
|
132
145
|
setTitle(r.title!);
|
|
133
146
|
setDetail((current) =>
|
|
134
147
|
current
|
|
@@ -185,30 +198,43 @@ export function RenameForm({
|
|
|
185
198
|
}
|
|
186
199
|
}
|
|
187
200
|
const c = theme.colors;
|
|
201
|
+
const currentTitle = detail
|
|
202
|
+
? (detail.workspace.title ?? detail.workspace.name)
|
|
203
|
+
: "";
|
|
204
|
+
const edited = !!detail && !!title.trim() && title.trim() !== currentTitle;
|
|
188
205
|
return (
|
|
189
206
|
<View style={{ gap: 14, width: "100%" }}>
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
207
|
+
{/* A large primary action at the very top, right under the header
|
|
208
|
+
button that opens the popover, so the second click needs no aim. */}
|
|
209
|
+
<Button
|
|
210
|
+
theme={theme}
|
|
211
|
+
large
|
|
212
|
+
primary
|
|
213
|
+
disabled={!!busy || queued || (!!detail && !detail.agents.length)}
|
|
214
|
+
onPress={() => {
|
|
215
|
+
if (scopeReady) void act("generate");
|
|
216
|
+
else setQueued(true);
|
|
196
217
|
}}
|
|
197
218
|
>
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
219
|
+
{busy === "generate" || queued
|
|
220
|
+
? "正在生成…"
|
|
221
|
+
: previousTitle
|
|
222
|
+
? "再次生成"
|
|
223
|
+
: "生成名称"}
|
|
224
|
+
</Button>
|
|
225
|
+
<Text
|
|
226
|
+
numberOfLines={1}
|
|
227
|
+
style={{ color: c.foregroundMuted, fontSize: 13, lineHeight: 20 }}
|
|
228
|
+
>
|
|
229
|
+
Tab Namer{detail ? ` · ${detail.workspace.project}` : ""}
|
|
230
|
+
</Text>
|
|
205
231
|
{detail ? (
|
|
206
232
|
<>
|
|
207
|
-
<Note theme={theme}>{detail.workspace.project}</Note>
|
|
208
233
|
<Choices
|
|
209
234
|
theme={theme}
|
|
210
|
-
|
|
211
|
-
|
|
235
|
+
small
|
|
236
|
+
disabled={!!busy || queued}
|
|
237
|
+
value={scope}
|
|
212
238
|
onChange={(scope) => {
|
|
213
239
|
setScopeTouched(true);
|
|
214
240
|
setSelection((s) => ({
|
|
@@ -226,12 +252,7 @@ export function RenameForm({
|
|
|
226
252
|
{ value: "selected", label: "指定 Tab" },
|
|
227
253
|
]}
|
|
228
254
|
/>
|
|
229
|
-
{
|
|
230
|
-
<Note theme={theme}>
|
|
231
|
-
最近 Tab:{detail.agents[0]?.title ?? "无 Tab"}
|
|
232
|
-
</Note>
|
|
233
|
-
)}
|
|
234
|
-
{selection.scope === "selected" && (
|
|
255
|
+
{scope === "selected" && (
|
|
235
256
|
<ScrollView style={{ maxHeight: 150 }} nestedScrollEnabled>
|
|
236
257
|
{detail.agents.map((a) => (
|
|
237
258
|
<Pressable
|
|
@@ -265,59 +286,62 @@ export function RenameForm({
|
|
|
265
286
|
value={title}
|
|
266
287
|
onChange={setTitle}
|
|
267
288
|
disabled={!!busy}
|
|
289
|
+
onSubmit={() => {
|
|
290
|
+
if (edited) void act("save");
|
|
291
|
+
}}
|
|
292
|
+
accessory={
|
|
293
|
+
edited ? (
|
|
294
|
+
<Button
|
|
295
|
+
theme={theme}
|
|
296
|
+
disabled={!!busy}
|
|
297
|
+
onPress={() => {
|
|
298
|
+
void act("save");
|
|
299
|
+
}}
|
|
300
|
+
>
|
|
301
|
+
{busy === "save" ? "正在应用…" : "应用"}
|
|
302
|
+
</Button>
|
|
303
|
+
) : undefined
|
|
304
|
+
}
|
|
268
305
|
/>
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
accessibilityState={{ expanded: showMore }}
|
|
306
|
-
onPress={() => setShowMore((value) => !value)}
|
|
307
|
-
style={{
|
|
308
|
-
paddingHorizontal: 10,
|
|
309
|
-
paddingVertical: 10,
|
|
310
|
-
justifyContent: "center",
|
|
311
|
-
}}
|
|
312
|
-
>
|
|
313
|
-
<Text style={{ color: c.foregroundMuted }}>
|
|
314
|
-
{showMore ? "收起" : "更多"}
|
|
315
|
-
</Text>
|
|
316
|
-
</Pressable>
|
|
317
|
-
</View>
|
|
318
|
-
{showMore && (
|
|
306
|
+
</>
|
|
307
|
+
) : null}
|
|
308
|
+
{error ? (
|
|
309
|
+
<View style={{ gap: 8, alignItems: "flex-start" }}>
|
|
310
|
+
<Note theme={theme} error>
|
|
311
|
+
{error}
|
|
312
|
+
</Note>
|
|
313
|
+
<Button
|
|
314
|
+
theme={theme}
|
|
315
|
+
small
|
|
316
|
+
disabled={!!busy}
|
|
317
|
+
onPress={() => {
|
|
318
|
+
void act("reload");
|
|
319
|
+
}}
|
|
320
|
+
>
|
|
321
|
+
{detail ? "刷新状态" : "重试"}
|
|
322
|
+
</Button>
|
|
323
|
+
</View>
|
|
324
|
+
) : (
|
|
325
|
+
<Note theme={theme}>
|
|
326
|
+
{notice ||
|
|
327
|
+
(queued
|
|
328
|
+
? "正在准备…"
|
|
329
|
+
: detail
|
|
330
|
+
? "生成后会直接应用新名称。"
|
|
331
|
+
: "正在加载 workspace…")}
|
|
332
|
+
</Note>
|
|
333
|
+
)}
|
|
334
|
+
{detail && showMore && (
|
|
335
|
+
<View style={{ gap: 10 }}>
|
|
336
|
+
{scope === "latest" && (
|
|
337
|
+
<Note theme={theme}>
|
|
338
|
+
最近 Tab:{detail.agents[0]?.title ?? "无 Tab"}
|
|
339
|
+
</Note>
|
|
340
|
+
)}
|
|
341
|
+
<View style={{ alignItems: "flex-start" }}>
|
|
319
342
|
<Button
|
|
320
343
|
theme={theme}
|
|
344
|
+
small
|
|
321
345
|
disabled={!!busy || !detail.agents.length}
|
|
322
346
|
onPress={() => {
|
|
323
347
|
void act("preview");
|
|
@@ -325,8 +349,8 @@ export function RenameForm({
|
|
|
325
349
|
>
|
|
326
350
|
{busy === "preview" ? "正在读取…" : "预览输入"}
|
|
327
351
|
</Button>
|
|
328
|
-
|
|
329
|
-
{
|
|
352
|
+
</View>
|
|
353
|
+
{stats && (
|
|
330
354
|
<View
|
|
331
355
|
style={{
|
|
332
356
|
backgroundColor: c.surface1,
|
|
@@ -353,7 +377,7 @@ export function RenameForm({
|
|
|
353
377
|
))}
|
|
354
378
|
</View>
|
|
355
379
|
)}
|
|
356
|
-
{
|
|
380
|
+
{!!prompt && (
|
|
357
381
|
<View style={{ gap: 8 }}>
|
|
358
382
|
<Note theme={theme}>
|
|
359
383
|
以下为本次准备的输入。生成时会重新读取 Tab 中的最新内容。
|
|
@@ -379,36 +403,49 @@ export function RenameForm({
|
|
|
379
403
|
{prompt}
|
|
380
404
|
</Text>
|
|
381
405
|
</ScrollView>
|
|
382
|
-
<
|
|
383
|
-
|
|
384
|
-
|
|
406
|
+
<View style={{ alignItems: "flex-start" }}>
|
|
407
|
+
<Button theme={theme} small onPress={() => setPrompt("")}>
|
|
408
|
+
收起预览
|
|
409
|
+
</Button>
|
|
410
|
+
</View>
|
|
385
411
|
</View>
|
|
386
412
|
)}
|
|
387
|
-
|
|
388
|
-
) : (
|
|
389
|
-
<>
|
|
390
|
-
<Note theme={theme}>
|
|
391
|
-
{error ? "Workspace 加载失败。" : "正在加载 workspace…"}
|
|
392
|
-
</Note>
|
|
393
|
-
</>
|
|
413
|
+
</View>
|
|
394
414
|
)}
|
|
395
|
-
{
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
415
|
+
{/* Rarely used actions stay out of the way, at the bottom. */}
|
|
416
|
+
<View
|
|
417
|
+
style={{
|
|
418
|
+
flexDirection: "row",
|
|
419
|
+
justifyContent: "space-between",
|
|
420
|
+
alignItems: "center",
|
|
421
|
+
borderTopWidth: 1,
|
|
422
|
+
borderTopColor: c.border,
|
|
423
|
+
paddingTop: 8,
|
|
424
|
+
marginHorizontal: -4,
|
|
425
|
+
}}
|
|
426
|
+
>
|
|
427
|
+
{detail ? (
|
|
400
428
|
<Button
|
|
401
429
|
theme={theme}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}}
|
|
430
|
+
small
|
|
431
|
+
ghost
|
|
432
|
+
onPress={() => setShowMore((value) => !value)}
|
|
406
433
|
>
|
|
407
|
-
{
|
|
434
|
+
{showMore ? "收起" : "更多"}
|
|
408
435
|
</Button>
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
436
|
+
) : (
|
|
437
|
+
<View />
|
|
438
|
+
)}
|
|
439
|
+
<Button
|
|
440
|
+
theme={theme}
|
|
441
|
+
small
|
|
442
|
+
ghost
|
|
443
|
+
disabled={!!busy}
|
|
444
|
+
onPress={openSettings}
|
|
445
|
+
>
|
|
446
|
+
设置
|
|
447
|
+
</Button>
|
|
448
|
+
</View>
|
|
412
449
|
</View>
|
|
413
450
|
);
|
|
414
451
|
}
|
package/client/ui.tsx
CHANGED
|
@@ -8,12 +8,19 @@ export function Button({
|
|
|
8
8
|
onPress,
|
|
9
9
|
disabled = false,
|
|
10
10
|
primary = false,
|
|
11
|
+
small = false,
|
|
12
|
+
large = false,
|
|
13
|
+
ghost = false,
|
|
11
14
|
}: {
|
|
12
15
|
theme: Theme;
|
|
13
16
|
children: string;
|
|
14
17
|
onPress(): void;
|
|
15
18
|
disabled?: boolean;
|
|
16
19
|
primary?: boolean;
|
|
20
|
+
small?: boolean;
|
|
21
|
+
/** A tall target for the main action of a surface. */
|
|
22
|
+
large?: boolean;
|
|
23
|
+
ghost?: boolean;
|
|
17
24
|
}) {
|
|
18
25
|
const c = theme.colors;
|
|
19
26
|
return (
|
|
@@ -23,19 +30,31 @@ export function Button({
|
|
|
23
30
|
disabled={disabled}
|
|
24
31
|
onPress={onPress}
|
|
25
32
|
style={{
|
|
26
|
-
paddingHorizontal: 14,
|
|
27
|
-
paddingVertical: 10,
|
|
28
|
-
|
|
33
|
+
paddingHorizontal: small ? 10 : 14,
|
|
34
|
+
paddingVertical: small ? 6 : 10,
|
|
35
|
+
minHeight: large ? 56 : undefined,
|
|
36
|
+
borderRadius: small ? 6 : large ? 10 : 8,
|
|
29
37
|
borderWidth: 1,
|
|
30
|
-
borderColor: primary ? c.accent : c.border,
|
|
31
|
-
backgroundColor: primary
|
|
38
|
+
borderColor: primary ? c.accent : ghost ? "transparent" : c.border,
|
|
39
|
+
backgroundColor: primary
|
|
40
|
+
? c.accent
|
|
41
|
+
: ghost
|
|
42
|
+
? "transparent"
|
|
43
|
+
: c.surface1,
|
|
44
|
+
alignItems: "center",
|
|
45
|
+
justifyContent: "center",
|
|
32
46
|
opacity: disabled ? 0.45 : 1,
|
|
33
47
|
}}
|
|
34
48
|
>
|
|
35
49
|
<Text
|
|
36
50
|
style={{
|
|
37
|
-
color: primary
|
|
38
|
-
|
|
51
|
+
color: primary
|
|
52
|
+
? c.accentForeground
|
|
53
|
+
: ghost
|
|
54
|
+
? c.foregroundMuted
|
|
55
|
+
: c.foreground,
|
|
56
|
+
fontWeight: ghost ? "400" : "600",
|
|
57
|
+
fontSize: small ? 13 : large ? 16 : 14,
|
|
39
58
|
}}
|
|
40
59
|
>
|
|
41
60
|
{children}
|
|
@@ -50,6 +69,8 @@ export function Field({
|
|
|
50
69
|
onChange,
|
|
51
70
|
multiline = false,
|
|
52
71
|
disabled = false,
|
|
72
|
+
onSubmit,
|
|
73
|
+
accessory,
|
|
53
74
|
}: {
|
|
54
75
|
theme: Theme;
|
|
55
76
|
label: string;
|
|
@@ -57,32 +78,40 @@ export function Field({
|
|
|
57
78
|
onChange(v: string): void;
|
|
58
79
|
multiline?: boolean;
|
|
59
80
|
disabled?: boolean;
|
|
81
|
+
onSubmit?(): void;
|
|
82
|
+
/** Shown to the right of a single-line input, e.g. an apply button. */
|
|
83
|
+
accessory?: ReactNode;
|
|
60
84
|
}) {
|
|
61
85
|
const c = theme.colors;
|
|
62
86
|
return (
|
|
63
87
|
<View style={{ gap: 7 }}>
|
|
64
88
|
<Text style={{ color: c.foreground, fontWeight: "600" }}>{label}</Text>
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
|
|
90
|
+
<TextInput
|
|
91
|
+
accessibilityLabel={label}
|
|
92
|
+
value={value}
|
|
93
|
+
onChangeText={onChange}
|
|
94
|
+
onSubmitEditing={onSubmit}
|
|
95
|
+
multiline={multiline}
|
|
96
|
+
editable={!disabled}
|
|
97
|
+
autoCapitalize="none"
|
|
98
|
+
autoCorrect={false}
|
|
99
|
+
style={{
|
|
100
|
+
flex: 1,
|
|
101
|
+
borderWidth: 1,
|
|
102
|
+
borderColor: c.border,
|
|
103
|
+
borderRadius: 8,
|
|
104
|
+
padding: 12,
|
|
105
|
+
minHeight: multiline ? 160 : 44,
|
|
106
|
+
textAlignVertical: "top",
|
|
107
|
+
color: c.foreground,
|
|
108
|
+
backgroundColor: c.surface1,
|
|
109
|
+
fontSize: 14,
|
|
110
|
+
lineHeight: 21,
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
{accessory}
|
|
114
|
+
</View>
|
|
86
115
|
</View>
|
|
87
116
|
);
|
|
88
117
|
}
|
|
@@ -114,20 +143,25 @@ export function Choices<T extends string>({
|
|
|
114
143
|
options,
|
|
115
144
|
onChange,
|
|
116
145
|
disabled = false,
|
|
146
|
+
small = false,
|
|
117
147
|
}: {
|
|
118
148
|
theme: Theme;
|
|
119
149
|
value: T;
|
|
120
150
|
options: { value: T; label: string }[];
|
|
121
151
|
onChange(value: T): void;
|
|
122
152
|
disabled?: boolean;
|
|
153
|
+
small?: boolean;
|
|
123
154
|
}) {
|
|
124
155
|
return (
|
|
125
|
-
<View
|
|
156
|
+
<View
|
|
157
|
+
style={{ flexDirection: "row", flexWrap: "wrap", gap: small ? 6 : 8 }}
|
|
158
|
+
>
|
|
126
159
|
{options.map((o) => (
|
|
127
160
|
<Button
|
|
128
161
|
key={o.value}
|
|
129
162
|
theme={theme}
|
|
130
163
|
primary={value === o.value}
|
|
164
|
+
small={small}
|
|
131
165
|
disabled={disabled}
|
|
132
166
|
onPress={() => onChange(o.value)}
|
|
133
167
|
>
|
package/docs/images/popover.png
CHANGED
|
Binary file
|
package/index.client.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { View } from "react-native";
|
|
2
2
|
import type {
|
|
3
3
|
PluginClientContext,
|
|
4
4
|
PluginScreenProps,
|
|
@@ -11,10 +11,15 @@ export default function contribute(client: PluginClientContext) {
|
|
|
11
11
|
const Screen = (props: PluginScreenProps) => (
|
|
12
12
|
<RenameScreen {...props} openSettings={openSettings} />
|
|
13
13
|
);
|
|
14
|
+
// The host surface is at most 420 wide, pads its content and scrolls it,
|
|
15
|
+
// so the popover only sets a width that fits inside that padding.
|
|
14
16
|
const Popover = (props: PluginButtonContentProps) => (
|
|
15
|
-
<
|
|
16
|
-
style={{
|
|
17
|
-
|
|
17
|
+
<View
|
|
18
|
+
style={{
|
|
19
|
+
width: props.layout.compact ? "100%" : 392,
|
|
20
|
+
maxWidth: "100%",
|
|
21
|
+
padding: 2,
|
|
22
|
+
}}
|
|
18
23
|
>
|
|
19
24
|
<RenameForm
|
|
20
25
|
key={props.workspaceId}
|
|
@@ -25,7 +30,7 @@ export default function contribute(client: PluginClientContext) {
|
|
|
25
30
|
openSettings();
|
|
26
31
|
}}
|
|
27
32
|
/>
|
|
28
|
-
</
|
|
33
|
+
</View>
|
|
29
34
|
);
|
|
30
35
|
client.addScreen({ id: "rename", title: "Tab Namer", Component: Screen });
|
|
31
36
|
client.addSettingsScreen({
|
package/package.json
CHANGED