@momo-kits/auto-complete 0.0.48-rc.2 → 0.0.48

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/index.tsx ADDED
@@ -0,0 +1,87 @@
1
+ import React, {FC, useEffect, useState} from 'react';
2
+ import {FlatList, TouchableOpacity} from 'react-native';
3
+ import {Divider, Shadow, Text} from '@momo-kits/foundation';
4
+ import {AutoCompleteProps, SuggestItem} from './types';
5
+ import styles from './styles';
6
+
7
+ const AutoComplete: FC<AutoCompleteProps> = ({
8
+ data = [],
9
+ query = '',
10
+ onPressItem,
11
+ maxItemShow = 5,
12
+ searchKey = 'title',
13
+ }) => {
14
+ const [haveSelected, setHaveSelected] = useState(false);
15
+
16
+ useEffect(() => {
17
+ setHaveSelected(false);
18
+ onSearch(query);
19
+ return () => {};
20
+ }, [query]);
21
+
22
+ const removeDiacritics = (str: string) => {
23
+ return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
24
+ };
25
+
26
+ const sanitizeInput = (str: string) => {
27
+ return str.replace(/[\\/*+?().{}[\]^$|]/g, '');
28
+ };
29
+
30
+ const onSearch = (query: string) => {
31
+ const sanitizedText = sanitizeInput(query);
32
+ const normalizedText = removeDiacritics(sanitizedText.trim().toLowerCase());
33
+ const regex = new RegExp(normalizedText, 'i');
34
+
35
+ const filtered: (string | SuggestItem)[] = data
36
+ .filter((item: string | SuggestItem) => {
37
+ if (typeof item === 'string') {
38
+ return regex.test(removeDiacritics(item.toLowerCase()));
39
+ } else {
40
+ return regex.test(removeDiacritics(item?.[searchKey].toLowerCase()));
41
+ }
42
+ })
43
+ .slice(0, maxItemShow);
44
+ return filtered;
45
+ };
46
+
47
+ const onSelected = (title: string, value: null | string | undefined) => {
48
+ setHaveSelected(true);
49
+ onPressItem?.(title, value);
50
+ };
51
+
52
+ const _renderItem = ({item}: {item: string | SuggestItem}) => {
53
+ const title = typeof item === 'string' ? item : item.title;
54
+ const value = typeof item === 'string' ? null : item.value;
55
+
56
+ return (
57
+ <TouchableOpacity
58
+ onPress={() => onSelected(title, value)}
59
+ style={styles.item}>
60
+ <Text numberOfLines={1} typography={'body_default_regular'}>
61
+ {title}
62
+ </Text>
63
+ {!!value && (
64
+ <Text numberOfLines={1} typography={'body_default_regular'}>
65
+ {value}
66
+ </Text>
67
+ )}
68
+ </TouchableOpacity>
69
+ );
70
+ };
71
+
72
+ const filteredData = onSearch(query);
73
+
74
+ if (filteredData.length === 0 || query.length === 0 || haveSelected)
75
+ return null;
76
+ return (
77
+ <FlatList
78
+ keyExtractor={(item, index) => index.toString()}
79
+ ItemSeparatorComponent={() => <Divider />}
80
+ style={[styles.flatList, Shadow.Light]}
81
+ data={filteredData}
82
+ renderItem={_renderItem}
83
+ />
84
+ );
85
+ };
86
+
87
+ export {AutoComplete};
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
- "name": "@momo-kits/auto-complete",
3
- "version": "0.0.48-rc.2",
4
- "private": false,
5
- "main": "index.js",
6
- "dependencies": {},
7
- "peerDependencies": {
8
- "react": "16.9.0",
9
- "react-native": ">=0.55",
10
- "@momo-kits/core": ">=0.0.4-beta",
11
- "lodash": "^4.17.15",
12
- "prop-types": "^15.7.2"
13
- },
14
- "devDependencies": {},
15
- "license": "MoMo"
16
- }
2
+ "name": "@momo-kits/auto-complete",
3
+ "version": "0.0.48",
4
+ "private": false,
5
+ "main": "index.tsx",
6
+ "peerDependencies": {
7
+ "@momo-kits/foundation": "latest",
8
+ "react": "16.9.0",
9
+ "react-native": ">=0.55",
10
+ "prop-types": "^15.7.2"
11
+ },
12
+ "devDependencies": {
13
+ "@momo-platform/versions": "4.1.11"
14
+ },
15
+ "license": "MoMo",
16
+ "dependencies": {}
17
+ }
package/publish.sh CHANGED
@@ -1,29 +1,23 @@
1
1
  #!/bin/bash
2
+
3
+ # Prepare dist files
2
4
  rm -rf dist
3
5
  mkdir dist
4
-
5
- cp . ./dist
6
-
7
- # GET VERSION from mck_package.json
8
- VERSIONSTRING=( v$(jq .version package.json) )
9
- VERSION=(${VERSIONSTRING//[\"]/})
10
- echo VERSION: $VERSION
11
-
12
- rsync -r --verbose --exclude '*.mdx' --exclude '*Demo.js' --exclude 'props-type.js' --exclude 'prop-types.js' ./* dist
13
-
14
- # #babel component to dist
15
- #babel ./dist -d dist --copy-files
16
-
17
- #copy option
18
- #cp -r ./src/ dist
19
-
20
-
21
- #npm login
22
- #publish dist to npm
6
+ rsync -r --exclude=/dist ./* dist
23
7
  cd dist
24
- npm publish --tag beta --access=public
8
+
9
+ if [ "$1" == "beta" ]; then # Publish beta
10
+ npm version $(npm view @momo-kits/auto-complete@beta version)
11
+ npm version prepatch --preid=beta
12
+ npm publish --tag beta --access=public
13
+ else # Publish latest
14
+ npm version $(npm view @momo-kits/auto-complete version)
15
+ npm version patch
16
+ npm publish --tag latest --access=public
17
+ fi
18
+ PACKAGE_NAME=$(npm pkg get name)
19
+ NEW_PACKAGE_VERSION=$(npm pkg get version)
20
+
21
+ # Clean up
25
22
  cd ..
26
23
  rm -rf dist
27
-
28
-
29
- #curl -X POST -H 'Content-Type: application/json' 'https://chat.googleapis.com/v1/spaces/AAAAbP8987c/messages?key=AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI&token=UGSFRvk_oYb9uGsAgs31bVvMm6jDkmD8zihGm3eyaQA%3D&threadKey=JoaXTEYaNNkl' -d '{"text": "@momo-kits/auto-complete new version release: '*"$VERSION"*' https://www.npmjs.com/package/@momo-kits/auto-complete"}'
package/styles.ts ADDED
@@ -0,0 +1,15 @@
1
+ import {StyleSheet} from 'react-native';
2
+ import {Radius, Spacing} from '@momo-kits/foundation';
3
+
4
+ export default StyleSheet.create({
5
+ flatList: {
6
+ backgroundColor: 'white',
7
+ borderRadius: Radius.S,
8
+ paddingHorizontal: Spacing.M,
9
+ },
10
+ item: {
11
+ paddingVertical: Spacing.M,
12
+ flexDirection: 'row',
13
+ justifyContent: 'space-between',
14
+ },
15
+ });
package/types.ts ADDED
@@ -0,0 +1,60 @@
1
+ import {ReactElement} from 'react';
2
+
3
+ /**
4
+ * Represents a suggested item in the autocomplete list. Each item has a title for display
5
+ * and a value that represents the underlying data.
6
+ */
7
+ export type SuggestItem = {
8
+ title: string; // The display text for a suggestion item.
9
+ value: string; // The underlying value or identifier for this item.
10
+ };
11
+
12
+ /**
13
+ * Props for configuring the AutoComplete component.
14
+ */
15
+ export type AutoCompleteProps = {
16
+ /**
17
+ * An array of strings or `SuggestItem` objects that will be used to create the
18
+ * autocomplete dropdown list.
19
+ */
20
+ data: Array<string | SuggestItem>;
21
+
22
+ /**
23
+ * The current text in the autocomplete input field. This is used to determine
24
+ * which suggestions to display.
25
+ */
26
+ query: string;
27
+
28
+ /**
29
+ * Optional. The maximum number of items to display in the dropdown list. If not
30
+ * provided, all matching items will be shown.
31
+ */
32
+ maxItemShow?: number;
33
+
34
+ /**
35
+ * Optional. A function to render the UI of each item in the dropdown list.
36
+ * This is useful for adding custom styling or formatting to the displayed items.
37
+ */
38
+ renderItem?: (data: string) => ReactElement;
39
+
40
+ /**
41
+ * A function that is called when a user selects an item from the autocomplete list.
42
+ * It receives two arguments: the display text of the selected item and its corresponding value.
43
+ */
44
+ onPressItem?: (item: string, value: null | string | undefined) => void;
45
+
46
+ /**
47
+ * Indicates whether the 'title' or 'value' of a `SuggestItem` should be used for
48
+ * searching. This affects which items are filtered into the dropdown list as the
49
+ * user types in the input field.
50
+ */
51
+ searchKey: 'title' | 'value';
52
+ };
53
+
54
+ /**
55
+ * Props for representing individual items in the autocomplete suggestions list.
56
+ */
57
+ export type ListItemProps = {
58
+ item: string; // The content or data of the list item, to be rendered in the list.
59
+ index: number; // Represents the position of this item within the list.
60
+ };
package/AutoComplete.js DELETED
@@ -1,342 +0,0 @@
1
- /* eslint-disable no-extra-boolean-cast */
2
- /* eslint-disable no-param-reassign */
3
- import React, { Component } from 'react';
4
- import {
5
- findNodeHandle,
6
- StyleSheet,
7
- UIManager,
8
- View,
9
- Platform
10
- } from 'react-native';
11
- import { get } from 'lodash';
12
- import PropTypes from 'prop-types';
13
- import {
14
- ValueUtil, NumberUtils, Colors, Text, RNGestureHandler
15
- } from '@momo-kits/core';
16
-
17
- const { TouchableOpacity } = RNGestureHandler;
18
- export default class AutoComplete extends Component {
19
- constructor(props) {
20
- super(props);
21
- this.hashmapRefs = {};
22
- this.hashmapPosition = {};
23
- this.hashmapInputValue = {};
24
- this.selectedItem = null;
25
- this.childrenWithProps = null;
26
- }
27
-
28
- /**
29
- * func measure all component have keyAutoComplete to get it's position
30
- */
31
- measure() {
32
- try {
33
- if (this.hashmapRefs) {
34
- Object.keys(this.hashmapRefs)
35
- .forEach((key) => {
36
- if (Platform.OS === 'android') {
37
- UIManager.measureLayoutRelativeToParent(findNodeHandle(this.hashmapRefs[key]), (e) => { console.error(e); }, (x, y, width, height) => {
38
- this.hashmapPosition[key] = {
39
- x,
40
- y: y + height,
41
- width
42
- };
43
- });
44
- } else {
45
- UIManager.measure(findNodeHandle(this.hashmapRefs[key]), (x, y, width, height) => {
46
- this.hashmapPosition[key] = {
47
- x,
48
- y: y + height,
49
- width
50
- };
51
- });
52
- }
53
- });
54
- }
55
- } catch (e) {
56
- console.log(`try catch :: ${e}`);
57
- }
58
- }
59
-
60
- componentDidMount() {
61
- // setTimeout to fix async
62
- setTimeout(() => {
63
- this.measure();
64
- }, 500);
65
- }
66
-
67
- getValueByKey = (key, value) => {
68
- const splitKey = key.split('-');
69
- return splitKey.length > 1 ? splitKey.reduce((result, item, index) => result = result + value[item] + (index === splitKey.length - 1 ? '' : !!value[item] ? ' ' : ''), '')
70
- : key === 'phone' ? NumberUtils.formatPhoneNumberVN(value[key]) : value[key];
71
- };
72
-
73
- /**
74
- * loop all child components
75
- * @param {"Children"} components ;
76
- */
77
- cloneChildren(components) {
78
- if (components) {
79
- return React.Children.map(components, (child) => {
80
- if (!child?.props) return child;
81
- if (child?.props?.children && React.Children.count(child?.props?.children) > 0 && child.type.name !== Text) {
82
- // component have children -> clone it and all it's children
83
- return React.cloneElement(child, {
84
- children: this.cloneChildren(child.props.children)
85
- });
86
- }
87
-
88
- const {
89
- onChangeText,
90
- keyAutoComplete,
91
- onFocus,
92
- onEndEditing
93
- } = child.props;
94
- if (keyAutoComplete) {
95
- // Update props when component have keyAutoComplete
96
- if (this.selectedItem) {
97
- this.hashmapInputValue[keyAutoComplete] = this.getValueByKey(keyAutoComplete, this.selectedItem);// this.selectedItem[keyAutoComplete];
98
- return React.cloneElement(child, {
99
- ref: (view) => this.hashmapRefs[keyAutoComplete] = view,
100
- onChangeText: (text) => this.changeTextHandle(child, text, onChangeText),
101
- onFocus: (e) => this.focusHandle(e, child, onFocus),
102
- // value: this.getValueByKey(keyAutoComplete, this.selectedItem),
103
- onEndEditing: (e) => this.endFocusHandle(e, onEndEditing)
104
- });
105
- }
106
- return React.cloneElement(child, {
107
- ref: (view) => this.hashmapRefs[keyAutoComplete] = view,
108
- onChangeText: (text) => this.changeTextHandle(child, text, onChangeText),
109
- onFocus: (e) => this.focusHandle(e, child, onFocus),
110
- onEndEditing: (e) => this.endFocusHandle(e, onEndEditing)
111
- });
112
- }
113
- return child;
114
- });
115
- }
116
-
117
- return components;
118
- }
119
-
120
- render() {
121
- const {
122
- style = {},
123
- } = this.props;
124
- const suggest = get(this.state, 'suggest', {});
125
- const childrenWithProps = this.cloneChildren(get(this.props, 'children', null));
126
- return (
127
- <View style={[{ zIndex: 1 }, style]}>
128
- {childrenWithProps}
129
- {this.renderSuggest(suggest)}
130
- </View>
131
- );
132
- }
133
-
134
- querySearch = (child, text) => {
135
- const keyAutoComplete = get(child.props, 'keyAutoComplete', '');
136
- const isShowAutoComplete = get(child.props, 'isShowAutoComplete', true);
137
- const { data } = this.props;
138
- const dataOutput = data && data.length > 0 && this.filter(data, keyAutoComplete, text);
139
- if (this.hashmapRefs[keyAutoComplete]) {
140
- this.setState({
141
- suggest: {
142
- data: dataOutput,
143
- position: this.hashmapPosition[keyAutoComplete],
144
- isShowAutoComplete
145
- }
146
- });
147
- }
148
- };
149
-
150
- changeTextHandle = (child, text, onChangeText) => {
151
- this.querySearch(child, text);
152
- if (onChangeText && typeof onChangeText === 'function') {
153
- onChangeText(text);
154
- }
155
- };
156
-
157
- focusHandle = (e, child, onFocus) => {
158
- let text = '';
159
- if (child && typeof child.getText === 'function') {
160
- text = child.getText();
161
- }
162
-
163
- this.querySearch(child, text);
164
-
165
- if (onFocus && typeof onFocus === 'function') {
166
- onFocus(e);
167
- }
168
- };
169
-
170
- endFocusHandle = (e, onEndEditing) => {
171
- if (this.isShowingSuggest()) {
172
- this.hideSuggest();
173
- }
174
- if (onEndEditing && typeof onEndEditing === 'function') {
175
- onEndEditing(e);
176
- }
177
- };
178
-
179
- filter = (data, key, query) => {
180
- if (!data || !key) {
181
- return null;
182
- }
183
- if (query === '') {
184
- return data;
185
- }
186
-
187
- return data.filter((item) => {
188
- const valueStr = item ? this.getValueByKey(key, item) : '';
189
- const valueStrFormated = ValueUtil.removeAlias(valueStr)
190
- .toLowerCase()
191
- .trim()
192
- .replace(/\s/g, '');
193
- const queryFormated = ValueUtil.removeAlias(query)
194
- .toLowerCase()
195
- .trim()
196
- .replace(/\s/g, '');
197
- return (
198
- valueStrFormated.indexOf(queryFormated) !== -1
199
- );
200
- });
201
- };
202
-
203
- renderSuggest(suggest) {
204
- const { numSuggest } = this.props;
205
- if (suggest && suggest.data && suggest.data.length > 0 && suggest.isShowAutoComplete) {
206
- const { x = 0, y = 0, width = 0 } = suggest.position || {};
207
- const sliceData = suggest.data.slice(0, numSuggest);
208
-
209
- return (
210
- <View style={
211
- [styles.containerSuggest, {
212
- left: x,
213
- top: y,
214
- width
215
- }]
216
- }
217
- >
218
- {
219
- sliceData.map((item, index) => (
220
- <View key={index.toString()}>
221
- {this.renderItem({ item, index })}
222
- {index !== sliceData.length - 1 && this.renderSeparator()}
223
- </View>
224
- ))
225
- }
226
- </View>
227
-
228
- );
229
- }
230
- return null;
231
- }
232
-
233
- renderItem = ({ item, index }) => {
234
- const {
235
- renderSuggestItem
236
- } = this.props;
237
- return (
238
- <TouchableOpacity onPress={() => this.onPressItemSuggest(item)}>
239
- {
240
- (renderSuggestItem && typeof renderSuggestItem === 'function')
241
- ? renderSuggestItem({ item, index })
242
- : this.renderSuggestItemDefault({ item, index })
243
- }
244
- </TouchableOpacity>
245
- );
246
- };
247
-
248
- renderSeparator = () => (
249
- <View
250
- style={styles.separator}
251
- />
252
- );
253
-
254
- renderSuggestItemDefault = ({ item }) => {
255
- const { title, value } = item;
256
- return (
257
- <View style={[styles.viewSuggest]}>
258
- <Text.Title>{title}</Text.Title>
259
- <Text.Title>{value}</Text.Title>
260
- </View>
261
- );
262
- };
263
-
264
- onPressItemSuggest = (item) => {
265
- this.selectedItem = item;
266
- // Loop hashRef to update value of child
267
- Object.keys(this.hashmapRefs).forEach((key) => {
268
- if (this.hashmapRefs[key].setText && typeof this.hashmapRefs[key].setText === 'function') this.hashmapRefs[key].setText(this.getValueByKey(key, this.selectedItem));
269
- if (this.hashmapRefs[key].setValue && typeof this.hashmapRefs[key].setValue === 'function') this.hashmapRefs[key].setValue(this.getValueByKey(key, this.selectedItem));
270
- });
271
-
272
- const {
273
- onSelected
274
- } = this.props;
275
- this.setState({
276
- suggest: {}
277
- }, () => {
278
- if (onSelected && typeof onSelected === 'function') {
279
- onSelected(item);
280
- }
281
- this.selectedItem = null;
282
- });
283
- };
284
-
285
- isShowingSuggest = () => {
286
- const { suggest } = this.state;
287
- return suggest && suggest.data && suggest.data.length > 0;
288
- };
289
-
290
- hideSuggest = () => {
291
- this.setState({
292
- suggest: {}
293
- }, () => {
294
- this.selectedItem = null;
295
- });
296
- };
297
- }
298
-
299
- const styles = StyleSheet.create({
300
- viewSuggest: {
301
- flexDirection: 'row',
302
- justifyContent: 'space-between',
303
- alignItems: 'center',
304
- paddingVertical: 5,
305
- },
306
- containerSuggest: {
307
- backgroundColor: 'white',
308
- paddingVertical: 10,
309
- paddingHorizontal: 12,
310
- position: 'absolute',
311
- maxHeight: 240,
312
- borderColor: '#DADADA',
313
- borderRadius: 4,
314
- borderWidth: 1,
315
- shadowColor: '#000000',
316
- shadowOffset: {
317
- width: 0,
318
- height: 1
319
- },
320
- shadowRadius: 1,
321
- elevation: 2,
322
- shadowOpacity: 0.5,
323
- },
324
- separator: {
325
- height: 1,
326
- width: '100%',
327
- backgroundColor: Colors.placeholder,
328
- marginVertical: 10
329
- }
330
- });
331
-
332
- AutoComplete.propTypes = {
333
- style: PropTypes.oneOfType([PropTypes.array, PropTypes.object, PropTypes.number]),
334
- data: PropTypes.arrayOf(PropTypes.object).isRequired,
335
- renderSuggestItem: PropTypes.func,
336
- onSelected: PropTypes.func.isRequired,
337
- numSuggest: PropTypes.number,
338
- };
339
-
340
- AutoComplete.defaultProps = {
341
- numSuggest: 2,
342
- };
package/index.js DELETED
@@ -1,5 +0,0 @@
1
- import AutoComplete from './AutoComplete';
2
-
3
- export {
4
- AutoComplete
5
- };