agent-facets 0.3.0 → 0.3.5

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.
Files changed (60) hide show
  1. package/bin/facet +1 -1
  2. package/package.json +16 -37
  3. package/{scripts/postinstall.mjs → postinstall.mjs} +1 -1
  4. package/.package.json.bak +0 -45
  5. package/.turbo/turbo-build.log +0 -3
  6. package/CHANGELOG.md +0 -95
  7. package/bunfig.toml +0 -2
  8. package/dist/facet +0 -0
  9. package/src/__tests__/cli.test.ts +0 -195
  10. package/src/__tests__/create-build.test.ts +0 -227
  11. package/src/__tests__/edit-integration.test.ts +0 -171
  12. package/src/__tests__/launcher.test.ts +0 -106
  13. package/src/__tests__/postinstall.test.ts +0 -196
  14. package/src/__tests__/resolve-dir.test.ts +0 -95
  15. package/src/commands/build.ts +0 -58
  16. package/src/commands/create/index.ts +0 -76
  17. package/src/commands/create/types.ts +0 -9
  18. package/src/commands/create/wizard.tsx +0 -75
  19. package/src/commands/create-scaffold.ts +0 -184
  20. package/src/commands/edit/index.ts +0 -144
  21. package/src/commands/edit/wizard.tsx +0 -74
  22. package/src/commands/resolve-dir.ts +0 -98
  23. package/src/commands.ts +0 -40
  24. package/src/help.ts +0 -43
  25. package/src/index.ts +0 -10
  26. package/src/run.ts +0 -82
  27. package/src/suggest.ts +0 -35
  28. package/src/tui/components/asset-description.tsx +0 -17
  29. package/src/tui/components/asset-field-picker.tsx +0 -78
  30. package/src/tui/components/asset-inline-input.tsx +0 -91
  31. package/src/tui/components/asset-item.tsx +0 -44
  32. package/src/tui/components/asset-section.tsx +0 -191
  33. package/src/tui/components/button.tsx +0 -92
  34. package/src/tui/components/editable-field.tsx +0 -172
  35. package/src/tui/components/exit-toast.tsx +0 -20
  36. package/src/tui/components/reconciliation-item.tsx +0 -129
  37. package/src/tui/components/stage-row.tsx +0 -45
  38. package/src/tui/components/version-selector.tsx +0 -79
  39. package/src/tui/context/focus-mode-context.ts +0 -36
  40. package/src/tui/context/focus-order-context.ts +0 -68
  41. package/src/tui/context/form-state-context.ts +0 -260
  42. package/src/tui/editor.ts +0 -40
  43. package/src/tui/gradient.ts +0 -1
  44. package/src/tui/hooks/use-exit-keys.ts +0 -75
  45. package/src/tui/hooks/use-navigation-keys.ts +0 -34
  46. package/src/tui/layouts/wizard-layout.tsx +0 -41
  47. package/src/tui/theme.ts +0 -1
  48. package/src/tui/views/build/build-view.tsx +0 -152
  49. package/src/tui/views/create/confirm-view.tsx +0 -74
  50. package/src/tui/views/create/create-view.tsx +0 -158
  51. package/src/tui/views/create/wizard.tsx +0 -97
  52. package/src/tui/views/edit/edit-confirm-view.tsx +0 -93
  53. package/src/tui/views/edit/edit-types.ts +0 -34
  54. package/src/tui/views/edit/edit-view.tsx +0 -140
  55. package/src/tui/views/edit/manifest-to-form.ts +0 -38
  56. package/src/tui/views/edit/reconciliation-view.tsx +0 -170
  57. package/src/tui/views/edit/use-edit-session.ts +0 -125
  58. package/src/tui/views/edit/wizard.tsx +0 -129
  59. package/src/version.ts +0 -3
  60. package/tsconfig.json +0 -4
package/src/help.ts DELETED
@@ -1,43 +0,0 @@
1
- import type { Command } from './commands.ts'
2
- import { version } from './version.ts'
3
-
4
- export function printGlobalHelp(commands: Record<string, Command>): void {
5
- const entries = Object.values(commands)
6
- const maxNameLength = Math.max(...entries.map((c) => c.name.length))
7
-
8
- const lines = [
9
- `facet v${version}`,
10
- '',
11
- 'Usage: facet <command> [options]',
12
- '',
13
- 'Commands:',
14
- ...entries.map((c) => ` ${c.name.padEnd(maxNameLength + 2)}${c.description}`),
15
- '',
16
- 'Options:',
17
- ' --help Show help',
18
- ' --version Show version',
19
- ]
20
-
21
- console.log(lines.join('\n'))
22
- }
23
-
24
- export function printCommandHelp(command: Command): void {
25
- const usage = command.usage ? ` ${command.usage}` : ''
26
-
27
- const lines = [`Usage: facet ${command.name}${usage} [options]`, '', ` ${command.description}`, '', 'Options:']
28
-
29
- if (command.flags) {
30
- const flagEntries = Object.entries(command.flags)
31
- const maxFlagLength = Math.max(...flagEntries.map(([name]) => `--${name}`.length), '--help'.length)
32
-
33
- for (const [name, def] of flagEntries) {
34
- lines.push(` ${`--${name}`.padEnd(maxFlagLength + 4)}${def.description}`)
35
- }
36
-
37
- lines.push(` ${'--help'.padEnd(maxFlagLength + 4)}Show help`)
38
- } else {
39
- lines.push(' --help Show help')
40
- }
41
-
42
- console.log(lines.join('\n'))
43
- }
package/src/index.ts DELETED
@@ -1,10 +0,0 @@
1
- import { commands } from './commands.ts'
2
- import { run } from './run.ts'
3
-
4
- try {
5
- const code = await run(process.argv.slice(2), commands)
6
- process.exit(code)
7
- } catch (error) {
8
- console.error(error instanceof Error ? error.message : 'An unexpected error occurred.')
9
- process.exit(2)
10
- }
package/src/run.ts DELETED
@@ -1,82 +0,0 @@
1
- import { parse } from '@bomb.sh/args'
2
- import type { Command } from './commands.ts'
3
- import { printCommandHelp, printGlobalHelp } from './help.ts'
4
- import { findClosestCommand } from './suggest.ts'
5
- import { version } from './version.ts'
6
-
7
- export async function run(argv: string[], commands: Record<string, Command>): Promise<number> {
8
- const args = parse(argv, {
9
- boolean: ['help', 'version'],
10
- })
11
-
12
- if (args.version) {
13
- console.log(version)
14
- return 0
15
- }
16
-
17
- const commandName = String(args._[0] ?? '')
18
-
19
- // No command given — show global help
20
- if (!commandName) {
21
- printGlobalHelp(commands)
22
- return 0
23
- }
24
-
25
- // Explicit `help` command: `facets help` or `facets help build`
26
- if (commandName === 'help') {
27
- const subCommandName = String(args._[1] ?? '')
28
- const subCommand = subCommandName ? commands[subCommandName] : undefined
29
- if (subCommand) {
30
- printCommandHelp(subCommand)
31
- } else {
32
- printGlobalHelp(commands)
33
- }
34
- return 0
35
- }
36
-
37
- const command = commands[commandName]
38
-
39
- if (!command) {
40
- const suggestion = findClosestCommand(commandName, Object.keys(commands))
41
- const message = suggestion
42
- ? `Unknown command "${commandName}". Did you mean "${suggestion}"?`
43
- : `Unknown command "${commandName}".`
44
- console.error(message)
45
- return 1
46
- }
47
-
48
- // Per-command help: `facets build --help`
49
- if (args.help) {
50
- printCommandHelp(command)
51
- return 0
52
- }
53
-
54
- // Build per-command flag parsing config
55
- const booleanFlags: string[] = []
56
- const stringFlags: string[] = []
57
-
58
- if (command.flags) {
59
- for (const [name, def] of Object.entries(command.flags)) {
60
- if (def.type === 'boolean') booleanFlags.push(name)
61
- else if (def.type === 'string') stringFlags.push(name)
62
- }
63
- }
64
-
65
- // Parse with per-command config
66
- const parsed = parse(argv.slice(1), {
67
- boolean: booleanFlags,
68
- string: stringFlags,
69
- })
70
-
71
- // Build positional args and flags
72
- const positionalArgs = parsed._.map(String)
73
- const flags: Record<string, unknown> = {}
74
-
75
- for (const name of [...booleanFlags, ...stringFlags]) {
76
- if (parsed[name] !== undefined) {
77
- flags[name] = parsed[name]
78
- }
79
- }
80
-
81
- return command.run(positionalArgs, flags)
82
- }
package/src/suggest.ts DELETED
@@ -1,35 +0,0 @@
1
- function levenshtein(a: string, b: string): number {
2
- const m = a.length
3
- const n = b.length
4
- const row: number[] = []
5
-
6
- for (let j = 0; j <= n; j++) row.push(j)
7
-
8
- for (let i = 1; i <= m; i++) {
9
- let prev = row[0] ?? 0
10
- row[0] = i
11
- for (let j = 1; j <= n; j++) {
12
- const current = row[j] ?? 0
13
- const cost = a[i - 1] === b[j - 1] ? 0 : 1
14
- row[j] = Math.min(current + 1, (row[j - 1] ?? 0) + 1, prev + cost)
15
- prev = current
16
- }
17
- }
18
-
19
- return row[n] ?? 0
20
- }
21
-
22
- export function findClosestCommand(input: string, commandNames: string[]): string | undefined {
23
- let best: string | undefined
24
- let bestDistance = Infinity
25
-
26
- for (const name of commandNames) {
27
- const distance = levenshtein(input, name)
28
- if (distance < bestDistance) {
29
- bestDistance = distance
30
- best = name
31
- }
32
- }
33
-
34
- return bestDistance <= 3 ? best : undefined
35
- }
@@ -1,17 +0,0 @@
1
- import { Box, Text } from 'ink'
2
-
3
- export function truncateDescription(text: string, maxLen = 50): string {
4
- const lines = text.split(/\r?\n/)
5
- const firstLine = lines[0] ?? text
6
- const hasMore = lines.length > 1
7
- if (firstLine.length <= maxLen) return hasMore ? `${firstLine}...` : firstLine
8
- return `${firstLine.slice(0, maxLen)}...`
9
- }
10
-
11
- export function AssetDescription({ description }: { description: string }) {
12
- return (
13
- <Box marginLeft={4}>
14
- <Text dimColor>{truncateDescription(description)}</Text>
15
- </Box>
16
- )
17
- }
@@ -1,78 +0,0 @@
1
- import { Box, Text, useInput } from 'ink'
2
- import { useEffect, useState } from 'react'
3
- import { useFocusMode } from '../context/focus-mode-context.ts'
4
- import { THEME } from '../theme.ts'
5
-
6
- export type AssetField = 'name' | 'description'
7
-
8
- export function AssetFieldPicker({
9
- name,
10
- description,
11
- initialField,
12
- onChoose,
13
- onCancel,
14
- }: {
15
- name: string
16
- description: string
17
- initialField?: AssetField
18
- onChoose: (field: AssetField) => void
19
- onCancel: () => void
20
- }) {
21
- const { setMode } = useFocusMode()
22
- const [field, setField] = useState<AssetField>(initialField ?? 'name')
23
-
24
- useEffect(() => {
25
- setMode('field-revision')
26
- return () => setMode('form-navigation')
27
- }, [setMode])
28
-
29
- useInput((_input, key) => {
30
- if (key.upArrow) setField('name')
31
- if (key.downArrow) setField('description')
32
- if (key.escape) onCancel()
33
- if (key.return) onChoose(field)
34
- })
35
-
36
- return (
37
- <Box flexDirection="column">
38
- <Box gap={1} marginLeft={2}>
39
- {field === 'name' ? (
40
- <>
41
- <Text color={THEME.primary} bold>
42
-
43
- </Text>
44
- <Text color={THEME.primary}>{name}</Text>
45
- <Text color={THEME.hint}>
46
- <Text color={THEME.keyword}>↑↓</Text> select · <Text color={THEME.keyword}>Enter</Text> edit ·{' '}
47
- <Text color={THEME.keyword}>Esc</Text> back
48
- </Text>
49
- </>
50
- ) : (
51
- <>
52
- <Text color={THEME.success}>•</Text>
53
- <Text>{name}</Text>
54
- </>
55
- )}
56
- </Box>
57
- <Box gap={1} marginLeft={2}>
58
- {field === 'description' ? (
59
- <>
60
- <Text color={THEME.primary} bold>
61
-
62
- </Text>
63
- <Text color={THEME.primary}>{description}</Text>
64
- <Text color={THEME.hint}>
65
- <Text color={THEME.keyword}>↑↓</Text> select · <Text color={THEME.keyword}>Enter</Text> edit ·{' '}
66
- <Text color={THEME.keyword}>Esc</Text> back
67
- </Text>
68
- </>
69
- ) : (
70
- <>
71
- <Text> </Text>
72
- <Text dimColor>{description}</Text>
73
- </>
74
- )}
75
- </Box>
76
- </Box>
77
- )
78
- }
@@ -1,91 +0,0 @@
1
- import { Box, Text, useInput } from 'ink'
2
- import TextInput from 'ink-text-input'
3
- import { THEME } from '../theme.ts'
4
-
5
- export function AssetInlineInput({
6
- value,
7
- placeholder,
8
- error,
9
- isFocused,
10
- onChange,
11
- validate,
12
- onError,
13
- onSubmit,
14
- onCancel,
15
- onDownArrow,
16
- }: {
17
- id: string
18
- value: string
19
- placeholder?: string
20
- error: string
21
- isFocused: boolean
22
- onChange: (value: string) => void
23
- validate?: (value: string) => string | undefined
24
- onError: (error: string) => void
25
- onSubmit: (name: string) => void
26
- onCancel: () => void
27
- onDownArrow?: () => void
28
- }) {
29
- useInput(
30
- (_input, key) => {
31
- if (!isFocused) return
32
- if (key.return) {
33
- const name = value || placeholder || ''
34
- if (!name) {
35
- onCancel()
36
- return
37
- }
38
- if (validate) {
39
- const err = validate(name)
40
- if (err) {
41
- onError(err)
42
- return
43
- }
44
- }
45
- onSubmit(name)
46
- return
47
- }
48
- if (key.escape) {
49
- onCancel()
50
- return
51
- }
52
- if (key.downArrow && onDownArrow) {
53
- onDownArrow()
54
- return
55
- }
56
- if (key.tab && !value && placeholder) {
57
- onChange(placeholder)
58
- return
59
- }
60
- },
61
- { isActive: isFocused },
62
- )
63
-
64
- return (
65
- <Box marginLeft={2} gap={1}>
66
- <Text color={THEME.tertiary}>{'>'}</Text>
67
- <TextInput value={value} onChange={onChange} placeholder={placeholder} focus={isFocused} />
68
- {error ? (
69
- <Text color={THEME.warning}>· {error}</Text>
70
- ) : value ? (
71
- <Text color={THEME.hint}>
72
- · <Text color={THEME.keyword}>Enter</Text> save ·{' '}
73
- {onDownArrow && (
74
- <>
75
- <Text color={THEME.keyword}>↓</Text> description ·{' '}
76
- </>
77
- )}
78
- <Text color={THEME.keyword}>Esc</Text> revert
79
- </Text>
80
- ) : placeholder ? (
81
- <Text color={THEME.hint}>
82
- · <Text color={THEME.keyword}>Tab</Text> to fill · <Text color={THEME.keyword}>Escape</Text> to cancel
83
- </Text>
84
- ) : (
85
- <Text color={THEME.hint}>
86
- · <Text color={THEME.keyword}>Enter</Text> to add · <Text color={THEME.keyword}>Escape</Text> to cancel
87
- </Text>
88
- )}
89
- </Box>
90
- )
91
- }
@@ -1,44 +0,0 @@
1
- import { Box, Text, useInput } from 'ink'
2
- import { THEME } from '../theme.ts'
3
-
4
- export function AssetItem({
5
- name,
6
- isFocused,
7
- onEdit,
8
- onRemove,
9
- }: {
10
- id: string
11
- name: string
12
- isFocused: boolean
13
- onEdit: () => void
14
- onRemove: () => void
15
- }) {
16
- useInput(
17
- (_input, key) => {
18
- if (key.return) onEdit()
19
- if (key.delete || key.backspace) onRemove()
20
- },
21
- { isActive: isFocused },
22
- )
23
-
24
- return (
25
- <Box gap={1} marginLeft={2}>
26
- {isFocused ? (
27
- <>
28
- <Text color={THEME.primary} bold>
29
-
30
- </Text>
31
- <Text color={THEME.primary}>{name}</Text>
32
- <Text color={THEME.hint}>
33
- <Text color={THEME.keyword}>Enter</Text> edit name · <Text color={THEME.keyword}>Del</Text> remove
34
- </Text>
35
- </>
36
- ) : (
37
- <>
38
- <Text color={THEME.success}>•</Text>
39
- <Text>{name}</Text>
40
- </>
41
- )}
42
- </Box>
43
- )
44
- }
@@ -1,191 +0,0 @@
1
- import { Box, Text } from 'ink'
2
- import { useState } from 'react'
3
- import { useFocusMode } from '../context/focus-mode-context.ts'
4
- import { useFocusOrder } from '../context/focus-order-context.ts'
5
- import type { AssetSectionKey } from '../context/form-state-context.ts'
6
- import { useFormState } from '../context/form-state-context.ts'
7
- import { AssetDescription, truncateDescription } from './asset-description.tsx'
8
- import type { AssetField } from './asset-field-picker.tsx'
9
- import { AssetFieldPicker } from './asset-field-picker.tsx'
10
- import { AssetInlineInput } from './asset-inline-input.tsx'
11
- import { AssetItem } from './asset-item.tsx'
12
- import { Button } from './button.tsx'
13
-
14
- export function AssetSection({
15
- section,
16
- label,
17
- defaultName,
18
- dimmed,
19
- validate,
20
- onEditDescription,
21
- }: {
22
- section: AssetSectionKey
23
- label: string
24
- defaultName?: string
25
- dimmed?: boolean
26
- validate?: (value: string) => string | undefined
27
- onEditDescription?: (section: AssetSectionKey, name: string) => void
28
- }) {
29
- const { form, addAsset, removeAsset, renameAsset, setAssetAdding, setAssetEditing } = useFormState()
30
- const { items, descriptions, editing, adding } = form.assets[section]
31
- const { setMode } = useFocusMode()
32
- const { focusedId, focus } = useFocusOrder()
33
- const [inputValue, setInputValue] = useState('')
34
- const [error, setError] = useState('')
35
- const [selectedItem, setSelectedItem] = useState<string | null>(null)
36
-
37
- const startAdding = () => {
38
- setAssetAdding(section, true)
39
- setInputValue('')
40
- setError('')
41
- setMode('field-revision')
42
- }
43
-
44
- const startEditing = (name: string) => {
45
- setAssetEditing(section, name)
46
- setInputValue(name)
47
- setError('')
48
- setMode('field-revision')
49
- }
50
-
51
- const closeInput = (focusTarget?: string | false) => {
52
- setAssetAdding(section, false)
53
- setAssetEditing(section, undefined)
54
- setInputValue('')
55
- setError('')
56
- if (focusTarget !== false) {
57
- setMode('form-navigation')
58
- focus(focusTarget ?? `add-${section}`)
59
- }
60
- }
61
-
62
- const handleFieldChoice = (name: string, field: AssetField) => {
63
- setSelectedItem(null)
64
- if (field === 'name') {
65
- startEditing(name)
66
- } else {
67
- onEditDescription?.(section, name)
68
- }
69
- }
70
-
71
- const handleRemove = (name: string) => {
72
- const index = items.indexOf(name)
73
- removeAsset(section, name)
74
-
75
- if (index < items.length - 1) {
76
- focus(`item-${section}-${index}`)
77
- } else if (index > 0) {
78
- focus(`item-${section}-${index - 1}`)
79
- } else {
80
- focus(`add-${section}`)
81
- }
82
- }
83
-
84
- return (
85
- <Box flexDirection="column" gap={0}>
86
- <Box gap={1}>
87
- <Text bold dimColor={dimmed}>
88
- {label}
89
- </Text>
90
- {items.length === 0 && !adding && <Text dimColor>(none)</Text>}
91
- </Box>
92
-
93
- {items.map((item, i) => {
94
- const itemId = `item-${section}-${i}`
95
- const isFocusedItem = focusedId === itemId
96
- const description = descriptions[item] ?? `A ${item} ${section}`
97
-
98
- // Field picker (entered via ↓ during name editing)
99
- if (selectedItem === item) {
100
- return (
101
- <AssetFieldPicker
102
- key={itemId}
103
- name={item}
104
- description={truncateDescription(description)}
105
- initialField="description"
106
- onChoose={(field) => handleFieldChoice(item, field)}
107
- onCancel={() => {
108
- setSelectedItem(null)
109
- setMode('form-navigation')
110
- focus(itemId)
111
- }}
112
- />
113
- )
114
- }
115
-
116
- // Inline name editing
117
- if (editing === item) {
118
- return (
119
- <Box key={itemId} flexDirection="column">
120
- <AssetInlineInput
121
- id={itemId}
122
- value={inputValue}
123
- placeholder={item}
124
- error={error}
125
- isFocused={isFocusedItem}
126
- onChange={setInputValue}
127
- validate={validate}
128
- onError={setError}
129
- onSubmit={(newName) => {
130
- renameAsset(section, item, newName)
131
- closeInput(itemId)
132
- }}
133
- onCancel={() => closeInput(itemId)}
134
- onDownArrow={() => {
135
- closeInput(false)
136
- setSelectedItem(item)
137
- }}
138
- />
139
- <AssetDescription description={description} />
140
- </Box>
141
- )
142
- }
143
-
144
- // Normal display (level 1)
145
- return (
146
- <Box key={itemId} flexDirection="column">
147
- <AssetItem
148
- id={itemId}
149
- name={item}
150
- isFocused={isFocusedItem}
151
- onEdit={() => startEditing(item)}
152
- onRemove={() => handleRemove(item)}
153
- />
154
- <AssetDescription description={description} />
155
- </Box>
156
- )
157
- })}
158
-
159
- {adding ? (
160
- <AssetInlineInput
161
- id={`add-${section}`}
162
- value={inputValue}
163
- placeholder={defaultName}
164
- error={error}
165
- isFocused={focusedId === `add-${section}`}
166
- onChange={setInputValue}
167
- validate={validate}
168
- onError={setError}
169
- onSubmit={(name) => {
170
- addAsset(section, name)
171
- closeInput()
172
- }}
173
- onCancel={closeInput}
174
- />
175
- ) : (
176
- <Box marginLeft={2}>
177
- <Button
178
- id={`add-${section}`}
179
- label="+ Add"
180
- hint={
181
- <Text dimColor>
182
- <Text>Enter</Text> to add
183
- </Text>
184
- }
185
- onPress={startAdding}
186
- />
187
- </Box>
188
- )}
189
- </Box>
190
- )
191
- }
@@ -1,92 +0,0 @@
1
- import { Box, Text, useInput } from 'ink'
2
- import Gradient from 'ink-gradient'
3
- import type { ReactNode } from 'react'
4
- import { useEffect, useState } from 'react'
5
- import { useFocusOrder } from '../context/focus-order-context.ts'
6
- import { GRADIENT_STOPS, getAnimatedGradient } from '../gradient.ts'
7
- import { THEME } from '../theme.ts'
8
-
9
- const ANIMATION_INTERVAL_MS = 75
10
-
11
- export function Button({
12
- id,
13
- label,
14
- hint,
15
- onPress,
16
- disabled,
17
- color,
18
- gradient: showGradient,
19
- animateGradient,
20
- }: {
21
- id: string
22
- label: string
23
- hint?: ReactNode
24
- onPress: () => void
25
- disabled?: boolean
26
- color?: string
27
- autoFocus?: boolean
28
- gradient?: boolean
29
- animateGradient?: boolean
30
- }) {
31
- const { focusedId } = useFocusOrder()
32
- const isFocused = focusedId === id && !disabled
33
- const [offset, setOffset] = useState(0)
34
-
35
- useEffect(() => {
36
- if (!animateGradient) return
37
- const interval = setInterval(() => {
38
- setOffset((prev) => (prev + 1) % GRADIENT_STOPS.length)
39
- }, ANIMATION_INTERVAL_MS)
40
- return () => clearInterval(interval)
41
- }, [animateGradient])
42
-
43
- useInput(
44
- (_input, key) => {
45
- if (key.return) {
46
- onPress()
47
- }
48
- },
49
- { isActive: isFocused },
50
- )
51
-
52
- const prefix = isFocused ? '▸ ' : ' '
53
-
54
- const focusHint = isFocused && hint ? <Text> {hint}</Text> : null
55
-
56
- if (disabled) {
57
- return (
58
- <Box gap={0}>
59
- <Text color="gray" dimColor>
60
- {prefix}
61
- {label}
62
- </Text>
63
- </Box>
64
- )
65
- }
66
-
67
- if (showGradient) {
68
- const colors = animateGradient ? getAnimatedGradient(offset) : [...THEME.gradient]
69
-
70
- return (
71
- <Box gap={0}>
72
- <Gradient colors={colors}>
73
- <Text bold>
74
- {prefix}
75
- {label}
76
- </Text>
77
- </Gradient>
78
- {focusHint}
79
- </Box>
80
- )
81
- }
82
-
83
- return (
84
- <Box gap={0}>
85
- <Text color={isFocused ? (color ?? THEME.primary) : undefined} bold={isFocused}>
86
- {prefix}
87
- {label}
88
- </Text>
89
- {focusHint}
90
- </Box>
91
- )
92
- }