// The pill and choice-row primitives the session-option card is built from, kept
// beside it so the card file stays about layout and apply wiring.
import { Pressable, StyleSheet, Text, View } from 'react-native'
import { Check, ChevronDown, ChevronRight } from 'lucide-react-native'
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
import type {
SessionOptionDescriptor,
SessionOptionValue
} from '../../../src/shared/native-chat-session-options'
/** Muted one-liner above a group — dispatch state, or why a row is locked. */
export function SessionOptionCaption({ children }: { children: string }): React.JSX.Element {
return {children}
}
export function Pill({
label,
accessibleName,
disabled,
onPress
}: {
label: string
accessibleName: string
disabled: boolean
onPress: () => void
}): React.JSX.Element {
return (
[styles.pill, pressed && !disabled && styles.pressed]}
onPress={onPress}
disabled={disabled}
hitSlop={6}
>
{label}
)
}
function ChoiceRow({
label,
description,
selected,
disabled,
grouped,
divided,
onPress
}: {
label: string
description?: string
selected: boolean
disabled: boolean
grouped: boolean
divided: boolean
onPress: () => void
}): React.JSX.Element {
return (
{selected ? : null}
{label}
{description ? (
{description}
) : null}
)
}
function ActionRow({
label,
disabled,
grouped,
onPress
}: {
label: string
disabled: boolean
grouped: boolean
onPress: () => void
}): React.JSX.Element {
return (
{label}
)
}
export function SessionOptionSummaryRow({
label,
value,
disabled,
divided,
onPress
}: {
label: string
value: string
disabled: boolean
divided: boolean
onPress: () => void
}): React.JSX.Element {
return (
[
styles.summaryRow,
divided && styles.rowDivided,
pressed && !disabled && styles.pressed,
disabled && styles.rowDisabled
]}
onPress={onPress}
disabled={disabled}
>
{label}
{value}
)
}
export function DescriptorRows({
descriptor,
disabled,
grouped = false,
onSetOption,
onInvokeAction
}: {
descriptor: SessionOptionDescriptor
disabled: boolean
grouped?: boolean
onSetOption: (value: SessionOptionValue) => void
onInvokeAction: () => void
}): React.JSX.Element {
const locked = disabled || !descriptor.settable
// Why: flip-only without a baseline is an action — never claim On/Off.
if (descriptor.action?.type === 'toggle-command') {
return (
)
}
// Why: agent-picker opens the TUI; it is not a set of radio choices.
if (descriptor.action?.type === 'agent-picker') {
return (
)
}
// Unknown booleans leave both radios unselected instead of inventing truth.
if (descriptor.kind.type === 'boolean') {
const current = descriptor.kind.currentValue
return (
<>
{current === undefined ? (
Current value unknown — pick On or Off
) : null}
onSetOption(true)}
/>
onSetOption(false)}
/>
>
)
}
const { currentValue, choices } = descriptor.kind
return (
<>
{choices.map((choice, index) => (
onSetOption(choice.value)}
/>
))}
>
)
}
const styles = StyleSheet.create({
pill: {
flexDirection: 'row',
alignItems: 'center',
gap: 4,
maxWidth: 180,
minHeight: 28,
paddingHorizontal: spacing.sm,
paddingVertical: 4,
borderRadius: radii.button,
borderWidth: StyleSheet.hairlineWidth,
borderColor: colors.borderSubtle,
backgroundColor: colors.bgRaised
},
pillText: {
color: colors.textSecondary,
fontSize: typography.metaSize,
fontWeight: '600',
flexShrink: 1
},
pillTextDisabled: {
color: colors.textMuted
},
pressed: {
opacity: 0.7
},
caption: {
color: colors.textMuted,
fontSize: typography.metaSize,
paddingHorizontal: spacing.md,
paddingBottom: spacing.xs
},
row: {
flexDirection: 'row',
gap: spacing.sm,
padding: spacing.sm,
minHeight: 44,
alignItems: 'center',
borderRadius: radii.card,
backgroundColor: colors.bgRaised,
borderWidth: 1,
borderColor: colors.borderSubtle,
marginBottom: spacing.xs
},
rowSelected: {
borderColor: colors.statusGreen
},
rowGrouped: {
marginBottom: 0,
borderWidth: 0,
borderRadius: 0,
backgroundColor: 'transparent'
},
rowDivided: {
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: colors.borderSubtle
},
rowDisabled: {
opacity: 0.5
},
radio: {
width: 18,
height: 18,
borderRadius: 9,
borderWidth: 1.5,
borderColor: colors.textMuted,
alignItems: 'center',
justifyContent: 'center'
},
radioOn: {
backgroundColor: colors.statusGreen,
borderColor: colors.statusGreen
},
rowBody: {
flex: 1,
gap: 2
},
rowLabel: {
color: colors.textPrimary,
fontSize: typography.bodySize,
fontWeight: '600'
},
rowDescription: {
color: colors.textSecondary,
fontSize: typography.metaSize
},
summaryRow: {
minHeight: 48,
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm
},
summaryLabel: {
flex: 1,
color: colors.textPrimary,
fontSize: typography.bodySize,
fontWeight: '600'
},
summaryValue: {
maxWidth: 160,
color: colors.textSecondary,
fontSize: typography.bodySize
}
})