@hero-design/rn 7.16.2 → 7.17.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 (122) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/es/index.js +331 -259
  3. package/lib/index.js +330 -258
  4. package/package.json +2 -2
  5. package/src/components/Accordion/__tests__/__snapshots__/AccordionItem.spec.tsx.snap +12 -12
  6. package/src/components/Accordion/__tests__/__snapshots__/index.spec.tsx.snap +18 -18
  7. package/src/components/Alert/__tests__/__snapshots__/index.spec.tsx.snap +26 -26
  8. package/src/components/Avatar/__tests__/__snapshots__/StyledAvatar.spec.tsx.snap +3 -3
  9. package/src/components/Avatar/__tests__/__snapshots__/index.spec.tsx.snap +2 -2
  10. package/src/components/Badge/__tests__/__snapshots__/Badge.spec.tsx.snap +1 -1
  11. package/src/components/Badge/__tests__/__snapshots__/Status.spec.tsx.snap +2 -2
  12. package/src/components/BottomNavigation/__tests__/__snapshots__/index.spec.tsx.snap +9 -9
  13. package/src/components/BottomSheet/__tests__/__snapshots__/index.spec.tsx.snap +12 -12
  14. package/src/components/Button/LoadingIndicator/__tests__/__snapshots__/StyledLoadingIndicator.spec.tsx.snap +2 -2
  15. package/src/components/Button/LoadingIndicator/__tests__/__snapshots__/index.spec.tsx.snap +6 -6
  16. package/src/components/Button/UtilityButton/__tests__/__snapshots__/index.spec.tsx.snap +6 -6
  17. package/src/components/Button/__tests__/__snapshots__/IconButton.spec.tsx.snap +1 -1
  18. package/src/components/Button/__tests__/__snapshots__/StyledButton.spec.tsx.snap +67 -67
  19. package/src/components/Calendar/__tests__/__snapshots__/CalendarRowItem.spec.tsx.snap +12 -12
  20. package/src/components/Card/__tests__/__snapshots__/StyledCard.spec.tsx.snap +1 -1
  21. package/src/components/Checkbox/__tests__/__snapshots__/StyledCheckbox.spec.tsx.snap +1 -1
  22. package/src/components/Checkbox/__tests__/__snapshots__/index.spec.tsx.snap +2 -2
  23. package/src/components/ContentNavigator/__tests__/__snapshots__/StyledContentNavigator.spec.tsx.snap +1 -1
  24. package/src/components/ContentNavigator/__tests__/__snapshots__/index.spec.tsx.snap +6 -6
  25. package/src/components/DatePicker/__tests__/__snapshots__/DatePickerAndroid.spec.tsx.snap +5 -5
  26. package/src/components/DatePicker/__tests__/__snapshots__/DatePickerIOS.spec.tsx.snap +12 -12
  27. package/src/components/Divider/__tests__/__snapshots__/StyledDivider.spec.tsx.snap +12 -12
  28. package/src/components/Drawer/__tests__/__snapshots__/index.spec.tsx.snap +3 -3
  29. package/src/components/Empty/__tests__/__snapshots__/index.spec.tsx.snap +3 -3
  30. package/src/components/FAB/ActionGroup/__tests__/__snapshots__/index.spec.tsx.snap +28 -28
  31. package/src/components/FAB/__tests__/__snapshots__/AnimatedFABIcon.spec.tsx.snap +2 -2
  32. package/src/components/FAB/__tests__/__snapshots__/StyledFAB.spec.tsx.snap +3 -3
  33. package/src/components/FAB/__tests__/__snapshots__/index.spec.tsx.snap +9 -9
  34. package/src/components/Icon/__tests__/__snapshots__/index.spec.tsx.snap +3 -3
  35. package/src/components/List/BasicListItem.tsx +8 -4
  36. package/src/components/List/__tests__/__snapshots__/BasicListItem.spec.tsx.snap +3 -3
  37. package/src/components/List/__tests__/__snapshots__/ListItem.spec.tsx.snap +20 -20
  38. package/src/components/List/__tests__/__snapshots__/StyledBasicListItem.spec.tsx.snap +6 -6
  39. package/src/components/List/__tests__/__snapshots__/StyledListItem.spec.tsx.snap +12 -12
  40. package/src/components/PinInput/__tests__/__snapshots__/PinCell.spec.tsx.snap +4 -4
  41. package/src/components/PinInput/__tests__/__snapshots__/index.spec.tsx.snap +22 -22
  42. package/src/components/Progress/ProgressCircle.tsx +25 -22
  43. package/src/components/Progress/StyledProgressCircle.tsx +33 -28
  44. package/src/components/Progress/__tests__/__snapshots__/index.spec.js.snap +102 -92
  45. package/src/components/Radio/__tests__/__snapshots__/Radio.spec.tsx.snap +5 -5
  46. package/src/components/Radio/__tests__/__snapshots__/RadioGroup.spec.tsx.snap +6 -6
  47. package/src/components/Radio/__tests__/__snapshots__/StyledRadio.spec.tsx.snap +3 -3
  48. package/src/components/RichTextEditor/__tests__/__snapshots__/EditorToolbar.spec.tsx.snap +11 -11
  49. package/src/components/RichTextEditor/__tests__/__snapshots__/RichTextEditor.spec.tsx.snap +6 -6
  50. package/src/components/SectionHeading/__tests__/__snapshots__/StyledHeading.spec.tsx.snap +1 -1
  51. package/src/components/SectionHeading/__tests__/__snapshots__/index.spec.tsx.snap +9 -9
  52. package/src/components/Select/MultiSelect/Option.tsx +20 -11
  53. package/src/components/Select/MultiSelect/OptionList.tsx +47 -41
  54. package/src/components/Select/MultiSelect/__tests__/OptionList.spec.tsx +25 -14
  55. package/src/components/Select/MultiSelect/__tests__/__snapshots__/Option.spec.tsx.snap +6 -4
  56. package/src/components/Select/MultiSelect/__tests__/__snapshots__/OptionList.spec.tsx.snap +1638 -134
  57. package/src/components/Select/MultiSelect/__tests__/__snapshots__/index.spec.tsx.snap +5312 -366
  58. package/src/components/Select/MultiSelect/__tests__/index.spec.tsx +122 -1
  59. package/src/components/Select/MultiSelect/index.tsx +26 -36
  60. package/src/components/Select/SingleSelect/Option.tsx +19 -3
  61. package/src/components/Select/SingleSelect/OptionList.tsx +47 -39
  62. package/src/components/Select/SingleSelect/__tests__/OptionList.spec.tsx +23 -12
  63. package/src/components/Select/SingleSelect/__tests__/__snapshots__/Option.spec.tsx.snap +5 -3
  64. package/src/components/Select/SingleSelect/__tests__/__snapshots__/OptionList.spec.tsx.snap +1632 -128
  65. package/src/components/Select/SingleSelect/__tests__/__snapshots__/index.spec.tsx.snap +4932 -302
  66. package/src/components/Select/SingleSelect/__tests__/index.spec.tsx +117 -1
  67. package/src/components/Select/SingleSelect/index.tsx +26 -37
  68. package/src/components/Select/StyledOptionList.tsx +43 -44
  69. package/src/components/Select/StyledSelect.tsx +7 -3
  70. package/src/components/Select/__tests__/StyledSelect.spec.tsx +1 -9
  71. package/src/components/Select/__tests__/__snapshots__/StyledSelect.spec.tsx.snap +2 -15
  72. package/src/components/Select/__tests__/helpers.spec.tsx +74 -0
  73. package/src/components/Select/helpers.tsx +87 -4
  74. package/src/components/Select/types.ts +99 -0
  75. package/src/components/Spinner/__tests__/__snapshots__/AnimatedSpinner.spec.tsx.snap +4 -4
  76. package/src/components/Spinner/__tests__/__snapshots__/StyledSpinner.spec.tsx.snap +8 -8
  77. package/src/components/Spinner/__tests__/__snapshots__/index.spec.tsx.snap +4 -4
  78. package/src/components/Switch/__tests__/__snapshots__/StyledHeading.spec.tsx.snap +1 -1
  79. package/src/components/Switch/__tests__/__snapshots__/index.spec.tsx.snap +2 -2
  80. package/src/components/Tabs/__tests__/__snapshots__/ScrollableTabs.spec.tsx.snap +6 -6
  81. package/src/components/Tabs/__tests__/__snapshots__/index.spec.tsx.snap +8 -8
  82. package/src/components/Tag/__tests__/__snapshots__/Tag.spec.tsx.snap +8 -8
  83. package/src/components/TextInput/__tests__/__snapshots__/StyledTextInput.spec.tsx.snap +50 -50
  84. package/src/components/TextInput/__tests__/__snapshots__/index.spec.tsx.snap +241 -85
  85. package/src/components/TextInput/__tests__/index.spec.tsx +29 -8
  86. package/src/components/TextInput/index.tsx +18 -7
  87. package/src/components/TimePicker/__tests__/__snapshots__/TimePickerAndroid.spec.tsx.snap +5 -5
  88. package/src/components/TimePicker/__tests__/__snapshots__/TimePickerIOS.spec.tsx.snap +12 -12
  89. package/src/components/Toast/__tests__/__snapshots__/Toast.spec.tsx.snap +22 -22
  90. package/src/components/Toolbar/__tests__/__snapshots__/ToolbarGroup.spec.tsx.snap +12 -12
  91. package/src/components/Toolbar/__tests__/__snapshots__/ToolbarItem.spec.tsx.snap +8 -8
  92. package/src/components/Typography/Text/__tests__/__snapshots__/StyledText.spec.tsx.snap +13 -13
  93. package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +145 -146
  94. package/src/theme/components/alert.ts +3 -3
  95. package/src/theme/components/badge.ts +1 -1
  96. package/src/theme/components/card.ts +4 -4
  97. package/src/theme/components/list.ts +4 -4
  98. package/src/theme/components/pinInput.ts +2 -2
  99. package/src/theme/components/progress.ts +5 -5
  100. package/src/theme/components/select.ts +3 -3
  101. package/src/theme/components/toast.ts +3 -3
  102. package/src/theme/global/colors.ts +40 -39
  103. package/src/types.ts +7 -1
  104. package/types/components/List/BasicListItem.d.ts +1 -1
  105. package/types/components/Progress/StyledProgressCircle.d.ts +12 -6
  106. package/types/components/Select/MultiSelect/Option.d.ts +4 -2
  107. package/types/components/Select/MultiSelect/OptionList.d.ts +6 -7
  108. package/types/components/Select/MultiSelect/index.d.ts +5 -5
  109. package/types/components/Select/SingleSelect/Option.d.ts +4 -2
  110. package/types/components/Select/SingleSelect/OptionList.d.ts +6 -7
  111. package/types/components/Select/SingleSelect/index.d.ts +5 -5
  112. package/types/components/Select/StyledOptionList.d.ts +10 -16
  113. package/types/components/Select/StyledSelect.d.ts +8 -2
  114. package/types/components/Select/__tests__/helpers.spec.d.ts +1 -0
  115. package/types/components/Select/helpers.d.ts +14 -2
  116. package/types/components/Select/index.d.ts +1 -1
  117. package/types/components/Select/types.d.ts +32 -7
  118. package/types/components/TextInput/index.d.ts +4 -2
  119. package/types/theme/components/progress.d.ts +1 -2
  120. package/types/theme/components/select.d.ts +3 -3
  121. package/types/types.d.ts +2 -1
  122. package/src/components/Select/types.tsx +0 -52
@@ -2,6 +2,9 @@ import { fireEvent } from '@testing-library/react-native';
2
2
  import React from 'react';
3
3
  import renderWithTheme from '../../../../testHelpers/renderWithTheme';
4
4
  import SingleSelect from '..';
5
+ import Typography from '../../../Typography';
6
+ import List from '../../../List';
7
+ import { ListRenderOptionInfo } from '../../types';
5
8
 
6
9
  const options = [
7
10
  { text: 'Monday', value: 'mon' },
@@ -10,9 +13,64 @@ const options = [
10
13
  { text: 'Thursday', value: 'thu' },
11
14
  { text: 'Friday', value: 'fri' },
12
15
  { text: 'Saturday', value: 'sat' },
13
- { text: 'Sunday', value: 'sun' },
16
+ { text: 'Sunday', value: 'sun', disabled: true },
14
17
  ];
15
18
 
19
+ const sections = [
20
+ { category: 'A', data: [{ text: 'A1', value: 'a1' }] },
21
+ {
22
+ category: 'B',
23
+ data: [
24
+ { text: 'B1', value: 'b1' },
25
+ { text: 'B2', value: 'b2' },
26
+ ],
27
+ },
28
+ ];
29
+
30
+ type CustomOptionType = {
31
+ text: string;
32
+ value: string;
33
+ role: string;
34
+ };
35
+
36
+ const collaboratorSections = [
37
+ {
38
+ category: 'D',
39
+ data: [
40
+ { text: 'Daniel', value: 'daniel', role: 'Senior Developer' },
41
+ { text: 'Daemon', value: 'daemon', role: 'Manager' },
42
+ ],
43
+ },
44
+ {
45
+ category: 'J',
46
+ data: [
47
+ { text: 'Jennifer', value: 'jennifer', role: 'UX Designer' },
48
+ { text: 'Josh ', value: 'josh', role: 'Junior Developer' },
49
+ ],
50
+ },
51
+ ];
52
+
53
+ const renderSingleOption = ({
54
+ item,
55
+ selected,
56
+ onPress,
57
+ }: ListRenderOptionInfo<string, CustomOptionType>) => (
58
+ <List.BasicItem
59
+ selected={selected}
60
+ onPress={onPress}
61
+ title={
62
+ <>
63
+ <Typography.Text fontSize="large" fontWeight="semi-bold">
64
+ {item.text}
65
+ </Typography.Text>
66
+ <Typography.Text fontSize="large" intent="subdued">
67
+ {item.role}
68
+ </Typography.Text>
69
+ </>
70
+ }
71
+ />
72
+ );
73
+
16
74
  describe('rendering', () => {
17
75
  it('renders correctly when bottom sheet is NOT visible', () => {
18
76
  const { queryAllByText, toJSON, getByTestId } = renderWithTheme(
@@ -50,6 +108,64 @@ describe('rendering', () => {
50
108
  expect(getByText('Saturday')).toBeDefined();
51
109
  expect(getByText('Sunday')).toBeDefined();
52
110
  });
111
+
112
+ it('renders correctly when input is loading', () => {
113
+ const { toJSON, getByTestId } = renderWithTheme(
114
+ <SingleSelect
115
+ label="Allow notifications"
116
+ options={options}
117
+ value="mon"
118
+ inputProps={{ loading: true }}
119
+ onConfirm={jest.fn()}
120
+ />
121
+ );
122
+
123
+ expect(toJSON()).toMatchSnapshot();
124
+ expect(getByTestId('input-suffix')).toHaveProp('name', 'loading');
125
+ });
126
+
127
+ it('renders correctly when receives sections', () => {
128
+ const { queryAllByText, getByText, toJSON, getByTestId } = renderWithTheme(
129
+ <SingleSelect
130
+ label="Allow notifications"
131
+ options={sections}
132
+ value="a1"
133
+ onConfirm={jest.fn()}
134
+ />
135
+ );
136
+ fireEvent.press(getByTestId('text-input'));
137
+
138
+ expect(toJSON()).toMatchSnapshot();
139
+ expect(queryAllByText('Allow notifications')).toHaveLength(2);
140
+ expect(getByText('A')).toBeTruthy();
141
+ expect(getByText('A1')).toBeTruthy();
142
+ expect(getByText('B')).toBeTruthy();
143
+ expect(getByText('B1')).toBeTruthy();
144
+ expect(getByText('B2')).toBeTruthy();
145
+ });
146
+
147
+ it('allows custom renderer', () => {
148
+ const { getByText, toJSON, getByTestId } = renderWithTheme(
149
+ <SingleSelect<string, CustomOptionType>
150
+ label="Choose collaborators"
151
+ options={collaboratorSections}
152
+ renderOption={renderSingleOption}
153
+ value=""
154
+ onConfirm={jest.fn()}
155
+ />
156
+ );
157
+ fireEvent.press(getByTestId('text-input'));
158
+
159
+ expect(toJSON()).toMatchSnapshot();
160
+ expect(getByText('Daniel')).toBeTruthy();
161
+ expect(getByText('Senior Developer')).toBeTruthy();
162
+ expect(getByText('Daemon')).toBeTruthy();
163
+ expect(getByText('Manager')).toBeTruthy();
164
+ expect(getByText('Jennifer')).toBeTruthy();
165
+ expect(getByText('UX Designer')).toBeTruthy();
166
+ expect(getByText('Josh')).toBeTruthy();
167
+ expect(getByText('Junior Developer')).toBeTruthy();
168
+ });
53
169
  });
54
170
 
55
171
  describe('behavior', () => {
@@ -1,31 +1,35 @@
1
- import React, { useEffect, useState } from 'react';
2
- import { TouchableOpacity, Keyboard, KeyboardEvent, View } from 'react-native';
1
+ import React, { useState } from 'react';
2
+ import { TouchableOpacity, View } from 'react-native';
3
3
 
4
- import { SelectProps } from '../types';
4
+ import { OptionType, SelectProps } from '../types';
5
5
  import BottomSheet from '../../BottomSheet';
6
6
  import OptionList from './OptionList';
7
7
  import TextInput from '../../TextInput';
8
8
  import { StyledSearchBar } from '../StyledSelect';
9
+ import { toFlatOptions, toSections, useKeyboard } from '../helpers';
9
10
 
10
- export interface SingleSelectProps<T> extends SelectProps<T> {
11
+ export interface SingleSelectProps<V, T extends OptionType<V> = OptionType<V>>
12
+ extends SelectProps<V, T> {
11
13
  /**
12
14
  * Current selected value.
13
15
  */
14
- value: T | null;
16
+ value: V | null;
15
17
  /**
16
18
  * on select event handler
17
19
  */
18
- onConfirm: (value: T | null) => void;
20
+ onConfirm: (value: V | null) => void;
19
21
  }
20
22
 
21
- const SingleSelect = <T,>({
23
+ const SingleSelect = <V, T extends OptionType<V>>({
22
24
  label,
23
- loading,
25
+ loading = false,
26
+ inputProps,
24
27
  onConfirm,
25
28
  onDimiss,
26
29
  onEndReached,
27
30
  onQueryChange,
28
31
  options,
32
+ renderOption,
29
33
  query,
30
34
  error,
31
35
  editable = true,
@@ -34,38 +38,21 @@ const SingleSelect = <T,>({
34
38
  style,
35
39
  testID,
36
40
  value,
37
- }: SingleSelectProps<T>) => {
41
+ }: SingleSelectProps<V, T>) => {
42
+ const { isKeyboardVisible, keyboardHeight } = useKeyboard();
38
43
  const [open, setOpen] = useState(false);
39
- const [selectingValue, setSelectingValue] = useState<T | null>(value);
40
- const displayedValue = options.find(opt => value === opt.value)?.text;
41
-
42
- const [isKeyboardVisible, setKeyboardVisible] = useState(false);
43
- const [keyboardHeight, setKeyboardHeight] = useState(0);
44
-
45
- useEffect(() => {
46
- const keyboardDidShowListener = Keyboard.addListener(
47
- 'keyboardWillShow',
48
- (e: KeyboardEvent) => {
49
- setKeyboardVisible(true);
50
- setKeyboardHeight(e.endCoordinates.height);
51
- }
52
- );
53
- const keyboardDidHideListener = Keyboard.addListener(
54
- 'keyboardWillHide',
55
- () => {
56
- setKeyboardVisible(false);
57
- }
58
- );
59
-
60
- return () => {
61
- keyboardDidHideListener.remove();
62
- keyboardDidShowListener.remove();
63
- };
64
- }, []);
44
+ const [selectingValue, setSelectingValue] = useState<V | null>(value);
45
+ const sections = toSections(options);
46
+ const flatOptions = toFlatOptions(options);
47
+ const displayedValue = flatOptions.find(opt => value === opt.value)?.text;
65
48
 
66
49
  return (
67
50
  <>
68
- <View pointerEvents={!editable || disabled ? 'none' : 'auto'}>
51
+ <View
52
+ pointerEvents={
53
+ !editable || disabled || inputProps?.loading ? 'none' : 'auto'
54
+ }
55
+ >
69
56
  <TouchableOpacity onPress={() => setOpen(true)}>
70
57
  <TextInput
71
58
  label={label}
@@ -75,6 +62,7 @@ const SingleSelect = <T,>({
75
62
  error={error}
76
63
  editable={editable}
77
64
  disabled={disabled}
65
+ loading={inputProps?.loading}
78
66
  numberOfLines={numberOfLines}
79
67
  pointerEvents="none"
80
68
  style={style}
@@ -109,7 +97,8 @@ const SingleSelect = <T,>({
109
97
  onQueryChange={onQueryChange}
110
98
  onEndReached={onEndReached}
111
99
  loading={loading}
112
- options={options}
100
+ sections={sections}
101
+ renderOption={renderOption}
113
102
  value={selectingValue}
114
103
  onPress={selectedValue => {
115
104
  setOpen(false);
@@ -1,63 +1,54 @@
1
1
  import { useTheme } from '@emotion/react';
2
- import React, { useRef, useState } from 'react';
3
- import { Dimensions, FlatList, ListRenderItemInfo, View } from 'react-native';
2
+ import React, { ReactElement, useRef, useState } from 'react';
3
+ import {
4
+ Dimensions,
5
+ SectionList,
6
+ SectionListRenderItemInfo,
7
+ View,
8
+ } from 'react-native';
9
+ import SectionHeading from '../SectionHeading';
4
10
  import Spinner from '../Spinner';
5
- import { getKey } from './helpers';
6
- import { OptionType, SelectProps } from './types';
11
+ import { ScrollParams } from './helpers';
12
+ import { OptionSpacer, SectionSpacer } from './StyledSelect';
13
+ import { SectionData, OptionType, SelectProps, SectionType } from './types';
7
14
 
8
- export interface RenderItemProps<T> {
9
- item: OptionType<T>;
10
- }
11
-
12
- export interface OptionListProps<T> extends SelectProps<T> {
13
- /**
14
- * FlatList renderItem Element
15
- */
16
- RenderItem: React.FC<RenderItemProps<T>>;
17
- /**
18
- * Selected scroll index
19
- */
20
- scrollIndex?: number;
21
- }
15
+ export type StyledOptionListProps<V, T extends OptionType<V>> = Pick<
16
+ SelectProps<V, T>,
17
+ 'keyExtractor' | 'loading' | 'onEndReached' | 'onQueryChange'
18
+ > & {
19
+ scrollParams: ScrollParams;
20
+ sections: SectionData<V, T>[];
21
+ renderItem: (info: SectionListRenderItemInfo<T, SectionType>) => ReactElement;
22
+ };
22
23
 
23
- const StyledOptionList = <T,>({
24
+ const StyledOptionList = <V, T extends OptionType<V>>({
24
25
  keyExtractor,
25
26
  loading,
26
27
  onEndReached,
27
28
  onQueryChange,
28
- options,
29
- RenderItem,
30
- scrollIndex = 0,
31
- }: Pick<
32
- OptionListProps<T>,
33
- | 'scrollIndex'
34
- | 'keyExtractor'
35
- | 'loading'
36
- | 'onEndReached'
37
- | 'options'
38
- | 'RenderItem'
39
- | 'onQueryChange'
40
- >) => {
29
+ sections,
30
+ renderItem,
31
+ scrollParams,
32
+ }: StyledOptionListProps<V, T>) => {
41
33
  const theme = useTheme();
42
- const flatListRef = useRef<FlatList>(null);
34
+ const sectionListRef = useRef<SectionList<T, SectionType>>(null);
43
35
 
44
36
  const [onEndReachedCalled, setOnEndReachedCalled] = useState(false);
45
37
 
46
38
  return (
47
- <FlatList
48
- ref={flatListRef}
39
+ <SectionList<T, SectionType>
40
+ ref={sectionListRef}
49
41
  style={{
50
- paddingHorizontal: theme.__hd__.select.space.optionListPadding,
51
42
  ...(onQueryChange ? { height: Dimensions.get('screen').height } : {}),
52
43
  }}
53
- data={options}
44
+ sections={sections}
54
45
  keyExtractor={keyExtractor}
55
46
  onEndReachedThreshold={0.1}
56
47
  onEndReached={() => setOnEndReachedCalled(true)}
57
48
  onScrollToIndexFailed={() => {}}
58
49
  onContentSizeChange={() =>
59
- options.length &&
60
- flatListRef.current?.scrollToIndex({ index: scrollIndex })
50
+ sections.length &&
51
+ sectionListRef.current?.scrollToLocation(scrollParams)
61
52
  }
62
53
  onMomentumScrollBegin={() => {
63
54
  if (onEndReached && onEndReachedCalled && !loading) onEndReached();
@@ -76,11 +67,19 @@ const StyledOptionList = <T,>({
76
67
  </View>
77
68
  ) : null
78
69
  }
79
- renderItem={({ item, index }: ListRenderItemInfo<OptionType<T>>) => (
80
- <View key={getKey(item, index, keyExtractor)}>
81
- <RenderItem item={item} />
82
- </View>
83
- )}
70
+ renderSectionHeader={({ section: { category } }) =>
71
+ category !== '' ? (
72
+ <SectionHeading
73
+ text={category}
74
+ style={{
75
+ marginBottom: 0,
76
+ }}
77
+ />
78
+ ) : null
79
+ }
80
+ renderItem={renderItem}
81
+ ItemSeparatorComponent={OptionSpacer}
82
+ SectionSeparatorComponent={SectionSpacer}
84
83
  />
85
84
  );
86
85
  };
@@ -2,8 +2,12 @@ import { View } from 'react-native';
2
2
  import styled from '@emotion/native';
3
3
  import Typography from '../Typography';
4
4
 
5
- const Spacer = styled(View)(({ theme }) => ({
6
- marginTop: theme.__hd__.select.space.optionListSpacing,
5
+ const SectionSpacer = styled(View)(({ theme }) => ({
6
+ marginTop: theme.__hd__.select.space.sectionSpacing,
7
+ }));
8
+
9
+ const OptionSpacer = styled(View)(({ theme }) => ({
10
+ marginTop: theme.__hd__.select.space.optionSpacing,
7
11
  }));
8
12
 
9
13
  const FooterText = styled(Typography.Text)(({ theme }) => ({
@@ -16,4 +20,4 @@ const StyledSearchBar = styled(View)(({ theme }) => ({
16
20
  paddingBottom: theme.__hd__.select.space.searchBarBottomSpacing,
17
21
  }));
18
22
 
19
- export { Spacer, FooterText, StyledSearchBar };
23
+ export { SectionSpacer, OptionSpacer, FooterText, StyledSearchBar };
@@ -1,14 +1,6 @@
1
1
  import React from 'react';
2
2
  import renderWithTheme from '../../../testHelpers/renderWithTheme';
3
- import { Spacer, FooterText } from '../StyledSelect';
4
-
5
- describe('Spacer', () => {
6
- it('has correct style', () => {
7
- const { toJSON } = renderWithTheme(<Spacer />);
8
-
9
- expect(toJSON()).toMatchSnapshot();
10
- });
11
- });
3
+ import { FooterText } from '../StyledSelect';
12
4
 
13
5
  describe('FooterText', () => {
14
6
  it('has correct style', () => {
@@ -5,7 +5,7 @@ exports[`FooterText has correct style 1`] = `
5
5
  style={
6
6
  Array [
7
7
  Object {
8
- "color": "#292a2b",
8
+ "color": "#001f23",
9
9
  "fontFamily": "BeVietnamPro-Regular",
10
10
  "fontSize": 14,
11
11
  "letterSpacing": 0.42,
@@ -13,7 +13,7 @@ exports[`FooterText has correct style 1`] = `
13
13
  },
14
14
  Array [
15
15
  Object {
16
- "color": "#7622d7",
16
+ "color": "#8505a2",
17
17
  },
18
18
  undefined,
19
19
  ],
@@ -26,16 +26,3 @@ exports[`FooterText has correct style 1`] = `
26
26
  Confirm
27
27
  </Text>
28
28
  `;
29
-
30
- exports[`Spacer has correct style 1`] = `
31
- <View
32
- style={
33
- Array [
34
- Object {
35
- "marginTop": 4,
36
- },
37
- undefined,
38
- ]
39
- }
40
- />
41
- `;
@@ -0,0 +1,74 @@
1
+ import {
2
+ getScrollParams,
3
+ isSections,
4
+ toFlatOptions,
5
+ toSections,
6
+ } from '../helpers';
7
+
8
+ const sections = [
9
+ { category: 'A', data: [{ text: 'A1', value: 'a1' }] },
10
+ {
11
+ category: 'B',
12
+ data: [
13
+ { text: 'B1', value: 'b1' },
14
+ { text: 'B2', value: 'b2' },
15
+ ],
16
+ },
17
+ ];
18
+
19
+ const flatOptions = [
20
+ { text: 'A', value: 'a' },
21
+ { text: 'B', value: 'b' },
22
+ ];
23
+
24
+ describe('isSections', () => {
25
+ it('returns true if receives sections', () => {
26
+ expect(isSections(sections)).toBe(true);
27
+ });
28
+
29
+ it('returns false if receives flat options', () => {
30
+ expect(isSections(flatOptions)).toBe(false);
31
+ });
32
+ });
33
+
34
+ describe('toSections', () => {
35
+ it('converts flat options to sections', () => {
36
+ expect(toSections(flatOptions)).toStrictEqual([
37
+ { category: '', data: flatOptions },
38
+ ]);
39
+ });
40
+
41
+ it('does not convert sections', () => {
42
+ expect(toSections(sections)).toStrictEqual(sections);
43
+ });
44
+ });
45
+
46
+ describe('toFlatOptions', () => {
47
+ it('converts sections to flat options', () => {
48
+ expect(toFlatOptions(sections)).toStrictEqual([
49
+ { text: 'A1', value: 'a1' },
50
+ { text: 'B1', value: 'b1' },
51
+ { text: 'B2', value: 'b2' },
52
+ ]);
53
+ });
54
+
55
+ it('does not convert flat options', () => {
56
+ expect(toFlatOptions(flatOptions)).toStrictEqual(flatOptions);
57
+ });
58
+ });
59
+
60
+ describe('getScrollParams', () => {
61
+ it('returns correctly when found value in sections', () => {
62
+ expect(getScrollParams('b2', sections)).toStrictEqual({
63
+ sectionIndex: 1,
64
+ itemIndex: 1,
65
+ });
66
+ });
67
+
68
+ it('returns 0s when not found value in sections', () => {
69
+ expect(getScrollParams('c', sections)).toStrictEqual({
70
+ sectionIndex: 0,
71
+ itemIndex: 0,
72
+ });
73
+ });
74
+ });
@@ -1,9 +1,11 @@
1
- import { OptionType } from './types';
1
+ import { useEffect, useState } from 'react';
2
+ import { Keyboard } from 'react-native';
3
+ import { SectionData, CombinedOptionsType, OptionType } from './types';
2
4
 
3
- export const getKey = <T,>(
4
- option: OptionType<T>,
5
+ export const getKey = <V, T extends OptionType<V>>(
6
+ option: T,
5
7
  index: number,
6
- keyExtractor?: (opt: OptionType<T>, i?: number) => string
8
+ keyExtractor?: (opt: T, i?: number) => string
7
9
  ) => {
8
10
  let key: React.Key = '';
9
11
  if (keyExtractor !== undefined) {
@@ -16,3 +18,84 @@ export const getKey = <T,>(
16
18
 
17
19
  return key;
18
20
  };
21
+
22
+ export const isSections = <V, T extends OptionType<V>>(
23
+ options: CombinedOptionsType<V, T>
24
+ ): options is SectionData<V, T>[] => {
25
+ const firstOption = options[0];
26
+
27
+ return (
28
+ firstOption !== undefined &&
29
+ (firstOption as SectionData<V, T>).category !== undefined
30
+ );
31
+ };
32
+
33
+ export const toSections = <V, T extends OptionType<V>>(
34
+ options: CombinedOptionsType<V, T>
35
+ ): SectionData<V, T>[] => {
36
+ if (isSections(options)) {
37
+ return options;
38
+ }
39
+
40
+ return [{ category: '', data: options }];
41
+ };
42
+
43
+ export const toFlatOptions = <V, T extends OptionType<V>>(
44
+ options: CombinedOptionsType<V, T>
45
+ ): OptionType<V>[] => {
46
+ if (isSections(options)) {
47
+ return options.flatMap(opt => opt.data);
48
+ }
49
+
50
+ return options;
51
+ };
52
+
53
+ export type ScrollParams = {
54
+ itemIndex: number;
55
+ sectionIndex: number;
56
+ };
57
+
58
+ export const getScrollParams = <V, T extends OptionType<V>>(
59
+ value: V,
60
+ sections: SectionData<V, T>[]
61
+ ): ScrollParams => {
62
+ let itemIndex = -1;
63
+
64
+ const sectionIndex = sections.findIndex(section => {
65
+ itemIndex = section.data.findIndex(opt => opt.value === value);
66
+ return itemIndex !== -1;
67
+ });
68
+
69
+ return {
70
+ sectionIndex: sectionIndex < 0 ? 0 : sectionIndex,
71
+ itemIndex: itemIndex < 0 ? 0 : itemIndex,
72
+ };
73
+ };
74
+
75
+ export const useKeyboard = () => {
76
+ const [isKeyboardVisible, setKeyboardVisible] = useState(false);
77
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
78
+
79
+ useEffect(() => {
80
+ const keyboardWillShowListener = Keyboard.addListener(
81
+ 'keyboardWillShow',
82
+ e => {
83
+ setKeyboardVisible(true);
84
+ setKeyboardHeight(e.endCoordinates.height);
85
+ }
86
+ );
87
+ const keyboardWillHideListener = Keyboard.addListener(
88
+ 'keyboardWillHide',
89
+ () => {
90
+ setKeyboardVisible(false);
91
+ }
92
+ );
93
+
94
+ return () => {
95
+ keyboardWillShowListener.remove();
96
+ keyboardWillHideListener.remove();
97
+ };
98
+ }, []);
99
+
100
+ return { isKeyboardVisible, keyboardHeight };
101
+ };