@startupjs-ui/auto-suggest 0.1.3
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.
- package/CHANGELOG.md +20 -0
- package/README.mdx +139 -0
- package/index.cssx.styl +30 -0
- package/index.d.ts +51 -0
- package/index.tsx +261 -0
- package/package.json +24 -0
- package/useKeyboard.ts +59 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
## [0.1.3](https://github.com/startupjs/startupjs-ui/compare/v0.1.2...v0.1.3) (2025-12-29)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @startupjs-ui/auto-suggest
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.1.2](https://github.com/startupjs/startupjs-ui/compare/v0.1.1...v0.1.2) (2025-12-29)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* add mdx and docs packages. Refactor docs to get rid of any @startupjs/ui usage and use startupjs-ui instead ([703c926](https://github.com/startupjs/startupjs-ui/commit/703c92636efb0421ffd11783f692fc892b74018f))
|
|
20
|
+
* **auto-suggest:** refactor AutoSuggest component ([4602b67](https://github.com/startupjs/startupjs-ui/commit/4602b677b27b11c0cb31185bcaaf3cc40d3b1a87))
|
package/README.mdx
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import AutoSuggest, { _PropsJsonSchema as AutoSuggestPropsJsonSchema } from './index'
|
|
3
|
+
import Avatar from '@startupjs-ui/avatar'
|
|
4
|
+
import Div from '@startupjs-ui/div'
|
|
5
|
+
import Span from '@startupjs-ui/span'
|
|
6
|
+
import { Sandbox } from '@startupjs-ui/docs'
|
|
7
|
+
|
|
8
|
+
# AutoSuggest
|
|
9
|
+
A text field with a pop-up list of options.
|
|
10
|
+
|
|
11
|
+
```jsx
|
|
12
|
+
import { AutoSuggest } from 'startupjs-ui'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Initialization
|
|
16
|
+
|
|
17
|
+
Before use you need to configure [Portal](/docs/components/Portal)
|
|
18
|
+
|
|
19
|
+
## Simple example
|
|
20
|
+
```jsx example
|
|
21
|
+
const [value, setValue] = useState()
|
|
22
|
+
|
|
23
|
+
const options = [
|
|
24
|
+
{ value: '1', label: 'Harry' },
|
|
25
|
+
{ value: '2', label: 'Alfie' },
|
|
26
|
+
{ value: '3', label: 'Jacob' },
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<AutoSuggest
|
|
31
|
+
options={options}
|
|
32
|
+
value={value}
|
|
33
|
+
onChange={v => setValue(v)}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Props
|
|
39
|
+
- options: array of objects for options, each option has a value and a label
|
|
40
|
+
- value: active value
|
|
41
|
+
- maxHeight: maximum height of the popup list
|
|
42
|
+
- placeholder: the title of the field
|
|
43
|
+
- onChange: callback function, called after selecting a value, takes the selected value as the first parameter
|
|
44
|
+
|
|
45
|
+
```jsx example
|
|
46
|
+
const [value, setValue] = useState()
|
|
47
|
+
|
|
48
|
+
const options = [
|
|
49
|
+
{ value: '1', label: 'Harry' },
|
|
50
|
+
{ value: '2', label: 'Alfie' },
|
|
51
|
+
{ value: '3', label: 'Jacob' },
|
|
52
|
+
{ value: '4', label: 'Oscar' },
|
|
53
|
+
{ value: '5', label: 'Charlie' },
|
|
54
|
+
{ value: '6', label: 'James' },
|
|
55
|
+
{ value: '7', label: 'William' }
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<AutoSuggest
|
|
60
|
+
options={options}
|
|
61
|
+
value={value}
|
|
62
|
+
style={{ maxHeight: 160 }}
|
|
63
|
+
placeholder="Select value"
|
|
64
|
+
onChange={v=> setValue(v)}
|
|
65
|
+
/>
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Customization
|
|
70
|
+
- renderItem: a function that is called when rendering each element of the array. Gets 3 arguments - the current element (item), its index, and the selected index.
|
|
71
|
+
|
|
72
|
+
```jsx example
|
|
73
|
+
const [value, setValue] = useState()
|
|
74
|
+
|
|
75
|
+
const options = [
|
|
76
|
+
{ value: '1', label: 'Harry' },
|
|
77
|
+
{ value: '2', label: 'Alfie' },
|
|
78
|
+
{ value: '3', label: 'Jacob' },
|
|
79
|
+
{ value: '4', label: 'Oscar' },
|
|
80
|
+
{ value: '5', label: 'Charlie' },
|
|
81
|
+
{ value: '6', label: 'James' },
|
|
82
|
+
{ value: '7', label: 'William' }
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
const renderItem = (item, index, selectIndexValue)=> {
|
|
86
|
+
const style = { padding: 8 }
|
|
87
|
+
|
|
88
|
+
if (selectIndexValue === index) {
|
|
89
|
+
style.backgroundColor = "#eee"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Div
|
|
94
|
+
key={index}
|
|
95
|
+
vAlign='center'
|
|
96
|
+
style={style}
|
|
97
|
+
row
|
|
98
|
+
>
|
|
99
|
+
<Avatar size='s'>{item.label}</Avatar>
|
|
100
|
+
<Span style={{ marginLeft: 8 }}>{item.label}</Span>
|
|
101
|
+
</Div>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<AutoSuggest
|
|
107
|
+
options={options}
|
|
108
|
+
value={value}
|
|
109
|
+
style={{ maxHeight: 160 }}
|
|
110
|
+
placeholder="Select value"
|
|
111
|
+
renderItem={renderItem}
|
|
112
|
+
onChange={v=> setValue(v)}
|
|
113
|
+
/>
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Also
|
|
118
|
+
- style: responsible for styled hidden content
|
|
119
|
+
- captionStyle: responsible for heading styles (input)
|
|
120
|
+
- onDismiss: callback, called when the list is closed
|
|
121
|
+
- onChangeText: callback, called when entering text, accepts 1 argument text
|
|
122
|
+
- onScrollEnd: callback, called at the end of the scroll
|
|
123
|
+
|
|
124
|
+
## Sandbox
|
|
125
|
+
|
|
126
|
+
<Sandbox
|
|
127
|
+
Component={AutoSuggest}
|
|
128
|
+
propsJsonSchema={AutoSuggestPropsJsonSchema}
|
|
129
|
+
props={{
|
|
130
|
+
options: [
|
|
131
|
+
{ value: '1', label: 'Harry' },
|
|
132
|
+
{ value: '2', label: 'Alfie' },
|
|
133
|
+
{ value: '3', label: 'Jacob' }
|
|
134
|
+
],
|
|
135
|
+
value: '1',
|
|
136
|
+
placeholder: 'Select value',
|
|
137
|
+
onChange: value => alert(`Selected: ${value}`)
|
|
138
|
+
}}
|
|
139
|
+
/>
|
package/index.cssx.styl
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.root
|
|
2
|
+
.overlay
|
|
3
|
+
position absolute
|
|
4
|
+
top 0
|
|
5
|
+
left 0
|
|
6
|
+
right 0
|
|
7
|
+
bottom 0
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
.loaderCase
|
|
11
|
+
justify-content center
|
|
12
|
+
align-items center
|
|
13
|
+
min-height 8u
|
|
14
|
+
|
|
15
|
+
.item
|
|
16
|
+
width 100%
|
|
17
|
+
|
|
18
|
+
.content
|
|
19
|
+
width 100%
|
|
20
|
+
height 100%
|
|
21
|
+
background-color var(--color-bg-main)
|
|
22
|
+
|
|
23
|
+
.contentCase
|
|
24
|
+
border-radius 1u
|
|
25
|
+
width 100%
|
|
26
|
+
height 100%
|
|
27
|
+
overflow hidden
|
|
28
|
+
|
|
29
|
+
.selectMenu
|
|
30
|
+
background-color var(--AutoSuggest-itemBg)
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// DO NOT MODIFY THIS FILE - IT IS AUTOMATICALLY GENERATED ON COMMITS.
|
|
3
|
+
|
|
4
|
+
import { type ReactNode } from 'react';
|
|
5
|
+
import { type StyleProp, type TextStyle, type ViewStyle } from 'react-native';
|
|
6
|
+
import './index.cssx.styl';
|
|
7
|
+
interface AutoSuggestOptionObject {
|
|
8
|
+
value?: any;
|
|
9
|
+
label?: string | number;
|
|
10
|
+
}
|
|
11
|
+
type AutoSuggestOption = string | number | AutoSuggestOptionObject;
|
|
12
|
+
type AutoSuggestValue = AutoSuggestOption | null | undefined;
|
|
13
|
+
export declare const _PropsJsonSchema: {};
|
|
14
|
+
export interface AutoSuggestProps {
|
|
15
|
+
/** Custom styles for the suggestion list container */
|
|
16
|
+
style?: StyleProp<ViewStyle>;
|
|
17
|
+
/** Custom styles for the TextInput wrapper */
|
|
18
|
+
captionStyle?: StyleProp<ViewStyle>;
|
|
19
|
+
/** Custom styles for the TextInput input field */
|
|
20
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
21
|
+
/** Custom styles for the clear icon */
|
|
22
|
+
iconStyle?: StyleProp<TextStyle>;
|
|
23
|
+
/** Custom icon for the input */
|
|
24
|
+
inputIcon?: any;
|
|
25
|
+
/** Options list (strings, numbers, or objects with value/label) @default [] */
|
|
26
|
+
options?: AutoSuggestOption[];
|
|
27
|
+
/** Current selected value */
|
|
28
|
+
value?: AutoSuggestValue;
|
|
29
|
+
/** Placeholder text @default 'Select value' */
|
|
30
|
+
placeholder?: string | number;
|
|
31
|
+
/** Custom item renderer (item, index, highlightedIndex) */
|
|
32
|
+
renderItem?: (item: AutoSuggestOption, index: number, selectIndexValue: number) => ReactNode;
|
|
33
|
+
/** Show loader in the list @default false */
|
|
34
|
+
isLoading?: boolean;
|
|
35
|
+
/** Disable input interactions */
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
/** Render as non-interactive */
|
|
38
|
+
readonly?: boolean;
|
|
39
|
+
/** Change handler for selected value */
|
|
40
|
+
onChange?: (value?: any) => void | Promise<void>;
|
|
41
|
+
/** Called after the list is closed */
|
|
42
|
+
onDismiss?: () => void;
|
|
43
|
+
/** Change handler for input text */
|
|
44
|
+
onChangeText?: (text: string) => void;
|
|
45
|
+
/** Called when list scroll reaches the end */
|
|
46
|
+
onScrollEnd?: () => void;
|
|
47
|
+
/** Test identifier */
|
|
48
|
+
testID?: string;
|
|
49
|
+
}
|
|
50
|
+
declare const _default: import("react").ComponentType<AutoSuggestProps>;
|
|
51
|
+
export default _default;
|
package/index.tsx
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { useState, useRef, useEffect, useMemo, type ReactNode } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
TouchableOpacity,
|
|
4
|
+
TouchableWithoutFeedback,
|
|
5
|
+
View,
|
|
6
|
+
type StyleProp,
|
|
7
|
+
type TextStyle,
|
|
8
|
+
type ViewStyle
|
|
9
|
+
} from 'react-native'
|
|
10
|
+
import { pug, observer } from 'startupjs'
|
|
11
|
+
import { themed } from '@startupjs-ui/core'
|
|
12
|
+
import AbstractPopover from '@startupjs-ui/abstract-popover'
|
|
13
|
+
import FlatList from '@startupjs-ui/flat-list'
|
|
14
|
+
import Loader from '@startupjs-ui/loader'
|
|
15
|
+
import Menu from '@startupjs-ui/menu'
|
|
16
|
+
import TextInput from '@startupjs-ui/text-input'
|
|
17
|
+
import { faTimes } from '@fortawesome/free-solid-svg-icons/faTimes'
|
|
18
|
+
import escapeRegExp from 'lodash/escapeRegExp'
|
|
19
|
+
import useKeyboard from './useKeyboard'
|
|
20
|
+
import './index.cssx.styl'
|
|
21
|
+
|
|
22
|
+
const SUPPORT_PLACEMENTS = [
|
|
23
|
+
'bottom-start',
|
|
24
|
+
'bottom-center',
|
|
25
|
+
'bottom-end',
|
|
26
|
+
'top-start',
|
|
27
|
+
'top-center',
|
|
28
|
+
'top-end'
|
|
29
|
+
] as const
|
|
30
|
+
|
|
31
|
+
interface AutoSuggestOptionObject {
|
|
32
|
+
value?: any
|
|
33
|
+
label?: string | number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type AutoSuggestOption = string | number | AutoSuggestOptionObject
|
|
37
|
+
|
|
38
|
+
type AutoSuggestValue = AutoSuggestOption | null | undefined
|
|
39
|
+
|
|
40
|
+
export const _PropsJsonSchema = {/* AutoSuggestProps */}
|
|
41
|
+
|
|
42
|
+
export interface AutoSuggestProps {
|
|
43
|
+
/** Custom styles for the suggestion list container */
|
|
44
|
+
style?: StyleProp<ViewStyle>
|
|
45
|
+
/** Custom styles for the TextInput wrapper */
|
|
46
|
+
captionStyle?: StyleProp<ViewStyle>
|
|
47
|
+
/** Custom styles for the TextInput input field */
|
|
48
|
+
inputStyle?: StyleProp<TextStyle>
|
|
49
|
+
/** Custom styles for the clear icon */
|
|
50
|
+
iconStyle?: StyleProp<TextStyle>
|
|
51
|
+
/** Custom icon for the input */
|
|
52
|
+
inputIcon?: any
|
|
53
|
+
/** Options list (strings, numbers, or objects with value/label) @default [] */
|
|
54
|
+
options?: AutoSuggestOption[]
|
|
55
|
+
/** Current selected value */
|
|
56
|
+
value?: AutoSuggestValue
|
|
57
|
+
/** Placeholder text @default 'Select value' */
|
|
58
|
+
placeholder?: string | number
|
|
59
|
+
/** Custom item renderer (item, index, highlightedIndex) */
|
|
60
|
+
renderItem?: (item: AutoSuggestOption, index: number, selectIndexValue: number) => ReactNode
|
|
61
|
+
/** Show loader in the list @default false */
|
|
62
|
+
isLoading?: boolean
|
|
63
|
+
/** Disable input interactions */
|
|
64
|
+
disabled?: boolean
|
|
65
|
+
/** Render as non-interactive */
|
|
66
|
+
readonly?: boolean
|
|
67
|
+
/** Change handler for selected value */
|
|
68
|
+
onChange?: (value?: any) => void | Promise<void>
|
|
69
|
+
/** Called after the list is closed */
|
|
70
|
+
onDismiss?: () => void
|
|
71
|
+
/** Change handler for input text */
|
|
72
|
+
onChangeText?: (text: string) => void
|
|
73
|
+
/** Called when list scroll reaches the end */
|
|
74
|
+
onScrollEnd?: () => void
|
|
75
|
+
/** Test identifier */
|
|
76
|
+
testID?: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getOptionLabel (option: AutoSuggestOption): any {
|
|
80
|
+
return (option as AutoSuggestOptionObject)?.label ?? option
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function stringifyValue (option: AutoSuggestValue): string {
|
|
84
|
+
return JSON.stringify((option as AutoSuggestOptionObject)?.value ?? option)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseValue (value: string): any {
|
|
88
|
+
return JSON.parse(value)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getLabelFromValue (value: AutoSuggestValue, options: AutoSuggestOption[]): any {
|
|
92
|
+
for (const option of options) {
|
|
93
|
+
if (stringifyValue(value) === stringifyValue(option)) {
|
|
94
|
+
return getOptionLabel(option)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function AutoSuggest ({
|
|
100
|
+
style = {},
|
|
101
|
+
captionStyle,
|
|
102
|
+
inputStyle,
|
|
103
|
+
iconStyle,
|
|
104
|
+
options = [],
|
|
105
|
+
value,
|
|
106
|
+
placeholder = 'Select value',
|
|
107
|
+
renderItem,
|
|
108
|
+
isLoading = false,
|
|
109
|
+
disabled,
|
|
110
|
+
readonly,
|
|
111
|
+
onChange,
|
|
112
|
+
onDismiss,
|
|
113
|
+
onChangeText,
|
|
114
|
+
onScrollEnd,
|
|
115
|
+
testID
|
|
116
|
+
}: AutoSuggestProps): ReactNode {
|
|
117
|
+
const inputRef = useRef<any>(null)
|
|
118
|
+
const [isShow, setIsShow] = useState(false)
|
|
119
|
+
const [inputValue, setInputValue] = useState('')
|
|
120
|
+
const [wrapperHeight, setWrapperHeight] = useState<number | null>(null)
|
|
121
|
+
const [scrollHeightContent, setScrollHeightContent] = useState<number | null>(null)
|
|
122
|
+
const [textToFilter, setTextToFilter] = useState<string | undefined>()
|
|
123
|
+
const _options = useMemo(() => {
|
|
124
|
+
const escapedText = escapeRegExp(textToFilter ?? '')
|
|
125
|
+
return options.filter(option => {
|
|
126
|
+
return new RegExp(escapedText, 'gi')
|
|
127
|
+
.test(getLabelFromValue(option, options))
|
|
128
|
+
})
|
|
129
|
+
}, [options, textToFilter])
|
|
130
|
+
|
|
131
|
+
const [selectIndexValue, setSelectIndexValue, onKeyPress] = useKeyboard({
|
|
132
|
+
options: _options,
|
|
133
|
+
onChange,
|
|
134
|
+
onChangeShow: v => { setIsShow(v) }
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
const selectedLabel = useMemo(() => {
|
|
138
|
+
return getLabelFromValue(value, options)
|
|
139
|
+
}, [options, value])
|
|
140
|
+
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
setInputValue(selectedLabel)
|
|
143
|
+
}, [selectedLabel])
|
|
144
|
+
|
|
145
|
+
function onClose () {
|
|
146
|
+
setIsShow(false)
|
|
147
|
+
setSelectIndexValue(-1)
|
|
148
|
+
inputRef.current.blur()
|
|
149
|
+
onDismiss?.()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function _onChangeText (text: string) {
|
|
153
|
+
setInputValue(text)
|
|
154
|
+
setTextToFilter(text)
|
|
155
|
+
if (!text) onChange?.()
|
|
156
|
+
setSelectIndexValue(-1)
|
|
157
|
+
onChangeText?.(text)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function _onPress (item: AutoSuggestOption) {
|
|
161
|
+
onChange && await onChange(parseValue(stringifyValue(item)))
|
|
162
|
+
onClose()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function _renderItem ({ item, index }: { item: AutoSuggestOption, index: number }): ReactNode {
|
|
166
|
+
if (renderItem) {
|
|
167
|
+
return pug`
|
|
168
|
+
TouchableOpacity(
|
|
169
|
+
key=index
|
|
170
|
+
onPress=() => { void _onPress(item) }
|
|
171
|
+
)= renderItem(item, index, selectIndexValue)
|
|
172
|
+
`
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return pug`
|
|
176
|
+
Menu.Item.item(
|
|
177
|
+
key=index
|
|
178
|
+
styleName={ selectMenu: selectIndexValue === index }
|
|
179
|
+
onPress=() => { void _onPress(item) }
|
|
180
|
+
active=stringifyValue(item) === stringifyValue(value)
|
|
181
|
+
)= getLabelFromValue(item, options)
|
|
182
|
+
`
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function onScroll ({ nativeEvent }: any) {
|
|
186
|
+
if (nativeEvent.contentOffset.y + wrapperHeight === scrollHeightContent) {
|
|
187
|
+
onScrollEnd?.()
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function onLayoutWrapper ({ nativeEvent }: any) {
|
|
192
|
+
setWrapperHeight(nativeEvent.layout.height)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function onChangeSizeScroll (width: number, height: number) {
|
|
196
|
+
setScrollHeightContent(height)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function renderWrapper (children: ReactNode): ReactNode {
|
|
200
|
+
return pug`
|
|
201
|
+
View.root
|
|
202
|
+
TouchableWithoutFeedback(onPress=() => {
|
|
203
|
+
setInputValue(selectedLabel)
|
|
204
|
+
onClose()
|
|
205
|
+
})
|
|
206
|
+
View.overlay
|
|
207
|
+
= children
|
|
208
|
+
`
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const matchAnchorWidth = !(style as ViewStyle)?.width && !(style as ViewStyle)?.maxWidth
|
|
212
|
+
|
|
213
|
+
return pug`
|
|
214
|
+
TextInput(
|
|
215
|
+
ref=inputRef
|
|
216
|
+
style=captionStyle
|
|
217
|
+
inputStyle=inputStyle
|
|
218
|
+
icon=value && !disabled ? faTimes : undefined
|
|
219
|
+
iconPosition='right'
|
|
220
|
+
iconStyle=iconStyle
|
|
221
|
+
value=inputValue
|
|
222
|
+
placeholder=placeholder
|
|
223
|
+
disabled=disabled
|
|
224
|
+
readonly=readonly
|
|
225
|
+
onChangeText=_onChangeText
|
|
226
|
+
onFocus=() => setIsShow(true)
|
|
227
|
+
onKeyPress=onKeyPress
|
|
228
|
+
onIconPress=() => { void onChange?.() }
|
|
229
|
+
testID=testID
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
AbstractPopover(
|
|
233
|
+
visible=(isShow || isLoading)
|
|
234
|
+
anchorRef=inputRef
|
|
235
|
+
matchAnchorWidth=matchAnchorWidth
|
|
236
|
+
placements=SUPPORT_PLACEMENTS
|
|
237
|
+
durationOpen=200
|
|
238
|
+
durationClose=200
|
|
239
|
+
renderWrapper=renderWrapper
|
|
240
|
+
onCloseComplete=() => setTextToFilter()
|
|
241
|
+
)
|
|
242
|
+
if isLoading
|
|
243
|
+
View.loaderCase
|
|
244
|
+
Loader(size='s')
|
|
245
|
+
else
|
|
246
|
+
View.contentCase
|
|
247
|
+
FlatList.content(
|
|
248
|
+
style=style
|
|
249
|
+
data=_options
|
|
250
|
+
renderItem=_renderItem
|
|
251
|
+
keyExtractor=item => stringifyValue(item)
|
|
252
|
+
scrollEventThrottle=500
|
|
253
|
+
keyboardShouldPersistTaps='always'
|
|
254
|
+
onScroll=onScroll
|
|
255
|
+
onLayout=onLayoutWrapper
|
|
256
|
+
onContentSizeChange=onChangeSizeScroll
|
|
257
|
+
)
|
|
258
|
+
`
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export default observer(themed('AutoSuggest', AutoSuggest))
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@startupjs-ui/auto-suggest",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"main": "index.tsx",
|
|
8
|
+
"types": "index.d.ts",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@startupjs-ui/abstract-popover": "^0.1.3",
|
|
12
|
+
"@startupjs-ui/core": "^0.1.3",
|
|
13
|
+
"@startupjs-ui/flat-list": "^0.1.3",
|
|
14
|
+
"@startupjs-ui/loader": "^0.1.3",
|
|
15
|
+
"@startupjs-ui/menu": "^0.1.3",
|
|
16
|
+
"@startupjs-ui/text-input": "^0.1.3"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"react": "*",
|
|
20
|
+
"react-native": "*",
|
|
21
|
+
"startupjs": "*"
|
|
22
|
+
},
|
|
23
|
+
"gitHead": "fd964ebc3892d3dd0a6c85438c0af619cc50c3f0"
|
|
24
|
+
}
|
package/useKeyboard.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useState, type Dispatch, type SetStateAction } from 'react'
|
|
2
|
+
|
|
3
|
+
interface UseKeyboardOptions {
|
|
4
|
+
options: any[]
|
|
5
|
+
onChange?: (value: any) => void
|
|
6
|
+
onChangeShow: (visible: boolean) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function useKeyboard ({
|
|
10
|
+
options,
|
|
11
|
+
onChange,
|
|
12
|
+
onChangeShow
|
|
13
|
+
}: UseKeyboardOptions): [number, Dispatch<SetStateAction<number>>, (e: any) => void] {
|
|
14
|
+
const [selectIndexValue, setSelectIndexValue] = useState(-1)
|
|
15
|
+
|
|
16
|
+
function onKeyPress (e: any) {
|
|
17
|
+
const keyName = e.key
|
|
18
|
+
|
|
19
|
+
switch (keyName) {
|
|
20
|
+
case 'ArrowUp': {
|
|
21
|
+
e.preventDefault()
|
|
22
|
+
|
|
23
|
+
const nextIndex = selectIndexValue - 1
|
|
24
|
+
|
|
25
|
+
if (nextIndex < 0) {
|
|
26
|
+
setSelectIndexValue(options.length - 1)
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setSelectIndexValue(nextIndex)
|
|
31
|
+
break
|
|
32
|
+
}
|
|
33
|
+
case 'ArrowDown': {
|
|
34
|
+
e.preventDefault()
|
|
35
|
+
|
|
36
|
+
const nextIndex = selectIndexValue + 1
|
|
37
|
+
|
|
38
|
+
if (nextIndex === options.length) {
|
|
39
|
+
setSelectIndexValue(0)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setSelectIndexValue(nextIndex)
|
|
44
|
+
break
|
|
45
|
+
}
|
|
46
|
+
case 'Enter': {
|
|
47
|
+
e.preventDefault()
|
|
48
|
+
if (selectIndexValue === -1) return
|
|
49
|
+
const item = options.find((_: any, i: number) => i === selectIndexValue)
|
|
50
|
+
onChangeShow(false)
|
|
51
|
+
onChange && onChange(item)
|
|
52
|
+
setSelectIndexValue(-1)
|
|
53
|
+
break
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return [selectIndexValue, setSelectIndexValue, onKeyPress]
|
|
59
|
+
}
|