Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/share-version-selector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@perstack/tui": patch
---

Share VersionSelector component across wizard apps
53 changes: 1 addition & 52 deletions packages/tui/apps/status/app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Box, Text, useApp, useInput } from "ink"
import { useState } from "react"
import { ErrorStep, WizardExpertSelector } from "../../src/components/index.js"
import { ErrorStep, VersionSelector, WizardExpertSelector } from "../../src/components/index.js"
import type { WizardExpertChoice, WizardVersionInfo } from "../../src/types/wizard.js"
import { getStatusColor } from "../../src/utils/index.js"

Expand Down Expand Up @@ -34,57 +34,6 @@ function getAvailableStatusTransitions(currentStatus: string): string[] {
}
}

function VersionSelector({
expertName,
versions,
onSelect,
onBack,
}: {
expertName: string
versions: WizardVersionInfo[]
onSelect: (version: WizardVersionInfo) => void
onBack: () => void
}) {
const { exit } = useApp()
const [selectedIndex, setSelectedIndex] = useState(0)
useInput((input, key) => {
if (key.upArrow) {
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : versions.length - 1))
} else if (key.downArrow) {
setSelectedIndex((prev) => (prev < versions.length - 1 ? prev + 1 : 0))
} else if (key.return) {
const version = versions[selectedIndex]
if (version) {
onSelect(version)
}
} else if (key.escape || key.backspace || key.delete) {
onBack()
} else if (input === "q") {
exit()
}
})
return (
<Box flexDirection="column">
<Text bold>Select a version of {expertName}:</Text>
<Box flexDirection="column" marginTop={1}>
{versions.map((v, index) => (
<Box key={v.key}>
<Text color={index === selectedIndex ? "cyan" : undefined}>
{index === selectedIndex ? "❯ " : " "}
{v.version}
{v.tags.length > 0 && <Text color="magenta"> [{v.tags.join(", ")}]</Text>}
<Text color={getStatusColor(v.status)}> {v.status}</Text>
</Text>
</Box>
))}
</Box>
<Box marginTop={1}>
<Text dimColor>↑↓ navigate · enter select · esc back · q quit</Text>
</Box>
</Box>
)
}

function StatusSelector({
expertKey,
currentStatus,
Expand Down
54 changes: 1 addition & 53 deletions packages/tui/apps/tag/app.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Box, Text, useApp, useInput } from "ink"
import { useState } from "react"
import { ErrorStep, WizardExpertSelector } from "../../src/components/index.js"
import { ErrorStep, VersionSelector, WizardExpertSelector } from "../../src/components/index.js"
import type { WizardExpertChoice, WizardVersionInfo } from "../../src/types/wizard.js"
import { getStatusColor } from "../../src/utils/index.js"

type WizardStep =
| { type: "selectExpert" }
Expand All @@ -22,57 +21,6 @@ type TagAppProps = {
onCancel: () => void
}

function VersionSelector({
expertName,
versions,
onSelect,
onBack,
}: {
expertName: string
versions: WizardVersionInfo[]
onSelect: (version: WizardVersionInfo) => void
onBack: () => void
}) {
const { exit } = useApp()
const [selectedIndex, setSelectedIndex] = useState(0)
useInput((input, key) => {
if (key.upArrow) {
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : versions.length - 1))
} else if (key.downArrow) {
setSelectedIndex((prev) => (prev < versions.length - 1 ? prev + 1 : 0))
} else if (key.return) {
const version = versions[selectedIndex]
if (version) {
onSelect(version)
}
} else if (key.escape || key.backspace || key.delete) {
onBack()
} else if (input === "q") {
exit()
}
})
return (
<Box flexDirection="column">
<Text bold>Select a version of {expertName}:</Text>
<Box flexDirection="column" marginTop={1}>
{versions.map((v, index) => (
<Box key={v.key}>
<Text color={index === selectedIndex ? "cyan" : undefined}>
{index === selectedIndex ? "❯ " : " "}
{v.version}
{v.tags.length > 0 && <Text color="magenta"> [{v.tags.join(", ")}]</Text>}
<Text color={getStatusColor(v.status)}> {v.status}</Text>
</Text>
</Box>
))}
</Box>
<Box marginTop={1}>
<Text dimColor>↑↓ navigate · enter select · esc back · q quit</Text>
</Box>
</Box>
)
}

function TagInput({
expertKey,
currentTags,
Expand Down
54 changes: 2 additions & 52 deletions packages/tui/apps/unpublish/app.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Box, Text, useApp, useInput } from "ink"
import { useState } from "react"
import { ErrorStep, WizardExpertSelector } from "../../src/components/index.js"
import { ErrorStep, VersionSelector, WizardExpertSelector } from "../../src/components/index.js"
import type { WizardExpertChoice, WizardVersionInfo } from "../../src/types/wizard.js"
import { getStatusColor } from "../../src/utils/index.js"

type WizardStep =
| { type: "selectExpert" }
Expand All @@ -19,56 +18,6 @@ type UnpublishAppProps = {
onComplete: (result: UnpublishWizardResult) => void
onCancel: () => void
}
function VersionSelector({
expertName,
versions,
onSelect,
onBack,
}: {
expertName: string
versions: WizardVersionInfo[]
onSelect: (version: WizardVersionInfo) => void
onBack: () => void
}) {
const { exit } = useApp()
const [selectedIndex, setSelectedIndex] = useState(0)
useInput((input, key) => {
if (key.upArrow) {
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : versions.length - 1))
} else if (key.downArrow) {
setSelectedIndex((prev) => (prev < versions.length - 1 ? prev + 1 : 0))
} else if (key.return) {
const version = versions[selectedIndex]
if (version) {
onSelect(version)
}
} else if (key.escape || key.backspace || key.delete) {
onBack()
} else if (input === "q") {
exit()
}
})
return (
<Box flexDirection="column">
<Text bold>Select a version of {expertName} to unpublish:</Text>
<Box flexDirection="column" marginTop={1}>
{versions.map((v, index) => (
<Box key={v.key}>
<Text color={index === selectedIndex ? "cyan" : undefined}>
{index === selectedIndex ? "❯ " : " "}
{v.version}
{v.tags.length > 0 && <Text color="magenta"> [{v.tags.join(", ")}]</Text>}
<Text color={getStatusColor(v.status)}> {v.status}</Text>
</Text>
</Box>
))}
</Box>
<Box marginTop={1}>
<Text dimColor>↑↓ navigate · enter select · esc back · q quit</Text>
</Box>
</Box>
)
}
function ConfirmStep({
expertKey,
version,
Expand Down Expand Up @@ -199,6 +148,7 @@ export function UnpublishApp({
versions={step.versions}
onSelect={handleVersionSelect}
onBack={handleBack}
title={`Select a version of ${step.expertName} to unpublish:`}
/>
)
case "confirm":
Expand Down
1 change: 1 addition & 0 deletions packages/tui/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export {
WizardExpertSelector,
type WizardExpertSelectorProps,
} from "./wizard-expert-selector.js"
export { VersionSelector, type VersionSelectorProps } from "./version-selector.js"
59 changes: 59 additions & 0 deletions packages/tui/src/components/version-selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Box, Text, useApp, useInput } from "ink"
import { useState } from "react"
import type { WizardVersionInfo } from "../types/wizard.js"
import { getStatusColor } from "../utils/status-color.js"

export type VersionSelectorProps = {
expertName: string
versions: WizardVersionInfo[]
onSelect: (version: WizardVersionInfo) => void
onBack: () => void
title?: string
}

export function VersionSelector({
expertName,
versions,
onSelect,
onBack,
title,
}: VersionSelectorProps) {
const { exit } = useApp()
const [selectedIndex, setSelectedIndex] = useState(0)
useInput((input, key) => {
if (key.upArrow) {
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : versions.length - 1))
} else if (key.downArrow) {
setSelectedIndex((prev) => (prev < versions.length - 1 ? prev + 1 : 0))
} else if (key.return) {
const version = versions[selectedIndex]
if (version) {
onSelect(version)
}
} else if (key.escape || key.backspace || key.delete) {
onBack()
} else if (input === "q") {
exit()
}
})
return (
<Box flexDirection="column">
<Text bold>{title ?? `Select a version of ${expertName}:`}</Text>
<Box flexDirection="column" marginTop={1}>
{versions.map((v, index) => (
<Box key={v.key}>
<Text color={index === selectedIndex ? "cyan" : undefined}>
{index === selectedIndex ? "❯ " : " "}
{v.version}
{v.tags.length > 0 && <Text color="magenta"> [{v.tags.join(", ")}]</Text>}
<Text color={getStatusColor(v.status)}> {v.status}</Text>
</Text>
</Box>
))}
</Box>
<Box marginTop={1}>
<Text dimColor>↑↓ navigate · enter select · esc back · q quit</Text>
</Box>
</Box>
)
}