@wecareu/select 0.1.0

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 (77) hide show
  1. package/README.md +165 -0
  2. package/lib/commonjs/Select.js +272 -0
  3. package/lib/commonjs/Select.js.map +1 -0
  4. package/lib/commonjs/Select.types.js +6 -0
  5. package/lib/commonjs/Select.types.js.map +1 -0
  6. package/lib/commonjs/SelectChip.js +74 -0
  7. package/lib/commonjs/SelectChip.js.map +1 -0
  8. package/lib/commonjs/SelectDropdown.js +156 -0
  9. package/lib/commonjs/SelectDropdown.js.map +1 -0
  10. package/lib/commonjs/SelectErrorMessage.js +32 -0
  11. package/lib/commonjs/SelectErrorMessage.js.map +1 -0
  12. package/lib/commonjs/SelectIcon.js +64 -0
  13. package/lib/commonjs/SelectIcon.js.map +1 -0
  14. package/lib/commonjs/SelectOption.js +140 -0
  15. package/lib/commonjs/SelectOption.js.map +1 -0
  16. package/lib/commonjs/SelectSearch.js +79 -0
  17. package/lib/commonjs/SelectSearch.js.map +1 -0
  18. package/lib/commonjs/animations/shake.js +45 -0
  19. package/lib/commonjs/animations/shake.js.map +1 -0
  20. package/lib/commonjs/index.js +13 -0
  21. package/lib/commonjs/index.js.map +1 -0
  22. package/lib/commonjs/index.stories.js +383 -0
  23. package/lib/commonjs/index.stories.js.map +1 -0
  24. package/lib/module/Select.js +265 -0
  25. package/lib/module/Select.js.map +1 -0
  26. package/lib/module/Select.types.js +2 -0
  27. package/lib/module/Select.types.js.map +1 -0
  28. package/lib/module/SelectChip.js +67 -0
  29. package/lib/module/SelectChip.js.map +1 -0
  30. package/lib/module/SelectDropdown.js +149 -0
  31. package/lib/module/SelectDropdown.js.map +1 -0
  32. package/lib/module/SelectErrorMessage.js +25 -0
  33. package/lib/module/SelectErrorMessage.js.map +1 -0
  34. package/lib/module/SelectIcon.js +57 -0
  35. package/lib/module/SelectIcon.js.map +1 -0
  36. package/lib/module/SelectOption.js +133 -0
  37. package/lib/module/SelectOption.js.map +1 -0
  38. package/lib/module/SelectSearch.js +72 -0
  39. package/lib/module/SelectSearch.js.map +1 -0
  40. package/lib/module/animations/shake.js +39 -0
  41. package/lib/module/animations/shake.js.map +1 -0
  42. package/lib/module/index.js +2 -0
  43. package/lib/module/index.js.map +1 -0
  44. package/lib/module/index.stories.js +376 -0
  45. package/lib/module/index.stories.js.map +1 -0
  46. package/lib/typescript/src/Select.d.ts +3 -0
  47. package/lib/typescript/src/Select.d.ts.map +1 -0
  48. package/lib/typescript/src/Select.types.d.ts +245 -0
  49. package/lib/typescript/src/Select.types.d.ts.map +1 -0
  50. package/lib/typescript/src/SelectChip.d.ts +3 -0
  51. package/lib/typescript/src/SelectChip.d.ts.map +1 -0
  52. package/lib/typescript/src/SelectDropdown.d.ts +3 -0
  53. package/lib/typescript/src/SelectDropdown.d.ts.map +1 -0
  54. package/lib/typescript/src/SelectErrorMessage.d.ts +3 -0
  55. package/lib/typescript/src/SelectErrorMessage.d.ts.map +1 -0
  56. package/lib/typescript/src/SelectIcon.d.ts +3 -0
  57. package/lib/typescript/src/SelectIcon.d.ts.map +1 -0
  58. package/lib/typescript/src/SelectOption.d.ts +3 -0
  59. package/lib/typescript/src/SelectOption.d.ts.map +1 -0
  60. package/lib/typescript/src/SelectSearch.d.ts +3 -0
  61. package/lib/typescript/src/SelectSearch.d.ts.map +1 -0
  62. package/lib/typescript/src/animations/shake.d.ts +32 -0
  63. package/lib/typescript/src/animations/shake.d.ts.map +1 -0
  64. package/lib/typescript/src/index.d.ts +3 -0
  65. package/lib/typescript/src/index.d.ts.map +1 -0
  66. package/package.json +66 -0
  67. package/src/Select.tsx +338 -0
  68. package/src/Select.types.ts +256 -0
  69. package/src/SelectChip.tsx +75 -0
  70. package/src/SelectDropdown.tsx +184 -0
  71. package/src/SelectErrorMessage.tsx +31 -0
  72. package/src/SelectIcon.tsx +63 -0
  73. package/src/SelectOption.tsx +168 -0
  74. package/src/SelectSearch.tsx +81 -0
  75. package/src/animations/shake.ts +76 -0
  76. package/src/index.stories.tsx +336 -0
  77. package/src/index.tsx +2 -0
@@ -0,0 +1,168 @@
1
+ import React from 'react'
2
+
3
+ import { Pressable, StyleSheet, Text, View } from 'react-native'
4
+
5
+ import { FlagIcon, Icon } from '@wecareu/icons'
6
+
7
+ import { useTheme } from '@wecareu/theme'
8
+ import type { Theme } from '@wecareu/theme'
9
+
10
+ import type { SelectOptionItemProps } from './Select.types'
11
+
12
+ export function SelectOptionItem({
13
+ disabled,
14
+ isMulti,
15
+ isSelected,
16
+ onPress,
17
+ option
18
+ }: SelectOptionItemProps): JSX.Element {
19
+ const theme = useTheme()
20
+ const styles = React.useMemo(() => createStyles(theme), [theme])
21
+
22
+ const handlePress = React.useCallback(() => {
23
+ if (!disabled) {
24
+ onPress(option)
25
+ }
26
+ }, [disabled, onPress, option])
27
+
28
+ const resolvedLabelColor = option.labelColor
29
+ ? option.labelColor
30
+ : isSelected
31
+ ? theme.colors.brand.primary
32
+ : theme.colors.text.primary
33
+
34
+ return (
35
+ <Pressable
36
+ accessibilityRole='menuitem'
37
+ accessibilityState={{ disabled, selected: isSelected }}
38
+ disabled={disabled}
39
+ onPress={handlePress}
40
+ style={({ pressed }) => [
41
+ styles.container,
42
+ option.color ? { backgroundColor: option.color } : isSelected && styles.containerSelected,
43
+ disabled && styles.containerDisabled,
44
+ pressed && !disabled && !option.color && styles.containerPressed
45
+ ]}
46
+ >
47
+ {option.flagCode ? (
48
+ <View style={styles.iconWrapper}>
49
+ <FlagIcon
50
+ countryCode={option.flagCode}
51
+ size={theme.spacing.md + 2}
52
+ />
53
+ </View>
54
+ ) : option.icon ? (
55
+ <View style={styles.iconWrapper}>
56
+ <Icon
57
+ color={disabled ? theme.colors.text.disabled : resolvedLabelColor}
58
+ name={option.icon}
59
+ size={theme.spacing.lg}
60
+ />
61
+ </View>
62
+ ) : null}
63
+
64
+ <View style={styles.textWrapper}>
65
+ <Text
66
+ numberOfLines={1}
67
+ style={[
68
+ styles.label,
69
+ { color: disabled ? theme.colors.text.disabled : resolvedLabelColor },
70
+ isSelected && !option.labelColor && styles.labelSelected,
71
+ disabled && styles.labelDisabled
72
+ ]}
73
+ >
74
+ {option.label}
75
+ </Text>
76
+
77
+ {option.description ? (
78
+ <Text
79
+ numberOfLines={2}
80
+ style={[styles.description, disabled && styles.descriptionDisabled]}
81
+ >
82
+ {option.description}
83
+ </Text>
84
+ ) : null}
85
+ </View>
86
+
87
+ {isMulti ? (
88
+ <View style={[styles.checkbox, isSelected && styles.checkboxSelected, disabled && styles.checkboxDisabled]}>
89
+ {isSelected ? (
90
+ <Icon color={theme.colors.text.white} name='correct' size={12} />
91
+ ) : null}
92
+ </View>
93
+ ) : (
94
+ isSelected ? (
95
+ <Icon color={option.labelColor ?? theme.colors.brand.primary} name='correct' size={theme.spacing.md} />
96
+ ) : null
97
+ )}
98
+ </Pressable>
99
+ )
100
+ }
101
+
102
+ function createStyles(theme: Theme) {
103
+ return StyleSheet.create({
104
+ checkbox: {
105
+ alignItems: 'center',
106
+ borderColor: theme.colors.border.primary,
107
+ borderRadius: theme.radius.xs,
108
+ borderWidth: 1.5,
109
+ height: 20,
110
+ justifyContent: 'center',
111
+ width: 20
112
+ },
113
+ checkboxDisabled: {
114
+ borderColor: theme.colors.border.disabled
115
+ },
116
+ checkboxSelected: {
117
+ backgroundColor: theme.colors.brand.primary,
118
+ borderColor: theme.colors.brand.primary
119
+ },
120
+ container: {
121
+ alignItems: 'center',
122
+ flexDirection: 'row',
123
+ gap: theme.spacing.sm,
124
+ paddingHorizontal: theme.spacing.md,
125
+ paddingVertical: theme.spacing.sm
126
+ },
127
+ containerDisabled: {
128
+ opacity: 0.4
129
+ },
130
+ containerPressed: {
131
+ backgroundColor: theme.colors.surface.secondary
132
+ },
133
+ containerSelected: {
134
+ backgroundColor: theme.colors.surface.secondary
135
+ },
136
+ description: {
137
+ color: theme.colors.text.secondary,
138
+ fontFamily: theme.typography.fontFamily.caption,
139
+ fontSize: theme.typography.fontSize.xs,
140
+ lineHeight: theme.typography.lineHeight.labelSmall,
141
+ marginTop: 2
142
+ },
143
+ descriptionDisabled: {
144
+ color: theme.colors.text.disabled
145
+ },
146
+ iconWrapper: {
147
+ alignItems: 'center',
148
+ height: theme.spacing.lg,
149
+ justifyContent: 'center',
150
+ marginRight: theme.spacing.md,
151
+ width: theme.spacing.lg
152
+ },
153
+ label: {
154
+ fontFamily: theme.typography.fontFamily.body,
155
+ fontSize: theme.typography.fontSize.md,
156
+ lineHeight: theme.typography.lineHeight.bodySmall
157
+ },
158
+ labelDisabled: {
159
+ color: theme.colors.text.disabled
160
+ },
161
+ labelSelected: {
162
+ fontFamily: theme.typography.fontFamily.semibold
163
+ },
164
+ textWrapper: {
165
+ flex: 1
166
+ }
167
+ })
168
+ }
@@ -0,0 +1,81 @@
1
+ import React from 'react'
2
+
3
+ import { StyleSheet, TextInput, View } from 'react-native'
4
+
5
+ import { Icon } from '@wecareu/icons'
6
+ import { useTheme } from '@wecareu/theme'
7
+ import type { Theme } from '@wecareu/theme'
8
+
9
+ import type { SelectSearchProps } from './Select.types'
10
+
11
+ const DEBOUNCE_MS = 200
12
+
13
+ export function SelectSearch({ onChangeText, value }: SelectSearchProps): JSX.Element {
14
+ const theme = useTheme()
15
+ const styles = React.useMemo(() => createStyles(theme), [theme])
16
+ const debounceRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
17
+
18
+ const handleChangeText = React.useCallback(
19
+ (text: string) => {
20
+ if (debounceRef.current) {
21
+ clearTimeout(debounceRef.current)
22
+ }
23
+ debounceRef.current = setTimeout(() => {
24
+ onChangeText(text)
25
+ }, DEBOUNCE_MS)
26
+ },
27
+ [onChangeText]
28
+ )
29
+
30
+ React.useEffect(() => {
31
+ return () => {
32
+ if (debounceRef.current) {
33
+ clearTimeout(debounceRef.current)
34
+ }
35
+ }
36
+ }, [])
37
+
38
+ return (
39
+ <View style={styles.container}>
40
+ <View style={styles.iconWrapper}>
41
+ <Icon color={theme.colors.text.tertiary} name='search' size={theme.spacing.md} />
42
+ </View>
43
+ <TextInput
44
+ accessibilityLabel='Search options'
45
+ autoCapitalize='none'
46
+ autoCorrect={false}
47
+ clearButtonMode='while-editing'
48
+ defaultValue={value}
49
+ onChangeText={handleChangeText}
50
+ placeholderTextColor={theme.colors.text.tertiary}
51
+ returnKeyType='search'
52
+ style={styles.input}
53
+ />
54
+ </View>
55
+ )
56
+ }
57
+
58
+ function createStyles(theme: Theme) {
59
+ return StyleSheet.create({
60
+ container: {
61
+ alignItems: 'center',
62
+ borderBottomColor: theme.colors.border.secondary,
63
+ borderBottomWidth: 1,
64
+ flexDirection: 'row',
65
+ marginBottom: theme.spacing.xs,
66
+ paddingHorizontal: theme.spacing.md,
67
+ paddingVertical: theme.spacing.sm
68
+ },
69
+ iconWrapper: {
70
+ marginRight: theme.spacing.xs
71
+ },
72
+ input: {
73
+ color: theme.colors.text.primary,
74
+ flex: 1,
75
+ fontFamily: theme.typography.fontFamily.body,
76
+ fontSize: theme.typography.fontSize.md,
77
+ lineHeight: theme.typography.lineHeight.bodySmall,
78
+ paddingVertical: theme.spacing.xs
79
+ }
80
+ })
81
+ }
@@ -0,0 +1,76 @@
1
+ import { useCallback, useMemo, useRef } from 'react'
2
+
3
+ import { Animated } from 'react-native'
4
+
5
+ interface UseShakeAnimationParams {
6
+ /**
7
+ * Distance in pixels applied to the shake translation
8
+ */
9
+ distance?: number
10
+ /**
11
+ * Duration of each shake step in milliseconds
12
+ */
13
+ duration?: number
14
+ /**
15
+ * Number of oscillations executed per trigger
16
+ */
17
+ iterations?: number
18
+ }
19
+
20
+ interface UseShakeAnimationReturn {
21
+ /**
22
+ * Animated style applied to the container that needs feedback
23
+ */
24
+ animatedStyle: {
25
+ transform: { translateX: Animated.AnimatedInterpolation<number> }[]
26
+ }
27
+ /**
28
+ * Function responsible for starting the shake effect
29
+ */
30
+ triggerShake: () => void
31
+ }
32
+
33
+ export function useShakeAnimation({
34
+ distance = 6,
35
+ duration = 60,
36
+ iterations = 2
37
+ }: UseShakeAnimationParams = {}): UseShakeAnimationReturn {
38
+ const translation = useRef(new Animated.Value(0)).current
39
+
40
+ const animatedStyle = useMemo(
41
+ () => ({
42
+ transform: [
43
+ {
44
+ translateX: translation.interpolate({
45
+ inputRange: [-1, -0.5, 0, 0.5, 1],
46
+ outputRange: [-distance, -distance / 2, 0, distance / 2, distance]
47
+ })
48
+ }
49
+ ]
50
+ }),
51
+ [distance, translation]
52
+ )
53
+
54
+ const triggerShake = useCallback(() => {
55
+ translation.setValue(0)
56
+
57
+ const animation = Animated.sequence([
58
+ Animated.timing(translation, {
59
+ duration,
60
+ toValue: 1,
61
+ useNativeDriver: true
62
+ }),
63
+ Animated.timing(translation, {
64
+ duration,
65
+ toValue: -1,
66
+ useNativeDriver: true
67
+ })
68
+ ])
69
+
70
+ Animated.loop(animation, { iterations }).start(() => {
71
+ translation.setValue(0)
72
+ })
73
+ }, [duration, iterations, translation])
74
+
75
+ return { animatedStyle, triggerShake }
76
+ }
@@ -0,0 +1,336 @@
1
+ import React from 'react'
2
+
3
+ import { StyleSheet, View } from 'react-native'
4
+
5
+ import { action } from '@storybook/addon-actions'
6
+ import type { Meta, StoryObj } from '@storybook/react'
7
+
8
+ import { Select } from '.'
9
+ import type { SelectMultiProps, SelectOption, SelectSingleProps } from './Select.types'
10
+
11
+ const GENDER_OPTIONS: SelectOption[] = [
12
+ { label: 'Female', value: 'F' },
13
+ { label: 'Male', value: 'M' },
14
+ { label: 'Non-binary', value: 'NB' },
15
+ { label: 'Prefer not to say', value: 'X' }
16
+ ]
17
+
18
+ const COUNTRIES_OPTIONS: SelectOption[] = [
19
+ { description: 'South America', flagCode: 'AR', label: 'Argentina', value: 'AR' },
20
+ { description: 'Oceania', flagCode: 'AU', label: 'Australia', value: 'AU' },
21
+ { description: 'South America', flagCode: 'BR', label: 'Brazil', value: 'BR' },
22
+ { description: 'North America', flagCode: 'CA', label: 'Canada', value: 'CA' },
23
+ { description: 'South America', flagCode: 'CL', label: 'Chile', value: 'CL' },
24
+ { description: 'Asia', flagCode: 'CN', label: 'China', value: 'CN' },
25
+ { description: 'Europe', flagCode: 'FR', label: 'France', value: 'FR' },
26
+ { description: 'Europe', flagCode: 'DE', label: 'Germany', value: 'DE' },
27
+ { description: 'Asia', flagCode: 'IN', label: 'India', value: 'IN' },
28
+ { description: 'Europe', flagCode: 'IT', label: 'Italy', value: 'IT' },
29
+ { description: 'Asia', flagCode: 'JP', label: 'Japan', value: 'JP' },
30
+ { description: 'Europe', flagCode: 'PT', label: 'Portugal', value: 'PT' },
31
+ { description: 'Europe', flagCode: 'ES', label: 'Spain', value: 'ES' },
32
+ { description: 'North America', flagCode: 'US', label: 'United States', value: 'US' }
33
+ ]
34
+
35
+ const TAGS_OPTIONS: SelectOption[] = [
36
+ { label: 'Design System', value: 'ds' },
37
+ { label: 'Expo', value: 'expo' },
38
+ { label: 'React Native', value: 'rn' },
39
+ { label: 'Storybook', value: 'sb' },
40
+ { label: 'Testing', value: 'test' },
41
+ { label: 'TypeScript', value: 'ts' }
42
+ ]
43
+
44
+ const STATUS_OPTIONS: SelectOption[] = [
45
+ { description: 'User is available', icon: 'correct', label: 'Available', labelColor: '#22c55e', value: 'available' },
46
+ { description: 'User is in a meeting', icon: 'time', label: 'Busy', labelColor: '#f59e0b', value: 'busy' },
47
+ { description: 'User is not here', disabled: true, icon: 'close', label: 'Away', value: 'away' },
48
+ { description: 'Appears offline to others', icon: 'notificationOff', label: 'Offline', labelColor: '#94a3b8', value: 'offline' }
49
+ ]
50
+
51
+ const ROLE_OPTIONS: SelectOption[] = [
52
+ { color: '#fef9c3', label: 'Admin', labelColor: '#854d0e', value: 'admin' },
53
+ { color: '#dcfce7', label: 'Editor', labelColor: '#166534', value: 'editor' },
54
+ { color: '#dbeafe', label: 'Viewer', labelColor: '#1e40af', value: 'viewer' },
55
+ { color: '#fee2e2', disabled: true, label: 'Banned', labelColor: '#991b1b', value: 'banned' }
56
+ ]
57
+
58
+ interface StatefulSingleProps extends Omit<SelectSingleProps, 'onChange' | 'value'> {
59
+ initialValue?: string | number | null
60
+ }
61
+
62
+ interface StatefulMultiProps extends Omit<SelectMultiProps, 'multiple' | 'onChange' | 'value'> {
63
+ initialValues?: Array<string | number>
64
+ }
65
+
66
+ function StatefulSingleSelect({ initialValue = null, ...rest }: StatefulSingleProps) {
67
+ const [value, setValue] = React.useState<string | number | null>(initialValue)
68
+
69
+ const handleChange = React.useCallback(
70
+ (next: string | number | null) => {
71
+ setValue(next)
72
+ action('onChange')(next)
73
+ },
74
+ []
75
+ )
76
+
77
+ return <Select {...rest} onChange={handleChange} value={value} />
78
+ }
79
+
80
+ function StatefulMultiSelect({ initialValues = [], ...rest }: StatefulMultiProps) {
81
+ const [values, setValues] = React.useState<Array<string | number>>(initialValues)
82
+
83
+ const handleChange = React.useCallback(
84
+ (next: Array<string | number>) => {
85
+ setValues(next)
86
+ action('onChange')(next)
87
+ },
88
+ []
89
+ )
90
+
91
+ return <Select {...rest} multiple onChange={handleChange} value={values} />
92
+ }
93
+
94
+ const meta: Meta = {
95
+ component: Select,
96
+ parameters: {
97
+ layout: 'centered'
98
+ },
99
+ tags: ['autodocs'],
100
+ title: 'Forms/Select'
101
+ }
102
+
103
+ export default meta
104
+ type Story = StoryObj<typeof meta>
105
+
106
+ export const Default: Story = {
107
+ render: () => (
108
+ <View style={styles.container}>
109
+ <StatefulSingleSelect
110
+ options={GENDER_OPTIONS}
111
+ placeholder='Select gender'
112
+ />
113
+ </View>
114
+ )
115
+ }
116
+
117
+ export const Selected: Story = {
118
+ render: () => (
119
+ <View style={styles.container}>
120
+ <StatefulSingleSelect
121
+ initialValue='BR'
122
+ options={COUNTRIES_OPTIONS}
123
+ placeholder='Select country'
124
+ />
125
+ </View>
126
+ )
127
+ }
128
+
129
+ export const WithFlags: Story = {
130
+ render: () => (
131
+ <View style={styles.container}>
132
+ <StatefulSingleSelect
133
+ clearable
134
+ options={COUNTRIES_OPTIONS}
135
+ placeholder='Select country'
136
+ searchable
137
+ />
138
+ </View>
139
+ )
140
+ }
141
+
142
+ export const WithIconsAndLabelColors: Story = {
143
+ render: () => (
144
+ <View style={styles.container}>
145
+ <StatefulSingleSelect
146
+ clearable
147
+ options={STATUS_OPTIONS}
148
+ placeholder='Select status'
149
+ />
150
+ </View>
151
+ )
152
+ }
153
+
154
+ export const WithRowColors: Story = {
155
+ render: () => (
156
+ <View style={styles.container}>
157
+ <StatefulSingleSelect
158
+ clearable
159
+ options={ROLE_OPTIONS}
160
+ placeholder='Select role'
161
+ />
162
+ </View>
163
+ )
164
+ }
165
+
166
+ export const MultipleSelect: Story = {
167
+ render: () => (
168
+ <View style={styles.container}>
169
+ <StatefulMultiSelect
170
+ clearable
171
+ options={TAGS_OPTIONS}
172
+ placeholder='Select tags'
173
+ />
174
+ </View>
175
+ )
176
+ }
177
+
178
+ export const MultipleWithInitialValues: Story = {
179
+ render: () => (
180
+ <View style={styles.container}>
181
+ <StatefulMultiSelect
182
+ clearable
183
+ initialValues={['rn', 'ts']}
184
+ options={TAGS_OPTIONS}
185
+ placeholder='Select tags'
186
+ />
187
+ </View>
188
+ )
189
+ }
190
+
191
+ export const MultipleSearchable: Story = {
192
+ render: () => (
193
+ <View style={styles.container}>
194
+ <StatefulMultiSelect
195
+ clearable
196
+ options={COUNTRIES_OPTIONS}
197
+ placeholder='Select countries'
198
+ searchable
199
+ />
200
+ </View>
201
+ )
202
+ }
203
+
204
+ export const MultipleWithDisabledOptions: Story = {
205
+ render: () => (
206
+ <View style={styles.container}>
207
+ <StatefulMultiSelect
208
+ clearable
209
+ options={STATUS_OPTIONS}
210
+ placeholder='Select statuses'
211
+ />
212
+ </View>
213
+ )
214
+ }
215
+
216
+ export const Clearable: Story = {
217
+ render: () => (
218
+ <View style={styles.container}>
219
+ <StatefulSingleSelect
220
+ clearable
221
+ initialValue='M'
222
+ options={GENDER_OPTIONS}
223
+ placeholder='Select gender'
224
+ />
225
+ </View>
226
+ )
227
+ }
228
+
229
+ export const WithError: Story = {
230
+ render: () => (
231
+ <View style={styles.container}>
232
+ <StatefulSingleSelect
233
+ errorMessage='This field is required'
234
+ inputError
235
+ options={GENDER_OPTIONS}
236
+ placeholder='Select gender'
237
+ required
238
+ />
239
+ </View>
240
+ )
241
+ }
242
+
243
+ export const Disabled: Story = {
244
+ render: () => (
245
+ <View style={styles.container}>
246
+ <StatefulSingleSelect
247
+ disabled
248
+ initialValue='M'
249
+ options={GENDER_OPTIONS}
250
+ placeholder='Select gender'
251
+ />
252
+ </View>
253
+ )
254
+ }
255
+
256
+ export const Readonly: Story = {
257
+ render: () => (
258
+ <View style={styles.container}>
259
+ <StatefulSingleSelect
260
+ initialValue='BR'
261
+ options={COUNTRIES_OPTIONS}
262
+ placeholder='Select country'
263
+ readonly
264
+ />
265
+ </View>
266
+ )
267
+ }
268
+
269
+ export const WithDisabledOptions: Story = {
270
+ render: () => (
271
+ <View style={styles.container}>
272
+ <StatefulSingleSelect
273
+ options={STATUS_OPTIONS}
274
+ placeholder='Select status'
275
+ />
276
+ </View>
277
+ )
278
+ }
279
+
280
+ export const AllStates: Story = {
281
+ render: () => (
282
+ <View style={styles.column}>
283
+ <View style={styles.storyItem}>
284
+ <StatefulSingleSelect
285
+ options={GENDER_OPTIONS}
286
+ placeholder='Default - empty'
287
+ />
288
+ </View>
289
+ <View style={styles.storyItem}>
290
+ <StatefulSingleSelect
291
+ clearable
292
+ initialValue='M'
293
+ options={GENDER_OPTIONS}
294
+ placeholder='With value + clearable'
295
+ />
296
+ </View>
297
+ <View style={styles.storyItem}>
298
+ <StatefulSingleSelect
299
+ errorMessage='Required field'
300
+ inputError
301
+ options={GENDER_OPTIONS}
302
+ placeholder='Error state'
303
+ />
304
+ </View>
305
+ <View style={styles.storyItem}>
306
+ <StatefulSingleSelect
307
+ disabled
308
+ initialValue='F'
309
+ options={GENDER_OPTIONS}
310
+ placeholder='Disabled'
311
+ />
312
+ </View>
313
+ <View style={styles.storyItem}>
314
+ <StatefulMultiSelect
315
+ clearable
316
+ initialValues={['ts', 'rn']}
317
+ options={TAGS_OPTIONS}
318
+ placeholder='Multi select with chips'
319
+ />
320
+ </View>
321
+ </View>
322
+ )
323
+ }
324
+
325
+ const styles = StyleSheet.create({
326
+ column: {
327
+ gap: 12,
328
+ width: 320
329
+ },
330
+ container: {
331
+ width: 320
332
+ },
333
+ storyItem: {
334
+ width: '100%'
335
+ }
336
+ })
package/src/index.tsx ADDED
@@ -0,0 +1,2 @@
1
+ export { Select } from './Select'
2
+ export type { SelectMultiProps, SelectOption, SelectProps, SelectSingleProps } from './Select.types'