fix: make mobile type picker searchable

This commit is contained in:
lucaronin
2026-05-14 11:21:59 +02:00
parent 97f6a8c2b4
commit e3146e535f
5 changed files with 81 additions and 2 deletions

View File

@@ -14,6 +14,7 @@ import {
toggleMobileNoteTag,
type MobileNotePropertyPatch,
} from './mobileNoteProperties'
import { filterMobilePropertyComboOptions, resolveMobilePropertyComboValue } from './mobilePropertyCombo'
import { mobilePropertyDisplayValue, type MobilePropertyPickerKey } from './mobilePropertyPicker'
import { styles } from './styles'
import { colors } from './theme'
@@ -110,7 +111,12 @@ function EditableTextProperty({
variant?: 'chips' | 'combo'
}) {
const [draft, setDraft] = useState(value)
const commitDraft = () => commitTextValue({ current: value, next: draft, onCommit })
const commitDraft = () => {
const next = variant === 'combo'
? resolveMobilePropertyComboValue({ options: suggestions, value: draft })
: draft
commitTextValue({ current: value, next, onCommit, onSettled: setDraft })
}
return (
<PropertyPickerSection
@@ -123,6 +129,7 @@ function EditableTextProperty({
<View style={styles.propertyPickerOptions}>
<TextInput
autoCapitalize="sentences"
autoFocus={variant === 'combo'}
editable={!disabled}
keyboardType="default"
onBlur={commitDraft}
@@ -131,6 +138,7 @@ function EditableTextProperty({
placeholder={placeholder}
placeholderTextColor={colors.mutedText}
returnKeyType="done"
selectTextOnFocus={variant === 'combo'}
style={styles.propertyTextInput}
value={draft}
/>
@@ -138,6 +146,7 @@ function EditableTextProperty({
? (
<PropertyComboOptions
disabled={disabled}
query={draft}
suggestions={suggestions}
value={value}
onSelect={(selected) => {
@@ -348,17 +357,21 @@ function PropertyChipOptions({ children }: { children: ReactNode }) {
function PropertyComboOptions({
disabled,
onSelect,
query,
suggestions,
value,
}: {
disabled: boolean
onSelect: (option: string) => void
query: string
suggestions: readonly string[]
value: string
}) {
const options = filterMobilePropertyComboOptions({ options: suggestions, query })
return (
<View style={styles.propertyComboBox}>
{suggestions.map((option) => {
{options.map((option) => {
const isSelected = isMobileNotePropertySelected({ current: value, option })
return (
@@ -379,6 +392,7 @@ function PropertyComboOptions({
</Pressable>
)
})}
{options.length === 0 ? <Text style={styles.propertyComboEmpty}>No matching types.</Text> : null}
</View>
)
}
@@ -445,12 +459,15 @@ function commitTextValue({
current,
next,
onCommit,
onSettled,
}: {
current: string
next: string
onCommit: (value: string) => void
onSettled?: (value: string) => void
}) {
const normalized = next.trim()
onSettled?.(normalized)
if (normalized !== current) {
onCommit(normalized)
}

View File

@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest'
import { filterMobilePropertyComboOptions, resolveMobilePropertyComboValue } from './mobilePropertyCombo'
describe('mobile property combo', () => {
const options = ['Note', 'Essay', 'Project', 'Evergreen']
it('filters options from typed text', () => {
expect(filterMobilePropertyComboOptions({ options, query: 'ess' })).toEqual(['Essay'])
expect(filterMobilePropertyComboOptions({ options, query: 'e' })).toEqual(['Note', 'Essay', 'Project', 'Evergreen'])
})
it('normalizes exact typed selections to the canonical option label', () => {
expect(resolveMobilePropertyComboValue({ options, value: ' essay ' })).toBe('Essay')
expect(resolveMobilePropertyComboValue({ options, value: 'Custom' })).toBe('Custom')
})
})

View File

@@ -0,0 +1,30 @@
export function filterMobilePropertyComboOptions({
options,
query,
}: {
options: readonly string[]
query: string
}) {
const normalizedQuery = normalizeComboText(query)
if (!normalizedQuery) {
return options
}
return options.filter((option) => normalizeComboText(option).includes(normalizedQuery))
}
export function resolveMobilePropertyComboValue({
options,
value,
}: {
options: readonly string[]
value: string
}) {
const trimmed = value.trim()
const exactOption = options.find((option) => normalizeComboText(option) === normalizeComboText(trimmed))
return exactOption ?? trimmed
}
function normalizeComboText(value: string) {
return value.trim().toLowerCase()
}

View File

@@ -27,4 +27,11 @@ export const propertyComboStyles = StyleSheet.create({
propertyComboOptionTextSelected: {
color: colors.primary,
},
propertyComboEmpty: {
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
color: colors.mutedText,
fontSize: 13,
fontWeight: '600',
},
})

View File

@@ -515,3 +515,12 @@ Continue Phase 4 as a quality remediation pass before new feature work:
- `pnpm --filter @tolaria/mobile typecheck` passed.
- `pnpm --filter @tolaria/mobile test -- src/mobileEditorMessages.test.ts src/mobileWikilinkAutocomplete.test.ts` passed; the mobile test runner executed 61 files / 202 tests.
- `pnpm --filter @tolaria/mobile exec expo export --platform ios --output-dir /tmp/tolaria-mobile-export-wikilink-autocomplete` passed.
## 2026-05-14 Type Combobox Follow-Up
- Reworked the mobile Type picker into a searchable combobox: opening Type focuses the text field, typing filters available type options, and exact typed matches normalize to the canonical type label.
- Added empty-state copy for unmatched type searches.
- Added pure mobile combobox tests for option filtering and canonical exact-match resolution.
- `pnpm --filter @tolaria/mobile typecheck` passed.
- `pnpm --filter @tolaria/mobile test -- src/mobilePropertyCombo.test.ts src/mobileNoteProperties.test.ts` passed; the mobile test runner executed 62 files / 204 tests.
- `pnpm --filter @tolaria/mobile exec expo export --platform ios --output-dir /tmp/tolaria-mobile-export-type-combobox` passed.