import { Box, Text } from 'ink' import React, { type ReactNode } from 'react' import { SelectOption } from './select-option' import { type Theme } from './theme' import { useSelectState } from './use-select-state' import { useSelect } from './use-select' import { Option } from '@inkjs/ui' import { getTheme } from '../../utils/theme' export type OptionSubtree = { /** * Header to show above sub-options. */ readonly header?: string /** * Options. */ readonly options: (Option | OptionSubtree)[] } export type OptionHeader = { readonly header: string readonly optionValues: string[] } export const optionHeaderKey = (optionHeader: OptionHeader): string => `HEADER-${optionHeader.optionValues.join(',')}` export type SelectProps = { /** * When disabled, user input is ignored. * * @default false */ readonly isDisabled?: boolean /** * Number of visible options. * * @default 5 */ readonly visibleOptionCount?: number /** * Highlight text in option labels. */ readonly highlightText?: string /** * Options. */ readonly options: (Option | OptionSubtree)[] /** * Default value. */ readonly defaultValue?: string /** * Callback when selected option changes. */ readonly onChange?: (value: string) => void /** * Callback when focused option changes. */ readonly onFocus?: (value: string) => void /** * Value to focus */ readonly focusValue?: string } export function Select({ isDisabled = false, visibleOptionCount = 5, highlightText, options, defaultValue, onChange, onFocus, focusValue, }: SelectProps) { const state = useSelectState({ visibleOptionCount, options, defaultValue, onChange, onFocus, focusValue, }) useSelect({ isDisabled, state }) const appTheme = getTheme() const styles = { container: () => ({ flexDirection: 'column' as const, }), highlightedText: () => ({ color: appTheme.text, backgroundColor: appTheme.warning, }), } return ( {state.visibleOptions.map(option => { const key = 'value' in option ? option.value : optionHeaderKey(option) const isFocused = !isDisabled && state.focusedValue !== undefined && ('value' in option ? state.focusedValue === option.value : option.optionValues.includes(state.focusedValue)) const isSelected = !!state.value && ('value' in option ? state.value === option.value : option.optionValues.includes(state.value)) const smallPointer = 'header' in option const labelText = 'label' in option ? option.label : option.header let label: ReactNode = labelText if (highlightText && labelText.includes(highlightText)) { const index = labelText.indexOf(highlightText) label = ( <> {labelText.slice(0, index)} {highlightText} {labelText.slice(index + highlightText.length)} ) } return ( ) })} ) }