termcast 1.3.46 → 1.3.47
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/build.d.ts.map +1 -1
- package/dist/build.js +12 -3
- package/dist/build.js.map +1 -1
- package/dist/components/actions.d.ts +18 -0
- package/dist/components/actions.d.ts.map +1 -1
- package/dist/components/actions.js +70 -5
- package/dist/components/actions.js.map +1 -1
- package/dist/components/detail.d.ts.map +1 -1
- package/dist/components/detail.js +3 -1
- package/dist/components/detail.js.map +1 -1
- package/dist/components/dropdown.d.ts.map +1 -1
- package/dist/components/dropdown.js +40 -11
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/form/dropdown.d.ts.map +1 -1
- package/dist/components/form/dropdown.js +26 -10
- package/dist/components/form/dropdown.js.map +1 -1
- package/dist/components/list.d.ts +7 -0
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +82 -15
- package/dist/components/list.js.map +1 -1
- package/dist/components/metadata.d.ts.map +1 -1
- package/dist/components/metadata.js +2 -1
- package/dist/components/metadata.js.map +1 -1
- package/dist/components/spinner.d.ts +6 -0
- package/dist/components/spinner.d.ts.map +1 -0
- package/dist/components/spinner.js +12 -0
- package/dist/components/spinner.js.map +1 -0
- package/dist/examples/action-shortcut.d.ts +2 -0
- package/dist/examples/action-shortcut.d.ts.map +1 -0
- package/dist/examples/action-shortcut.js +20 -0
- package/dist/examples/action-shortcut.js.map +1 -0
- package/dist/examples/internal/scrollbox-with-descendants.js +19 -8
- package/dist/examples/internal/scrollbox-with-descendants.js.map +1 -1
- package/dist/examples/list-spacing-default.d.ts +5 -0
- package/dist/examples/list-spacing-default.d.ts.map +1 -0
- package/dist/examples/list-spacing-default.js +10 -0
- package/dist/examples/list-spacing-default.js.map +1 -0
- package/dist/examples/list-spacing-mode.d.ts +10 -0
- package/dist/examples/list-spacing-mode.d.ts.map +1 -0
- package/dist/examples/list-spacing-mode.js +26 -0
- package/dist/examples/list-spacing-mode.js.map +1 -0
- package/dist/examples/list-spacing-relaxed.d.ts +5 -0
- package/dist/examples/list-spacing-relaxed.d.ts.map +1 -0
- package/dist/examples/list-spacing-relaxed.js +10 -0
- package/dist/examples/list-spacing-relaxed.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/state.d.ts +9 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +2 -0
- package/dist/state.js.map +1 -1
- package/package.json +3 -3
- package/src/build.tsx +12 -3
- package/src/components/actions.tsx +77 -5
- package/src/components/detail.tsx +3 -1
- package/src/components/dropdown.tsx +49 -11
- package/src/components/form/dropdown.tsx +32 -10
- package/src/components/list.tsx +157 -18
- package/src/components/metadata.tsx +3 -2
- package/src/components/spinner.tsx +25 -0
- package/src/examples/action-shortcut.tsx +53 -0
- package/src/examples/action-shortcut.vitest.tsx +178 -0
- package/src/examples/actions-context.vitest.tsx +4 -4
- package/src/examples/detail-metadata-showcase.vitest.tsx +17 -3
- package/src/examples/form-dropdown.vitest.tsx +8 -8
- package/src/examples/form-tagpicker.vitest.tsx +4 -4
- package/src/examples/github.vitest.tsx +16 -9
- package/src/examples/internal/scrollbox-with-descendants.tsx +27 -8
- package/src/examples/list-detail-metadata.vitest.tsx +3 -1
- package/src/examples/list-loading-empty-view.vitest.tsx +5 -5
- package/src/examples/list-scrollbox.vitest.tsx +5 -5
- package/src/examples/list-spacing-default.tsx +38 -0
- package/src/examples/list-spacing-mode.tsx +137 -0
- package/src/examples/list-spacing-mode.vitest.tsx +158 -0
- package/src/examples/list-spacing-relaxed.tsx +38 -0
- package/src/examples/list-with-detail.vitest.tsx +63 -28
- package/src/examples/list-with-sections.vitest.tsx +13 -13
- package/src/examples/simple-file-picker.vitest.tsx +1 -1
- package/src/examples/simple-grid.vitest.tsx +42 -35
- package/src/examples/simple-navigation.vitest.tsx +14 -14
- package/src/extensions/dev.vitest.tsx +9 -3
- package/src/index.tsx +2 -0
- package/src/state.tsx +13 -0
|
@@ -351,11 +351,11 @@ test('form dropdown keyboard navigation', async () => {
|
|
|
351
351
|
◆ Programming Languages █
|
|
352
352
|
│ TypeScript, Rust █
|
|
353
353
|
│ █
|
|
354
|
-
│ ○ React █
|
|
355
|
-
│ ○ Vue █
|
|
356
354
|
│› ○ Svelte █
|
|
357
355
|
│ Backend █
|
|
358
356
|
│ ○ Node.js █
|
|
357
|
+
│ ○ Python █
|
|
358
|
+
│ ○ Go █
|
|
359
359
|
│ █
|
|
360
360
|
│ Choose your preferred programming languages █
|
|
361
361
|
│ █
|
|
@@ -465,11 +465,11 @@ test('form dropdown keyboard navigation', async () => {
|
|
|
465
465
|
◆ Programming Languages █
|
|
466
466
|
│ TypeScript, Rust █
|
|
467
467
|
│ █
|
|
468
|
-
│ ○ React █
|
|
469
|
-
│ ○ Vue █
|
|
470
468
|
│› ○ Svelte █
|
|
471
469
|
│ Backend █
|
|
472
470
|
│ ○ Node.js █
|
|
471
|
+
│ ○ Python █
|
|
472
|
+
│ ○ Go █
|
|
473
473
|
│ █
|
|
474
474
|
│ Choose your preferred programming languages █
|
|
475
475
|
│ █
|
|
@@ -522,11 +522,11 @@ test('form dropdown keyboard navigation', async () => {
|
|
|
522
522
|
◆ Programming Languages █
|
|
523
523
|
│ TypeScript, Rust █
|
|
524
524
|
│ █
|
|
525
|
-
│ ○ React █
|
|
526
|
-
│ ○ Vue █
|
|
527
525
|
│› ○ Svelte █
|
|
528
526
|
│ Backend █
|
|
529
527
|
│ ○ Node.js █
|
|
528
|
+
│ ○ Python █
|
|
529
|
+
│ ○ Go █
|
|
530
530
|
│ █
|
|
531
531
|
│ Choose your preferred programming languages █
|
|
532
532
|
│ █
|
|
@@ -715,11 +715,11 @@ test('selecting second-to-last visible item should not scroll', async () => {
|
|
|
715
715
|
◆ Programming Languages █
|
|
716
716
|
│ TypeScript, Rust █
|
|
717
717
|
│ █
|
|
718
|
+
│ Frontend █
|
|
718
719
|
│ ● TypeScript █
|
|
719
720
|
│ ○ JavaScript █
|
|
720
721
|
│› ○ React █
|
|
721
722
|
│ ○ Vue █
|
|
722
|
-
│ ○ Svelte █
|
|
723
723
|
│ █
|
|
724
724
|
│ Choose your preferred programming languages █
|
|
725
725
|
│ █
|
|
@@ -772,11 +772,11 @@ test('selecting second-to-last visible item should not scroll', async () => {
|
|
|
772
772
|
◆ Programming Languages █
|
|
773
773
|
│ TypeScript, Rust, React █
|
|
774
774
|
│ █
|
|
775
|
+
│ Frontend █
|
|
775
776
|
│ ● TypeScript █
|
|
776
777
|
│ ○ JavaScript █
|
|
777
778
|
│› ● React █
|
|
778
779
|
│ ○ Vue █
|
|
779
|
-
│ ○ Svelte █
|
|
780
780
|
│ █
|
|
781
781
|
│ Choose your preferred programming languages █
|
|
782
782
|
│ █
|
|
@@ -395,11 +395,11 @@ test('form tagpicker keyboard navigation', async () => {
|
|
|
395
395
|
◆ Favorite Sport
|
|
396
396
|
│ Choose your favorite sport...
|
|
397
397
|
│
|
|
398
|
+
│ ○ Basketball
|
|
399
|
+
│ ○ Football
|
|
398
400
|
│ ○ Tennis
|
|
399
401
|
│ ○ Baseball
|
|
400
402
|
│› ○ Golf
|
|
401
|
-
│ ○ Swimming
|
|
402
|
-
│ ○ Cycling
|
|
403
403
|
│
|
|
404
404
|
│ Select your favorite sport from the list
|
|
405
405
|
│
|
|
@@ -509,11 +509,11 @@ test('form tagpicker keyboard navigation', async () => {
|
|
|
509
509
|
◆ Favorite Sport
|
|
510
510
|
│ Choose your favorite sport...
|
|
511
511
|
│
|
|
512
|
-
│ ○ Tennis
|
|
513
512
|
│ ○ Baseball
|
|
514
513
|
│› ○ Golf
|
|
515
514
|
│ ○ Swimming
|
|
516
515
|
│ ○ Cycling
|
|
516
|
+
│ ○ Running
|
|
517
517
|
│
|
|
518
518
|
│ Select your favorite sport from the list
|
|
519
519
|
│
|
|
@@ -566,11 +566,11 @@ test('form tagpicker keyboard navigation', async () => {
|
|
|
566
566
|
◆ Favorite Sport
|
|
567
567
|
│ Choose your favorite sport...
|
|
568
568
|
│
|
|
569
|
-
│ ○ Tennis
|
|
570
569
|
│ ○ Baseball
|
|
571
570
|
│› ○ Golf
|
|
572
571
|
│ ○ Swimming
|
|
573
572
|
│ ○ Cycling
|
|
573
|
+
│ ○ Running
|
|
574
574
|
│
|
|
575
575
|
│ Select your favorite sport from the list
|
|
576
576
|
│
|
|
@@ -61,6 +61,13 @@ test.skipIf(!extensionExists)('github extension shows command list on launch', a
|
|
|
61
61
|
timeout: 30000,
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
+
// Wait for the full command list to render.
|
|
65
|
+
// The list can paint the first item before all descendants are registered.
|
|
66
|
+
await session.text({
|
|
67
|
+
waitFor: (text) => text.includes('My Pull Requests') && text.includes('Search Repositories'),
|
|
68
|
+
timeout: 30000,
|
|
69
|
+
})
|
|
70
|
+
|
|
64
71
|
expect(initialView).toContain('My Pull Requests')
|
|
65
72
|
expect(initialView).toContain('Search Repositories')
|
|
66
73
|
expect(initialView).toMatchInlineSnapshot(`
|
|
@@ -101,7 +108,7 @@ test.skipIf(!extensionExists)('github extension shows command list on launch', a
|
|
|
101
108
|
test.skipIf(!extensionExists)('github extension can navigate commands', async () => {
|
|
102
109
|
// Wait for command list
|
|
103
110
|
await session.text({
|
|
104
|
-
waitFor: (text) =>
|
|
111
|
+
waitFor: (text) => text.includes('My Pull Requests') && text.includes('Search Repositories'),
|
|
105
112
|
timeout: 30000,
|
|
106
113
|
})
|
|
107
114
|
|
|
@@ -172,26 +179,26 @@ test.skipIf(!extensionExists)('github extension can open actions panel', async (
|
|
|
172
179
|
|
|
173
180
|
> Search commands...
|
|
174
181
|
|
|
175
|
-
Commands
|
|
176
|
-
›My Pull Requests List pull requests you created, participated in, or view
|
|
177
182
|
╭──────────────────────────────────────────────────────────────────────────╮
|
|
178
183
|
│ │
|
|
179
184
|
│ Actions esc │
|
|
180
185
|
│ │
|
|
181
186
|
│ > Search actions... │
|
|
182
187
|
│ │
|
|
183
|
-
│ ›Run Command
|
|
184
|
-
│ Copy File Path
|
|
185
|
-
│ Copy Command Info
|
|
186
|
-
│
|
|
188
|
+
│ ›Run Command │
|
|
189
|
+
│ Copy File Path │
|
|
190
|
+
│ Copy Command Info │
|
|
191
|
+
│ │
|
|
187
192
|
│ Settings │
|
|
188
|
-
│ Configure GitHub...
|
|
193
|
+
│ Configure GitHub... ⌃⇧, │
|
|
189
194
|
│ Change Theme... │
|
|
195
|
+
│ See Console Logs │
|
|
196
|
+
│ │
|
|
197
|
+
│ │
|
|
190
198
|
│ │
|
|
191
199
|
│ ↵ select ↑↓ navigate │
|
|
192
200
|
│ │
|
|
193
201
|
╰──────────────────────────────────────────────────────────────────────────╯
|
|
194
|
-
↵ run command ↑↓ navigate ^k actions powered by termcast
|
|
195
202
|
|
|
196
203
|
|
|
197
204
|
|
|
@@ -16,20 +16,39 @@ function ScrollboxWithDescendants() {
|
|
|
16
16
|
const [selectedIndex, setSelectedIndex] = React.useState(0)
|
|
17
17
|
const scrollBoxRef = React.useRef<any>(null)
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
const scrollToItemIfNeeded = ({
|
|
20
|
+
item,
|
|
21
|
+
direction,
|
|
22
|
+
}: {
|
|
23
|
+
item: { props?: ItemDescendant }
|
|
24
|
+
direction: -1 | 1
|
|
25
|
+
}) => {
|
|
20
26
|
const scrollBox = scrollBoxRef.current
|
|
21
27
|
const elementRef = item.props?.elementRef
|
|
22
28
|
if (!scrollBox || !elementRef) return
|
|
23
29
|
|
|
24
30
|
const contentY = scrollBox.content?.y || 0
|
|
31
|
+
const scrollTop = scrollBox.scrollTop || 0
|
|
25
32
|
const viewportHeight = scrollBox.viewport?.height || 10
|
|
26
33
|
|
|
27
|
-
// Calculate item position relative to content
|
|
28
34
|
const itemTop = elementRef.y - contentY
|
|
35
|
+
const itemHeight = elementRef.height || 1
|
|
36
|
+
const itemBottom = itemTop + itemHeight
|
|
29
37
|
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
38
|
+
const viewportTop = scrollTop
|
|
39
|
+
const viewportBottom = scrollTop + viewportHeight
|
|
40
|
+
|
|
41
|
+
if (direction === 1) {
|
|
42
|
+
if (itemBottom > viewportBottom) {
|
|
43
|
+
scrollBox.scrollTo(Math.max(0, itemTop))
|
|
44
|
+
}
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (itemTop < viewportTop) {
|
|
49
|
+
const targetScrollTop = itemBottom - viewportHeight
|
|
50
|
+
scrollBox.scrollTo(Math.max(0, targetScrollTop))
|
|
51
|
+
}
|
|
33
52
|
}
|
|
34
53
|
|
|
35
54
|
const move = (direction: -1 | 1) => {
|
|
@@ -40,15 +59,15 @@ function ScrollboxWithDescendants() {
|
|
|
40
59
|
if (items.length === 0) return
|
|
41
60
|
|
|
42
61
|
let nextIndex = selectedIndex + direction
|
|
43
|
-
if (nextIndex < 0)
|
|
44
|
-
if (nextIndex >= items.length)
|
|
62
|
+
if (nextIndex < 0) return
|
|
63
|
+
if (nextIndex >= items.length) return
|
|
45
64
|
|
|
46
65
|
const nextItem = items[nextIndex]
|
|
47
66
|
if (nextItem) {
|
|
48
67
|
flushSync(() => {
|
|
49
68
|
setSelectedIndex(nextIndex)
|
|
50
69
|
})
|
|
51
|
-
|
|
70
|
+
scrollToItemIfNeeded({ item: nextItem, direction })
|
|
52
71
|
}
|
|
53
72
|
}
|
|
54
73
|
|
|
@@ -23,7 +23,9 @@ test('list detail metadata label renders short values in row layout (key: value)
|
|
|
23
23
|
text.includes('Metadata Test') &&
|
|
24
24
|
text.includes('Name') &&
|
|
25
25
|
text.includes('John Doe') &&
|
|
26
|
-
text.includes('Email')
|
|
26
|
+
text.includes('Email') &&
|
|
27
|
+
text.includes('Website') &&
|
|
28
|
+
text.includes('↑↓ navigate')
|
|
27
29
|
)
|
|
28
30
|
},
|
|
29
31
|
})
|
|
@@ -25,19 +25,19 @@ test('empty view shows spinner when list is loading', async () => {
|
|
|
25
25
|
},
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
-
// Spinner animates. Normalize for stable snapshots.
|
|
29
|
-
const normalized = text.replace(/[
|
|
28
|
+
// Spinner animates (pulsing dots: ' ' · •). Normalize for stable snapshots.
|
|
29
|
+
const normalized = text.replace(/[·•]/g, '•')
|
|
30
30
|
expect(normalized).toMatchInlineSnapshot(`
|
|
31
31
|
"
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
Loading Empty View ───────────────────────────────────
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
• Search...
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
• loading...
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
|
|
@@ -45,5 +45,5 @@ test('empty view shows spinner when list is loading', async () => {
|
|
|
45
45
|
|
|
46
46
|
↑↓ navigate ^k actions"
|
|
47
47
|
`)
|
|
48
|
-
expect(text).toMatch(/[
|
|
48
|
+
expect(text).toMatch(/[·• ]\s*loading\.\.\./)
|
|
49
49
|
}, 10000)
|
|
@@ -69,12 +69,12 @@ test('list scrollbox auto-scrolls when navigating down', async () => {
|
|
|
69
69
|
|
|
70
70
|
> Search items...
|
|
71
71
|
|
|
72
|
+
○ Item 1 Description for item 1
|
|
73
|
+
★ Item 2 Description for item 2
|
|
72
74
|
◆ Item 3 Description for item 3
|
|
73
75
|
↯ Item 4 Description for item 4
|
|
74
76
|
▷ Item 5 Description for item 5
|
|
75
|
-
›▦ Item 6 Description for item 6
|
|
76
|
-
◴ Item 7 Description for item 7
|
|
77
|
-
▯ Item 8 Description for item 8"
|
|
77
|
+
›▦ Item 6 Description for item 6"
|
|
78
78
|
`)
|
|
79
79
|
|
|
80
80
|
await session.press('down')
|
|
@@ -90,12 +90,12 @@ test('list scrollbox auto-scrolls when navigating down', async () => {
|
|
|
90
90
|
|
|
91
91
|
> Search items...
|
|
92
92
|
|
|
93
|
-
▦ Item 6 Description for item 6
|
|
94
93
|
◴ Item 7 Description for item 7
|
|
95
94
|
▯ Item 8 Description for item 8
|
|
96
95
|
›▤ Item 9 Description for item 9
|
|
97
96
|
Item 10 Description for item 10
|
|
98
|
-
○ Item 11 Description for item 11
|
|
97
|
+
○ Item 11 Description for item 11
|
|
98
|
+
★ Item 12 Description for item 12"
|
|
99
99
|
`)
|
|
100
100
|
|
|
101
101
|
await session.press('up')
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test file for List with spacingMode="default" (single-line items)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { List, Icon, Color, renderWithProviders } from 'termcast'
|
|
6
|
+
|
|
7
|
+
function ListSpacingDefault() {
|
|
8
|
+
return (
|
|
9
|
+
<List navigationTitle="Default Mode" spacingMode="default">
|
|
10
|
+
<List.Section title="With Icons">
|
|
11
|
+
<List.Item
|
|
12
|
+
icon={Icon.Document}
|
|
13
|
+
title="Report"
|
|
14
|
+
subtitle="Q4 financial summary"
|
|
15
|
+
accessories={[{ tag: { value: 'Draft', color: Color.Yellow } }]}
|
|
16
|
+
/>
|
|
17
|
+
<List.Item
|
|
18
|
+
icon={Icon.Code}
|
|
19
|
+
title="API Docs"
|
|
20
|
+
subtitle="REST endpoints guide"
|
|
21
|
+
accessories={[{ text: 'v2.1' }]}
|
|
22
|
+
/>
|
|
23
|
+
</List.Section>
|
|
24
|
+
<List.Section title="Without Icons">
|
|
25
|
+
<List.Item
|
|
26
|
+
title="Meeting Notes"
|
|
27
|
+
subtitle="Weekly standup points"
|
|
28
|
+
accessories={[{ tag: 'Important' }]}
|
|
29
|
+
/>
|
|
30
|
+
</List.Section>
|
|
31
|
+
<List.Section title="No Subtitle">
|
|
32
|
+
<List.Item icon={Icon.Star} title="Favorites" />
|
|
33
|
+
</List.Section>
|
|
34
|
+
</List>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await renderWithProviders(<ListSpacingDefault />)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example demonstrating List spacingMode prop.
|
|
3
|
+
*
|
|
4
|
+
* - 'default': Single-line items with title and subtitle on same row
|
|
5
|
+
* - 'relaxed': Two-line items with title on first row, subtitle below aligned with title start
|
|
6
|
+
*
|
|
7
|
+
* Use Action to toggle between modes and see the difference.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useState } from 'react'
|
|
11
|
+
import {
|
|
12
|
+
List,
|
|
13
|
+
ActionPanel,
|
|
14
|
+
Action,
|
|
15
|
+
Icon,
|
|
16
|
+
Color,
|
|
17
|
+
renderWithProviders,
|
|
18
|
+
type ListSpacingMode,
|
|
19
|
+
} from 'termcast'
|
|
20
|
+
|
|
21
|
+
function ListSpacingModeExample() {
|
|
22
|
+
const [spacingMode, setSpacingMode] = useState<ListSpacingMode>('relaxed')
|
|
23
|
+
|
|
24
|
+
const toggleMode = () => {
|
|
25
|
+
setSpacingMode((prev) => (prev === 'default' ? 'relaxed' : 'default'))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<List
|
|
30
|
+
navigationTitle={`Spacing Mode: ${spacingMode}`}
|
|
31
|
+
spacingMode={spacingMode}
|
|
32
|
+
>
|
|
33
|
+
<List.Section title="With Icons" subtitle="Items have icon, title, subtitle">
|
|
34
|
+
<List.Item
|
|
35
|
+
icon={Icon.Document}
|
|
36
|
+
title="Quarterly Report"
|
|
37
|
+
subtitle="Q4 2024 financial summary and projections"
|
|
38
|
+
accessories={[{ tag: { value: 'Draft', color: Color.Yellow } }]}
|
|
39
|
+
actions={
|
|
40
|
+
<ActionPanel>
|
|
41
|
+
<Action title="Toggle Spacing Mode" onAction={toggleMode} />
|
|
42
|
+
</ActionPanel>
|
|
43
|
+
}
|
|
44
|
+
/>
|
|
45
|
+
<List.Item
|
|
46
|
+
icon={Icon.Code}
|
|
47
|
+
title="API Documentation"
|
|
48
|
+
subtitle="REST endpoints and authentication guide"
|
|
49
|
+
accessories={[
|
|
50
|
+
{ text: 'v2.1' },
|
|
51
|
+
{ date: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000) },
|
|
52
|
+
]}
|
|
53
|
+
actions={
|
|
54
|
+
<ActionPanel>
|
|
55
|
+
<Action title="Toggle Spacing Mode" onAction={toggleMode} />
|
|
56
|
+
</ActionPanel>
|
|
57
|
+
}
|
|
58
|
+
/>
|
|
59
|
+
<List.Item
|
|
60
|
+
icon={Icon.Gear}
|
|
61
|
+
title="Configuration"
|
|
62
|
+
subtitle="System settings and preferences"
|
|
63
|
+
accessories={[{ tag: { value: 'Active', color: Color.Green } }]}
|
|
64
|
+
actions={
|
|
65
|
+
<ActionPanel>
|
|
66
|
+
<Action title="Toggle Spacing Mode" onAction={toggleMode} />
|
|
67
|
+
</ActionPanel>
|
|
68
|
+
}
|
|
69
|
+
/>
|
|
70
|
+
</List.Section>
|
|
71
|
+
|
|
72
|
+
<List.Section title="Without Icons" subtitle="Plain text items">
|
|
73
|
+
<List.Item
|
|
74
|
+
title="Meeting Notes"
|
|
75
|
+
subtitle="Weekly standup discussion points"
|
|
76
|
+
accessories={[{ date: new Date(Date.now() - 60 * 60 * 1000) }]}
|
|
77
|
+
actions={
|
|
78
|
+
<ActionPanel>
|
|
79
|
+
<Action title="Toggle Spacing Mode" onAction={toggleMode} />
|
|
80
|
+
</ActionPanel>
|
|
81
|
+
}
|
|
82
|
+
/>
|
|
83
|
+
<List.Item
|
|
84
|
+
title="Project Timeline"
|
|
85
|
+
subtitle="Milestones and deadlines for Q1"
|
|
86
|
+
accessories={[{ text: { value: 'Important', color: Color.Red } }]}
|
|
87
|
+
actions={
|
|
88
|
+
<ActionPanel>
|
|
89
|
+
<Action title="Toggle Spacing Mode" onAction={toggleMode} />
|
|
90
|
+
</ActionPanel>
|
|
91
|
+
}
|
|
92
|
+
/>
|
|
93
|
+
</List.Section>
|
|
94
|
+
|
|
95
|
+
<List.Section title="No Subtitle" subtitle="Title only items">
|
|
96
|
+
<List.Item
|
|
97
|
+
icon={Icon.Star}
|
|
98
|
+
title="Favorites"
|
|
99
|
+
accessories={[{ tag: '12 items' }]}
|
|
100
|
+
actions={
|
|
101
|
+
<ActionPanel>
|
|
102
|
+
<Action title="Toggle Spacing Mode" onAction={toggleMode} />
|
|
103
|
+
</ActionPanel>
|
|
104
|
+
}
|
|
105
|
+
/>
|
|
106
|
+
<List.Item
|
|
107
|
+
title="Recent"
|
|
108
|
+
accessories={[{ date: new Date() }]}
|
|
109
|
+
actions={
|
|
110
|
+
<ActionPanel>
|
|
111
|
+
<Action title="Toggle Spacing Mode" onAction={toggleMode} />
|
|
112
|
+
</ActionPanel>
|
|
113
|
+
}
|
|
114
|
+
/>
|
|
115
|
+
</List.Section>
|
|
116
|
+
|
|
117
|
+
<List.Section title="Long Content" subtitle="Testing overflow behavior">
|
|
118
|
+
<List.Item
|
|
119
|
+
icon={Icon.Text}
|
|
120
|
+
title="Very Long Title That Might Need Truncation"
|
|
121
|
+
subtitle="This is a particularly verbose subtitle that describes the item in great detail"
|
|
122
|
+
accessories={[
|
|
123
|
+
{ tag: { value: 'Beta', color: Color.Blue } },
|
|
124
|
+
{ text: 'Updated' },
|
|
125
|
+
]}
|
|
126
|
+
actions={
|
|
127
|
+
<ActionPanel>
|
|
128
|
+
<Action title="Toggle Spacing Mode" onAction={toggleMode} />
|
|
129
|
+
</ActionPanel>
|
|
130
|
+
}
|
|
131
|
+
/>
|
|
132
|
+
</List.Section>
|
|
133
|
+
</List>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await renderWithProviders(<ListSpacingModeExample />)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E tests for List spacingMode prop.
|
|
3
|
+
*
|
|
4
|
+
* Tests both 'default' (single-line) and 'relaxed' (two-line) modes
|
|
5
|
+
* to verify layout differences and subtitle alignment.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { test, expect, afterEach, beforeEach, describe } from 'vitest'
|
|
9
|
+
import { launchTerminal, Session } from 'tuistory/src'
|
|
10
|
+
|
|
11
|
+
describe('spacingMode default', () => {
|
|
12
|
+
let session: Session
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
session = await launchTerminal({
|
|
16
|
+
command: 'bun',
|
|
17
|
+
args: ['src/examples/list-spacing-default.tsx'],
|
|
18
|
+
cols: 70,
|
|
19
|
+
rows: 20,
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
session?.close()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('renders single-line items with title and subtitle on same row', async () => {
|
|
28
|
+
await session.text({
|
|
29
|
+
waitFor: (text) => /Default Mode/i.test(text),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const text = await session.text()
|
|
33
|
+
expect(text).toMatchInlineSnapshot(`
|
|
34
|
+
"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Default Mode ───────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
> Search...
|
|
40
|
+
|
|
41
|
+
With Icons
|
|
42
|
+
›▯ Report Q4 financial summary [Draft]
|
|
43
|
+
⟨⟩ API Docs REST endpoints guide v2.1
|
|
44
|
+
|
|
45
|
+
Without Icons
|
|
46
|
+
Meeting Notes Weekly standup points [Important]
|
|
47
|
+
|
|
48
|
+
No Subtitle
|
|
49
|
+
★ Favorites
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
↑↓ navigate ^k actions
|
|
53
|
+
|
|
54
|
+
"
|
|
55
|
+
`)
|
|
56
|
+
|
|
57
|
+
// Title and subtitle on same line
|
|
58
|
+
expect(text).toContain('Report')
|
|
59
|
+
expect(text).toContain('Q4 financial')
|
|
60
|
+
}, 10000)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('spacingMode relaxed', () => {
|
|
64
|
+
let session: Session
|
|
65
|
+
|
|
66
|
+
beforeEach(async () => {
|
|
67
|
+
session = await launchTerminal({
|
|
68
|
+
command: 'bun',
|
|
69
|
+
args: ['src/examples/list-spacing-relaxed.tsx'],
|
|
70
|
+
cols: 70,
|
|
71
|
+
rows: 25,
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
session?.close()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('renders two-line items with subtitle below title', async () => {
|
|
80
|
+
await session.text({
|
|
81
|
+
waitFor: (text) => /Relaxed Mode/i.test(text),
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const text = await session.text()
|
|
85
|
+
expect(text).toMatchInlineSnapshot(`
|
|
86
|
+
"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
Relaxed Mode ───────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
> Search...
|
|
92
|
+
|
|
93
|
+
With Icons
|
|
94
|
+
›▯ Report [Draft]
|
|
95
|
+
Q4 financial summary
|
|
96
|
+
|
|
97
|
+
⟨⟩ API Docs v2.1
|
|
98
|
+
REST endpoints guide
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
Without Icons
|
|
102
|
+
Meeting Notes [Important]
|
|
103
|
+
Weekly standup points
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
No Subtitle
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
↑↓ navigate ^k actions
|
|
110
|
+
|
|
111
|
+
"
|
|
112
|
+
`)
|
|
113
|
+
|
|
114
|
+
// Should have content
|
|
115
|
+
expect(text).toContain('Report')
|
|
116
|
+
expect(text).toContain('Q4 financial')
|
|
117
|
+
}, 10000)
|
|
118
|
+
|
|
119
|
+
test('navigates through relaxed items', async () => {
|
|
120
|
+
await session.text({
|
|
121
|
+
waitFor: (text) => /Relaxed Mode/i.test(text),
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
await session.press('down')
|
|
125
|
+
const afterDown = await session.text()
|
|
126
|
+
expect(afterDown).toMatchInlineSnapshot(`
|
|
127
|
+
"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
Relaxed Mode ───────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
> Search...
|
|
133
|
+
|
|
134
|
+
With Icons
|
|
135
|
+
▯ Report [Draft]
|
|
136
|
+
Q4 financial summary
|
|
137
|
+
|
|
138
|
+
›⟨⟩ API Docs v2.1
|
|
139
|
+
REST endpoints guide
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
Without Icons
|
|
143
|
+
Meeting Notes [Important]
|
|
144
|
+
Weekly standup points
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
No Subtitle
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
↑↓ navigate ^k actions
|
|
151
|
+
|
|
152
|
+
"
|
|
153
|
+
`)
|
|
154
|
+
|
|
155
|
+
// Second item should be selected
|
|
156
|
+
expect(afterDown).toContain('API Docs')
|
|
157
|
+
}, 10000)
|
|
158
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test file for List with spacingMode="relaxed" (two-line items)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { List, Icon, Color, renderWithProviders } from 'termcast'
|
|
6
|
+
|
|
7
|
+
function ListSpacingRelaxed() {
|
|
8
|
+
return (
|
|
9
|
+
<List navigationTitle="Relaxed Mode" spacingMode="relaxed">
|
|
10
|
+
<List.Section title="With Icons">
|
|
11
|
+
<List.Item
|
|
12
|
+
icon={Icon.Document}
|
|
13
|
+
title="Report"
|
|
14
|
+
subtitle="Q4 financial summary"
|
|
15
|
+
accessories={[{ tag: { value: 'Draft', color: Color.Yellow } }]}
|
|
16
|
+
/>
|
|
17
|
+
<List.Item
|
|
18
|
+
icon={Icon.Code}
|
|
19
|
+
title="API Docs"
|
|
20
|
+
subtitle="REST endpoints guide"
|
|
21
|
+
accessories={[{ text: 'v2.1' }]}
|
|
22
|
+
/>
|
|
23
|
+
</List.Section>
|
|
24
|
+
<List.Section title="Without Icons">
|
|
25
|
+
<List.Item
|
|
26
|
+
title="Meeting Notes"
|
|
27
|
+
subtitle="Weekly standup points"
|
|
28
|
+
accessories={[{ tag: 'Important' }]}
|
|
29
|
+
/>
|
|
30
|
+
</List.Section>
|
|
31
|
+
<List.Section title="No Subtitle">
|
|
32
|
+
<List.Item icon={Icon.Star} title="Favorites" />
|
|
33
|
+
</List.Section>
|
|
34
|
+
</List>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await renderWithProviders(<ListSpacingRelaxed />)
|