Skip to content

Commit 17dfee7

Browse files
authored
Refactor: Share VersionSelector across wizards (#36)
* Refactor: Share VersionSelector across wizards * Chore: Add changeset
1 parent 18e3f45 commit 17dfee7

File tree

6 files changed

+69
-157
lines changed

6 files changed

+69
-157
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@perstack/tui": patch
3+
---
4+
5+
Share VersionSelector component across wizard apps

packages/tui/apps/status/app.tsx

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Box, Text, useApp, useInput } from "ink"
22
import { useState } from "react"
3-
import { ErrorStep, WizardExpertSelector } from "../../src/components/index.js"
3+
import { ErrorStep, VersionSelector, WizardExpertSelector } from "../../src/components/index.js"
44
import type { WizardExpertChoice, WizardVersionInfo } from "../../src/types/wizard.js"
55
import { getStatusColor } from "../../src/utils/index.js"
66

@@ -34,57 +34,6 @@ function getAvailableStatusTransitions(currentStatus: string): string[] {
3434
}
3535
}
3636

37-
function VersionSelector({
38-
expertName,
39-
versions,
40-
onSelect,
41-
onBack,
42-
}: {
43-
expertName: string
44-
versions: WizardVersionInfo[]
45-
onSelect: (version: WizardVersionInfo) => void
46-
onBack: () => void
47-
}) {
48-
const { exit } = useApp()
49-
const [selectedIndex, setSelectedIndex] = useState(0)
50-
useInput((input, key) => {
51-
if (key.upArrow) {
52-
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : versions.length - 1))
53-
} else if (key.downArrow) {
54-
setSelectedIndex((prev) => (prev < versions.length - 1 ? prev + 1 : 0))
55-
} else if (key.return) {
56-
const version = versions[selectedIndex]
57-
if (version) {
58-
onSelect(version)
59-
}
60-
} else if (key.escape || key.backspace || key.delete) {
61-
onBack()
62-
} else if (input === "q") {
63-
exit()
64-
}
65-
})
66-
return (
67-
<Box flexDirection="column">
68-
<Text bold>Select a version of {expertName}:</Text>
69-
<Box flexDirection="column" marginTop={1}>
70-
{versions.map((v, index) => (
71-
<Box key={v.key}>
72-
<Text color={index === selectedIndex ? "cyan" : undefined}>
73-
{index === selectedIndex ? "❯ " : " "}
74-
{v.version}
75-
{v.tags.length > 0 && <Text color="magenta"> [{v.tags.join(", ")}]</Text>}
76-
<Text color={getStatusColor(v.status)}> {v.status}</Text>
77-
</Text>
78-
</Box>
79-
))}
80-
</Box>
81-
<Box marginTop={1}>
82-
<Text dimColor>↑↓ navigate · enter select · esc back · q quit</Text>
83-
</Box>
84-
</Box>
85-
)
86-
}
87-
8837
function StatusSelector({
8938
expertKey,
9039
currentStatus,

packages/tui/apps/tag/app.tsx

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { Box, Text, useApp, useInput } from "ink"
22
import { useState } from "react"
3-
import { ErrorStep, WizardExpertSelector } from "../../src/components/index.js"
3+
import { ErrorStep, VersionSelector, WizardExpertSelector } from "../../src/components/index.js"
44
import type { WizardExpertChoice, WizardVersionInfo } from "../../src/types/wizard.js"
5-
import { getStatusColor } from "../../src/utils/index.js"
65

76
type WizardStep =
87
| { type: "selectExpert" }
@@ -22,57 +21,6 @@ type TagAppProps = {
2221
onCancel: () => void
2322
}
2423

25-
function VersionSelector({
26-
expertName,
27-
versions,
28-
onSelect,
29-
onBack,
30-
}: {
31-
expertName: string
32-
versions: WizardVersionInfo[]
33-
onSelect: (version: WizardVersionInfo) => void
34-
onBack: () => void
35-
}) {
36-
const { exit } = useApp()
37-
const [selectedIndex, setSelectedIndex] = useState(0)
38-
useInput((input, key) => {
39-
if (key.upArrow) {
40-
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : versions.length - 1))
41-
} else if (key.downArrow) {
42-
setSelectedIndex((prev) => (prev < versions.length - 1 ? prev + 1 : 0))
43-
} else if (key.return) {
44-
const version = versions[selectedIndex]
45-
if (version) {
46-
onSelect(version)
47-
}
48-
} else if (key.escape || key.backspace || key.delete) {
49-
onBack()
50-
} else if (input === "q") {
51-
exit()
52-
}
53-
})
54-
return (
55-
<Box flexDirection="column">
56-
<Text bold>Select a version of {expertName}:</Text>
57-
<Box flexDirection="column" marginTop={1}>
58-
{versions.map((v, index) => (
59-
<Box key={v.key}>
60-
<Text color={index === selectedIndex ? "cyan" : undefined}>
61-
{index === selectedIndex ? "❯ " : " "}
62-
{v.version}
63-
{v.tags.length > 0 && <Text color="magenta"> [{v.tags.join(", ")}]</Text>}
64-
<Text color={getStatusColor(v.status)}> {v.status}</Text>
65-
</Text>
66-
</Box>
67-
))}
68-
</Box>
69-
<Box marginTop={1}>
70-
<Text dimColor>↑↓ navigate · enter select · esc back · q quit</Text>
71-
</Box>
72-
</Box>
73-
)
74-
}
75-
7624
function TagInput({
7725
expertKey,
7826
currentTags,

packages/tui/apps/unpublish/app.tsx

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { Box, Text, useApp, useInput } from "ink"
22
import { useState } from "react"
3-
import { ErrorStep, WizardExpertSelector } from "../../src/components/index.js"
3+
import { ErrorStep, VersionSelector, WizardExpertSelector } from "../../src/components/index.js"
44
import type { WizardExpertChoice, WizardVersionInfo } from "../../src/types/wizard.js"
5-
import { getStatusColor } from "../../src/utils/index.js"
65

76
type WizardStep =
87
| { type: "selectExpert" }
@@ -19,56 +18,6 @@ type UnpublishAppProps = {
1918
onComplete: (result: UnpublishWizardResult) => void
2019
onCancel: () => void
2120
}
22-
function VersionSelector({
23-
expertName,
24-
versions,
25-
onSelect,
26-
onBack,
27-
}: {
28-
expertName: string
29-
versions: WizardVersionInfo[]
30-
onSelect: (version: WizardVersionInfo) => void
31-
onBack: () => void
32-
}) {
33-
const { exit } = useApp()
34-
const [selectedIndex, setSelectedIndex] = useState(0)
35-
useInput((input, key) => {
36-
if (key.upArrow) {
37-
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : versions.length - 1))
38-
} else if (key.downArrow) {
39-
setSelectedIndex((prev) => (prev < versions.length - 1 ? prev + 1 : 0))
40-
} else if (key.return) {
41-
const version = versions[selectedIndex]
42-
if (version) {
43-
onSelect(version)
44-
}
45-
} else if (key.escape || key.backspace || key.delete) {
46-
onBack()
47-
} else if (input === "q") {
48-
exit()
49-
}
50-
})
51-
return (
52-
<Box flexDirection="column">
53-
<Text bold>Select a version of {expertName} to unpublish:</Text>
54-
<Box flexDirection="column" marginTop={1}>
55-
{versions.map((v, index) => (
56-
<Box key={v.key}>
57-
<Text color={index === selectedIndex ? "cyan" : undefined}>
58-
{index === selectedIndex ? "❯ " : " "}
59-
{v.version}
60-
{v.tags.length > 0 && <Text color="magenta"> [{v.tags.join(", ")}]</Text>}
61-
<Text color={getStatusColor(v.status)}> {v.status}</Text>
62-
</Text>
63-
</Box>
64-
))}
65-
</Box>
66-
<Box marginTop={1}>
67-
<Text dimColor>↑↓ navigate · enter select · esc back · q quit</Text>
68-
</Box>
69-
</Box>
70-
)
71-
}
7221
function ConfirmStep({
7322
expertKey,
7423
version,
@@ -199,6 +148,7 @@ export function UnpublishApp({
199148
versions={step.versions}
200149
onSelect={handleVersionSelect}
201150
onBack={handleBack}
151+
title={`Select a version of ${step.expertName} to unpublish:`}
202152
/>
203153
)
204154
case "confirm":

packages/tui/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export {
1010
WizardExpertSelector,
1111
type WizardExpertSelectorProps,
1212
} from "./wizard-expert-selector.js"
13+
export { VersionSelector, type VersionSelectorProps } from "./version-selector.js"
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Box, Text, useApp, useInput } from "ink"
2+
import { useState } from "react"
3+
import type { WizardVersionInfo } from "../types/wizard.js"
4+
import { getStatusColor } from "../utils/status-color.js"
5+
6+
export type VersionSelectorProps = {
7+
expertName: string
8+
versions: WizardVersionInfo[]
9+
onSelect: (version: WizardVersionInfo) => void
10+
onBack: () => void
11+
title?: string
12+
}
13+
14+
export function VersionSelector({
15+
expertName,
16+
versions,
17+
onSelect,
18+
onBack,
19+
title,
20+
}: VersionSelectorProps) {
21+
const { exit } = useApp()
22+
const [selectedIndex, setSelectedIndex] = useState(0)
23+
useInput((input, key) => {
24+
if (key.upArrow) {
25+
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : versions.length - 1))
26+
} else if (key.downArrow) {
27+
setSelectedIndex((prev) => (prev < versions.length - 1 ? prev + 1 : 0))
28+
} else if (key.return) {
29+
const version = versions[selectedIndex]
30+
if (version) {
31+
onSelect(version)
32+
}
33+
} else if (key.escape || key.backspace || key.delete) {
34+
onBack()
35+
} else if (input === "q") {
36+
exit()
37+
}
38+
})
39+
return (
40+
<Box flexDirection="column">
41+
<Text bold>{title ?? `Select a version of ${expertName}:`}</Text>
42+
<Box flexDirection="column" marginTop={1}>
43+
{versions.map((v, index) => (
44+
<Box key={v.key}>
45+
<Text color={index === selectedIndex ? "cyan" : undefined}>
46+
{index === selectedIndex ? "❯ " : " "}
47+
{v.version}
48+
{v.tags.length > 0 && <Text color="magenta"> [{v.tags.join(", ")}]</Text>}
49+
<Text color={getStatusColor(v.status)}> {v.status}</Text>
50+
</Text>
51+
</Box>
52+
))}
53+
</Box>
54+
<Box marginTop={1}>
55+
<Text dimColor>↑↓ navigate · enter select · esc back · q quit</Text>
56+
</Box>
57+
</Box>
58+
)
59+
}

0 commit comments

Comments
 (0)