@helpwave/hightide-native 0.0.8 → 0.1.1

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 (125) hide show
  1. package/README.md +4 -4
  2. package/package.json +8 -19
  3. package/src/components/card/Card.tsx +41 -0
  4. package/src/components/card/index.ts +1 -0
  5. package/src/components/chat/ChatAttachmentMessageBubble.tsx +246 -0
  6. package/src/components/chat/ChatConversationRow.tsx +21 -16
  7. package/src/components/chat/ChatDateDivider.tsx +2 -2
  8. package/src/components/chat/ChatMessageBubble.tsx +62 -52
  9. package/src/components/chat/ChatMessageComposer.tsx +16 -14
  10. package/src/components/chat/ChatQuickReplyChip.tsx +41 -43
  11. package/src/components/chat/ChatSystemLine.tsx +14 -7
  12. package/src/components/chat/ChatThreadHeader.tsx +60 -6
  13. package/src/components/chat/index.ts +1 -2
  14. package/src/components/index.ts +3 -1
  15. package/src/components/layout/Divider.tsx +60 -0
  16. package/src/components/layout/index.ts +1 -0
  17. package/src/components/list/ListActionItem.tsx +99 -0
  18. package/src/components/list/ListItem.tsx +96 -0
  19. package/src/components/list/ListNavigationItem.tsx +102 -0
  20. package/src/components/list/index.ts +3 -0
  21. package/src/components/user-interaction/Button.tsx +80 -31
  22. package/src/components/user-interaction/Checkbox.tsx +72 -32
  23. package/src/components/user-interaction/IconButton.tsx +68 -30
  24. package/src/components/user-interaction/Input.tsx +61 -14
  25. package/src/components/user-interaction/MultiSelect.tsx +142 -75
  26. package/src/components/user-interaction/SearchBar.tsx +222 -0
  27. package/src/components/user-interaction/Select.tsx +129 -58
  28. package/src/components/user-interaction/Switch.tsx +110 -80
  29. package/src/components/user-interaction/ThemedPressable.tsx +136 -0
  30. package/src/components/user-interaction/index.ts +2 -0
  31. package/src/components/visualization-and-display/Avatar.tsx +112 -93
  32. package/src/components/visualization-and-display/Chip.tsx +22 -22
  33. package/src/components/visualization-and-display/ThemedIcon.tsx +72 -0
  34. package/src/components/visualization-and-display/ThemedText.tsx +38 -0
  35. package/src/components/visualization-and-display/index.ts +2 -1
  36. package/src/global-contexts/HightideProvider.tsx +19 -10
  37. package/src/global-contexts/content-theme/ContentThemeContext.ts +22 -0
  38. package/src/global-contexts/content-theme/ContentThemeProvider.tsx +32 -0
  39. package/src/global-contexts/content-theme/index.ts +2 -0
  40. package/src/global-contexts/debug/forward-exports.ts +10 -0
  41. package/src/global-contexts/debug/index.ts +1 -0
  42. package/src/global-contexts/index.ts +2 -0
  43. package/src/global-contexts/theme/ThemeProvider.tsx +7 -1
  44. package/src/icons/HightideIconRegistry.ts +33 -0
  45. package/src/icons/index.ts +2 -0
  46. package/src/icons/types.ts +11 -0
  47. package/src/theme/adapters/container-adapter.ts +323 -0
  48. package/src/theme/adapters/defined.ts +11 -0
  49. package/src/theme/adapters/icon-style-adapter.ts +10 -0
  50. package/src/theme/adapters/index.ts +4 -0
  51. package/src/theme/adapters/text-style-adapter.ts +16 -0
  52. package/src/theme/index.ts +1 -0
  53. package/src/theme/resolvers/avatar.ts +385 -186
  54. package/src/theme/resolvers/button.ts +50 -82
  55. package/src/theme/resolvers/card.ts +27 -0
  56. package/src/theme/resolvers/chat/attachment-message-bubble.ts +163 -0
  57. package/src/theme/resolvers/chat/chat-theme.ts +69 -0
  58. package/src/theme/resolvers/chat/conversation-list.ts +35 -0
  59. package/src/theme/resolvers/chat/conversation-row.ts +64 -0
  60. package/src/theme/resolvers/chat/date-divider.ts +31 -0
  61. package/src/theme/resolvers/chat/index.ts +11 -0
  62. package/src/theme/resolvers/chat/message-bubble.ts +57 -0
  63. package/src/theme/resolvers/chat/message-composer.ts +38 -0
  64. package/src/theme/resolvers/chat/message-list.ts +27 -0
  65. package/src/theme/resolvers/chat/quick-reply-chip.ts +80 -0
  66. package/src/theme/resolvers/chat/system-line.ts +41 -0
  67. package/src/theme/resolvers/chat/thread-header.ts +85 -0
  68. package/src/theme/resolvers/checkbox.ts +87 -74
  69. package/src/theme/resolvers/chip.ts +30 -67
  70. package/src/theme/resolvers/colorScheme.ts +136 -0
  71. package/src/theme/resolvers/divider.ts +34 -0
  72. package/src/theme/resolvers/icon.ts +21 -0
  73. package/src/theme/resolvers/iconButton.ts +45 -61
  74. package/src/theme/resolvers/index.ts +7 -2
  75. package/src/theme/resolvers/input.ts +64 -42
  76. package/src/theme/resolvers/listItem.ts +115 -0
  77. package/src/theme/resolvers/multiSelect.ts +159 -84
  78. package/src/theme/resolvers/searchBar.ts +121 -0
  79. package/src/theme/resolvers/select.ts +141 -66
  80. package/src/theme/resolvers/switch.ts +59 -53
  81. package/src/theme/resolvers/themedPressable.ts +54 -0
  82. package/src/theme/themes/createHightideTheme.ts +214 -51
  83. package/src/theme/themes/hightideThemes.ts +10 -6
  84. package/src/theme/types/color.ts +2 -59
  85. package/src/theme/types/components/avatar.ts +37 -36
  86. package/src/theme/types/components/button.ts +13 -14
  87. package/src/theme/types/components/card.ts +9 -0
  88. package/src/theme/types/components/chat.ts +124 -103
  89. package/src/theme/types/components/checkbox.ts +11 -12
  90. package/src/theme/types/components/chip.ts +8 -14
  91. package/src/theme/types/components/divider.ts +18 -0
  92. package/src/theme/types/components/hightide.ts +41 -42
  93. package/src/theme/types/components/iconButton.ts +19 -14
  94. package/src/theme/types/components/index.ts +5 -1
  95. package/src/theme/types/components/input.ts +17 -8
  96. package/src/theme/types/components/listItem.ts +86 -0
  97. package/src/theme/types/components/multiSelect.ts +14 -12
  98. package/src/theme/types/components/searchBar.ts +28 -0
  99. package/src/theme/types/components/select.ts +21 -9
  100. package/src/theme/types/components/switch.ts +12 -5
  101. package/src/theme/types/components/themedPressable.ts +31 -0
  102. package/src/theme/types/decoration.ts +1 -11
  103. package/src/theme/types/icongraphy.ts +8 -0
  104. package/src/theme/types/index.ts +2 -0
  105. package/src/theme/types/layout.ts +25 -57
  106. package/src/theme/types/resolver.ts +54 -1
  107. package/src/theme/types/semantics.ts +72 -0
  108. package/src/theme/types/theme.ts +26 -13
  109. package/src/theme/types/typography.ts +16 -17
  110. package/src/utils/hitBoxOverlay.ts +44 -0
  111. package/src/utils/index.ts +2 -0
  112. package/src/utils/minimumTouchTargetHitSlop.ts +68 -0
  113. package/src/components/chat/ChatAttachmentCard.tsx +0 -110
  114. package/src/components/chat/ChatMessageCard.tsx +0 -118
  115. package/src/components/menu/Menu.tsx +0 -61
  116. package/src/components/menu/MenuActionItem.tsx +0 -90
  117. package/src/components/menu/MenuItem.tsx +0 -73
  118. package/src/components/menu/MenuNavigationItem.tsx +0 -90
  119. package/src/components/menu/index.ts +0 -4
  120. package/src/components/visualization-and-display/Icon.tsx +0 -27
  121. package/src/storybook/helper.tsx +0 -36
  122. package/src/theme/resolvers/chat.ts +0 -418
  123. package/src/theme/resolvers/coloring.ts +0 -94
  124. package/src/theme/resolvers/menu.ts +0 -120
  125. package/src/theme/types/components/menu.ts +0 -57
@@ -0,0 +1,222 @@
1
+ import {
2
+ forwardRef,
3
+ useMemo,
4
+ useState
5
+ } from 'react'
6
+ import {
7
+ TextInput,
8
+ View,
9
+ type StyleProp,
10
+ type TextInputProps,
11
+ type TextStyle,
12
+ type ViewStyle
13
+ } from 'react-native'
14
+
15
+ import {
16
+ useControlledState,
17
+ useDelay,
18
+ type UseDelayOptionsResolved
19
+ } from '@helpwave/hightide-utils/hooks'
20
+ import { useTranslation } from '@helpwave/hightide-utils/context'
21
+
22
+ import type { ColorPairToken } from '@helpwave/hightide-design/theme-tokens'
23
+ import { useTheme } from '../../global-contexts/theme/ThemeContext'
24
+ import { HightideIconRegistry } from '../../icons/HightideIconRegistry'
25
+ import type {
26
+ SearchBarContainerStyle,
27
+ SearchBarIconButtonStyle,
28
+ SearchBarInputStyle,
29
+ SearchBarState
30
+ } from '../../theme/types/components/searchBar'
31
+ import type { StyleOverwrite } from '../../theme/types/resolver'
32
+ import type {
33
+ FormFieldDataHandling,
34
+ FormFieldInteractionStates
35
+ } from '../../types/formField'
36
+ import { IconButton, type IconButtonProps } from './IconButton'
37
+
38
+ export type SearchBarEditCompleteOptionsResolved = {
39
+ onBlur: boolean,
40
+ afterDelay: boolean,
41
+ allowEnterComplete?: boolean,
42
+ } & Omit<UseDelayOptionsResolved, 'disabled'>
43
+
44
+ export type SearchBarEditCompleteOptions = Partial<SearchBarEditCompleteOptionsResolved>
45
+
46
+ const defaultEditCompleteOptions: SearchBarEditCompleteOptionsResolved = {
47
+ allowEnterComplete: true,
48
+ onBlur: true,
49
+ afterDelay: false,
50
+ delay: 2500,
51
+ }
52
+
53
+ export type SearchBarProps = Omit<TextInputProps, 'value' | 'style'>
54
+ & Partial<FormFieldDataHandling<string>>
55
+ & Partial<FormFieldInteractionStates>
56
+ & {
57
+ color?: ColorPairToken,
58
+ onSearch: (value: string) => void,
59
+ searchButtonProps?: Omit<IconButtonProps, 'onPress' | 'icon' | 'accessibilityLabel'>,
60
+ editCompleteOptions?: SearchBarEditCompleteOptions,
61
+ initialValue?: string,
62
+ style?: StyleProp<ViewStyle>,
63
+ containerStyle?: StyleOverwrite<SearchBarState, SearchBarContainerStyle>,
64
+ inputStyle?: StyleOverwrite<SearchBarState, SearchBarInputStyle>,
65
+ iconButtonStyle?: StyleOverwrite<SearchBarState, SearchBarIconButtonStyle>,
66
+ textStyle?: StyleProp<TextStyle>,
67
+ }
68
+
69
+ export const SearchBar = forwardRef<TextInput, SearchBarProps>(function SearchBar({
70
+ value: controlledValue,
71
+ initialValue,
72
+ color,
73
+ invalid = false,
74
+ disabled = false,
75
+ readOnly = false,
76
+ required = false,
77
+ onValueChange,
78
+ onEditComplete,
79
+ onSearch,
80
+ searchButtonProps,
81
+ editCompleteOptions,
82
+ style,
83
+ containerStyle,
84
+ inputStyle,
85
+ iconButtonStyle,
86
+ textStyle,
87
+ placeholder,
88
+ ...props
89
+ }, ref) {
90
+ const { theme } = useTheme()
91
+ const translation = useTranslation()
92
+ const [isHovered, setIsHovered] = useState(false)
93
+ const [isPressed, setIsPressed] = useState(false)
94
+ const [isFocused, setIsFocused] = useState(false)
95
+
96
+ const [value, setValue] = useControlledState({
97
+ value: controlledValue,
98
+ onValueChange,
99
+ defaultValue: initialValue,
100
+ })
101
+
102
+ const {
103
+ onBlur: allowEditCompleteOnBlur,
104
+ afterDelay,
105
+ delay,
106
+ allowEnterComplete,
107
+ } = { ...defaultEditCompleteOptions, ...editCompleteOptions }
108
+
109
+ const { restartTimer, clearTimer } = useDelay({
110
+ delay,
111
+ disabled: !afterDelay || disabled || readOnly,
112
+ })
113
+
114
+ const interactive = !disabled && !readOnly
115
+
116
+ const state = useMemo((): SearchBarState => ({
117
+ color,
118
+ isDisabled: disabled,
119
+ isInvalid: invalid,
120
+ isReadonly: readOnly,
121
+ isHovered: interactive && isHovered,
122
+ isPressed: interactive && isPressed,
123
+ isFocused: interactive && isFocused,
124
+ }), [color, disabled, invalid, readOnly, interactive, isHovered, isPressed, isFocused])
125
+
126
+ const searchBarTheme = theme.components.searchBar
127
+ const resolvedContainerStyle = useMemo(
128
+ () => searchBarTheme.container(state, containerStyle),
129
+ [searchBarTheme, state, containerStyle]
130
+ )
131
+ const resolvedInputStyle = useMemo(
132
+ () => searchBarTheme.input(state, inputStyle),
133
+ [searchBarTheme, state, inputStyle]
134
+ )
135
+ const resolvedPlaceholderStyle = useMemo(
136
+ () => searchBarTheme.placeholder(state),
137
+ [searchBarTheme, state]
138
+ )
139
+ const resolvedIconButtonStyle = useMemo(
140
+ () => searchBarTheme.iconButton(state, iconButtonStyle),
141
+ [searchBarTheme, state, iconButtonStyle]
142
+ )
143
+
144
+ const commitSearch = (nextValue: string) => {
145
+ onSearch(nextValue)
146
+ onEditComplete?.(nextValue)
147
+ }
148
+
149
+ return (
150
+ <View style={[resolvedContainerStyle, style]}>
151
+ <TextInput
152
+ {...props}
153
+ ref={ref}
154
+ value={value}
155
+ editable={interactive}
156
+ placeholder={placeholder ?? translation('search')}
157
+ placeholderTextColor={resolvedPlaceholderStyle.color}
158
+ onChangeText={(nextValue) => {
159
+ props.onChangeText?.(nextValue)
160
+ restartTimer(() => {
161
+ commitSearch(nextValue)
162
+ })
163
+ setValue(nextValue)
164
+ }}
165
+ onFocus={(event) => {
166
+ if (interactive) {
167
+ setIsFocused(true)
168
+ }
169
+ props.onFocus?.(event)
170
+ }}
171
+ onBlur={(event) => {
172
+ setIsFocused(false)
173
+ props.onBlur?.(event)
174
+ if (allowEditCompleteOnBlur) {
175
+ commitSearch(value ?? '')
176
+ clearTimer()
177
+ }
178
+ }}
179
+ onPressIn={(event) => {
180
+ if (interactive) {
181
+ setIsPressed(true)
182
+ }
183
+ props.onPressIn?.(event)
184
+ }}
185
+ onPressOut={(event) => {
186
+ setIsPressed(false)
187
+ props.onPressOut?.(event)
188
+ }}
189
+ onPointerEnter={(event) => {
190
+ if (interactive) {
191
+ setIsHovered(true)
192
+ }
193
+ props.onPointerEnter?.(event)
194
+ }}
195
+ onPointerLeave={(event) => {
196
+ setIsHovered(false)
197
+ props.onPointerLeave?.(event)
198
+ }}
199
+ onSubmitEditing={(event) => {
200
+ props.onSubmitEditing?.(event)
201
+ if (allowEnterComplete) {
202
+ commitSearch(value ?? '')
203
+ clearTimer()
204
+ }
205
+ }}
206
+ style={[resolvedInputStyle, textStyle]}
207
+ accessibilityState={{ disabled, selected: required }}
208
+ />
209
+ <IconButton
210
+ {...searchButtonProps}
211
+ icon={HightideIconRegistry.Search}
212
+ accessibilityLabel={translation('search')}
213
+ size="sm"
214
+ color={searchButtonProps?.color ?? searchBarTheme.iconButtonColor(state)}
215
+ variant={searchButtonProps?.variant ?? 'foreground'}
216
+ disabled={disabled || searchButtonProps?.disabled}
217
+ onPress={() => commitSearch(value ?? '')}
218
+ style={[resolvedIconButtonStyle, searchButtonProps?.style]}
219
+ />
220
+ </View>
221
+ )
222
+ })
@@ -1,25 +1,31 @@
1
- import { useMemo } from 'react'
1
+ import { Fragment, useMemo } from 'react'
2
2
  import {
3
3
  FlatList,
4
4
  Modal,
5
5
  Pressable,
6
- Text,
7
- TextInput,
8
6
  View,
9
7
  type StyleProp,
10
8
  type ViewStyle
11
9
  } from 'react-native'
12
10
 
11
+ import type { ColorPairToken } from '@helpwave/hightide-design/theme-tokens'
12
+ import { useTranslation } from '@helpwave/hightide-utils/context'
13
13
  import { useTheme } from '../../global-contexts/theme/ThemeContext'
14
+ import { ThemedText } from '../visualization-and-display/ThemedText'
15
+ import { ThemedIcon } from '../visualization-and-display/ThemedIcon'
16
+ import { ListActionItem } from '../list/ListActionItem'
17
+ import { HightideIconRegistry } from '../../icons/HightideIconRegistry'
14
18
  import {
15
19
  useSelect,
16
20
  type UseSelectOption
17
21
  } from '../../hooks/useSelect'
18
22
  import type { SelectState } from '../../theme/types/components/select'
23
+ import type { Color } from '../../theme/types/color'
19
24
  import type {
20
25
  FormFieldDataHandling,
21
26
  FormFieldInteractionStates
22
27
  } from '../../types/formField'
28
+ import { SearchBar } from './SearchBar'
23
29
 
24
30
  export type SelectOption<T extends string = string> = UseSelectOption & {
25
31
  value?: T,
@@ -33,6 +39,7 @@ export type SelectProps = Partial<FormFieldDataHandling<string>>
33
39
  initialValue?: string | null,
34
40
  placeholder?: string,
35
41
  showSearch?: boolean,
42
+ color?: ColorPairToken,
36
43
  style?: StyleProp<ViewStyle>,
37
44
  }
38
45
 
@@ -42,6 +49,7 @@ export const Select = ({
42
49
  initialValue = null,
43
50
  placeholder = 'Select…',
44
51
  showSearch = true,
52
+ color,
45
53
  disabled = false,
46
54
  readOnly = false,
47
55
  invalid = false,
@@ -50,6 +58,7 @@ export const Select = ({
50
58
  style,
51
59
  }: SelectProps) => {
52
60
  const { theme } = useTheme()
61
+ const translation = useTranslation()
53
62
  const interactive = !disabled && !readOnly
54
63
 
55
64
  const select = useSelect({
@@ -65,51 +74,93 @@ export const Select = ({
65
74
  return selected?.label ?? placeholder
66
75
  }, [options, placeholder, select.value])
67
76
 
77
+ const visibleOptions = useMemo(
78
+ () => options.filter((option) => select.visibleOptionIds.includes(option.id)),
79
+ [options, select.visibleOptionIds]
80
+ )
81
+
68
82
  const state = useMemo((): SelectState => ({
83
+ color,
69
84
  isDisabled: disabled,
70
- isReadOnly: readOnly,
85
+ isReadonly: readOnly,
71
86
  isInvalid: invalid,
72
87
  isOpen: select.isOpen,
73
88
  hasValue: !!select.value,
74
- }), [disabled, invalid, readOnly, select.isOpen, select.value])
89
+ }), [color, disabled, invalid, readOnly, select.isOpen, select.value])
75
90
 
76
91
  const selectTheme = theme.components.select
77
92
 
78
- const resolvedTriggerStyle = useMemo(
79
- () => selectTheme.trigger(state),
80
- [selectTheme, state]
81
- )
82
- const resolvedTriggerTextStyle = useMemo(
83
- () => selectTheme.triggerText(state),
84
- [selectTheme, state]
85
- )
86
93
  const resolvedOverlayStyle = useMemo(
87
- () => selectTheme.overlay(state),
88
- [selectTheme, state]
94
+ () => selectTheme.overlay({}),
95
+ [selectTheme]
89
96
  )
90
97
  const resolvedMenuStyle = useMemo(
91
- () => selectTheme.menu(state),
92
- [selectTheme, state]
98
+ () => selectTheme.menu({ hasSearch: showSearch }),
99
+ [selectTheme, showSearch]
93
100
  )
94
- const resolvedSearchStyle = useMemo(
95
- () => selectTheme.search(state),
96
- [selectTheme, state]
101
+ const resolvedHeaderStyle = useMemo(
102
+ () => selectTheme.header({}),
103
+ [selectTheme]
97
104
  )
98
- const searchPlaceholderColor = useMemo(
99
- () => selectTheme.searchPlaceholderColor(state),
100
- [selectTheme, state]
105
+ const resolvedEmptyTextStyle = useMemo(
106
+ () => selectTheme.emptyText({}),
107
+ [selectTheme]
101
108
  )
109
+ const showEmptySearchResults = showSearch
110
+ && select.searchQuery.trim().length > 0
111
+ && visibleOptions.length === 0
102
112
 
103
113
  return (
104
114
  <View style={style}>
105
115
  <Pressable
106
116
  disabled={!interactive}
107
117
  onPress={() => select.toggleOpen()}
108
- style={resolvedTriggerStyle}
118
+ style={(pressableState) => {
119
+ const interaction = pressableState as {
120
+ pressed: boolean,
121
+ hovered?: boolean,
122
+ focused?: boolean,
123
+ focusVisible?: boolean,
124
+ }
125
+ return selectTheme.trigger({
126
+ ...state,
127
+ isPressed: interaction.pressed,
128
+ isHovered: !!interaction.hovered,
129
+ isFocused: !!interaction.focused,
130
+ isFocusVisible: !!interaction.focusVisible,
131
+ })
132
+ }}
109
133
  >
110
- <Text style={resolvedTriggerTextStyle}>
111
- {selectedLabel}
112
- </Text>
134
+ {(pressableState) => {
135
+ const interaction = pressableState as {
136
+ pressed: boolean,
137
+ hovered?: boolean,
138
+ focused?: boolean,
139
+ focusVisible?: boolean,
140
+ }
141
+ const triggerState = {
142
+ ...state,
143
+ isPressed: interaction.pressed,
144
+ isHovered: !!interaction.hovered,
145
+ isFocused: !!interaction.focused,
146
+ isFocusVisible: !!interaction.focusVisible,
147
+ }
148
+ const icon = selectTheme.icon(triggerState)
149
+
150
+ return (
151
+ <Fragment>
152
+ <ThemedText style={[selectTheme.triggerText(triggerState), { flex: 1 }]}>
153
+ {selectedLabel}
154
+ </ThemedText>
155
+ <ThemedIcon
156
+ icon={HightideIconRegistry.ChevronDown}
157
+ size={icon.size}
158
+ strokeWidth={icon.strokeWidth}
159
+ color={icon.color}
160
+ />
161
+ </Fragment>
162
+ )
163
+ }}
113
164
  </Pressable>
114
165
 
115
166
  <Modal
@@ -127,39 +178,59 @@ export const Select = ({
127
178
  onPress={(event) => event.stopPropagation()}
128
179
  >
129
180
  {showSearch && (
130
- <TextInput
131
- value={select.searchQuery}
132
- onChangeText={select.setSearchQuery}
133
- placeholder="Search…"
134
- placeholderTextColor={searchPlaceholderColor}
135
- style={resolvedSearchStyle}
136
- />
181
+ <View style={resolvedHeaderStyle}>
182
+ <SearchBar
183
+ value={select.searchQuery}
184
+ onValueChange={select.setSearchQuery}
185
+ onSearch={select.setSearchQuery}
186
+ />
187
+ </View>
137
188
  )}
138
- <FlatList
139
- data={options.filter((option) => select.visibleOptionIds.includes(option.id))}
140
- keyExtractor={(option) => option.id}
141
- renderItem={({ item }) => {
142
- const isSelected = select.value === item.id
143
- const isHighlighted = select.highlightedValue === item.id
144
- const optionState = {
145
- isSelected,
146
- isHighlighted,
147
- isDisabled: item.disabled,
148
- }
189
+ {showEmptySearchResults ? (
190
+ <View
191
+ style={{
192
+ flex: 1,
193
+ justifyContent: 'center',
194
+ alignItems: 'center',
195
+ }}
196
+ >
197
+ <ThemedText style={resolvedEmptyTextStyle}>
198
+ {translation('nothingFound')}
199
+ </ThemedText>
200
+ </View>
201
+ ) : (
202
+ <FlatList
203
+ data={visibleOptions}
204
+ keyExtractor={(option) => option.id}
205
+ style={{ flex: 1 }}
206
+ renderItem={({ item }) => {
207
+ const isSelected = select.value === item.id
208
+ const optionColor = isSelected
209
+ ? (color ?? theme.colors.primary)
210
+ : undefined
211
+ const checkIcon = theme.components.listItem.action.icon({ color: optionColor })
149
212
 
150
- return (
151
- <Pressable
152
- disabled={item.disabled}
153
- onPress={() => select.selectValue(item.id)}
154
- style={selectTheme.option(optionState)}
155
- >
156
- <Text style={selectTheme.optionText(optionState)}>
157
- {item.label}
158
- </Text>
159
- </Pressable>
160
- )
161
- }}
162
- />
213
+ return (
214
+ <ListActionItem
215
+ label={item.label ?? item.id}
216
+ color={optionColor}
217
+ disabled={item.disabled}
218
+ onPress={() => select.selectValue(item.id)}
219
+ trailing={isSelected
220
+ ? (
221
+ <ThemedIcon
222
+ icon={HightideIconRegistry.Check}
223
+ size={checkIcon.size}
224
+ strokeWidth={checkIcon.strokeWidth}
225
+ color={checkIcon.color as Color | undefined}
226
+ />
227
+ )
228
+ : undefined}
229
+ />
230
+ )
231
+ }}
232
+ />
233
+ )}
163
234
  </Pressable>
164
235
  </Pressable>
165
236
  </Modal>