@wecareu/input-text 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.
- package/README.md +36 -0
- package/lib/commonjs/InputText.js +350 -0
- package/lib/commonjs/InputText.js.map +1 -0
- package/lib/commonjs/InputText.types.js +6 -0
- package/lib/commonjs/InputText.types.js.map +1 -0
- package/lib/commonjs/InputTextErrorMessage.js +32 -0
- package/lib/commonjs/InputTextErrorMessage.js.map +1 -0
- package/lib/commonjs/InputTextIcon.js +65 -0
- package/lib/commonjs/InputTextIcon.js.map +1 -0
- package/lib/commonjs/animations/shake.js +45 -0
- package/lib/commonjs/animations/shake.js.map +1 -0
- package/lib/commonjs/index.js +13 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/index.stories.js +409 -0
- package/lib/commonjs/index.stories.js.map +1 -0
- package/lib/module/InputText.js +343 -0
- package/lib/module/InputText.js.map +1 -0
- package/lib/module/InputText.types.js +2 -0
- package/lib/module/InputText.types.js.map +1 -0
- package/lib/module/InputTextErrorMessage.js +25 -0
- package/lib/module/InputTextErrorMessage.js.map +1 -0
- package/lib/module/InputTextIcon.js +58 -0
- package/lib/module/InputTextIcon.js.map +1 -0
- package/lib/module/animations/shake.js +39 -0
- package/lib/module/animations/shake.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/index.stories.js +402 -0
- package/lib/module/index.stories.js.map +1 -0
- package/lib/typescript/src/InputText.d.ts +5 -0
- package/lib/typescript/src/InputText.d.ts.map +1 -0
- package/lib/typescript/src/InputText.types.d.ts +146 -0
- package/lib/typescript/src/InputText.types.d.ts.map +1 -0
- package/lib/typescript/src/InputTextErrorMessage.d.ts +3 -0
- package/lib/typescript/src/InputTextErrorMessage.d.ts.map +1 -0
- package/lib/typescript/src/InputTextIcon.d.ts +3 -0
- package/lib/typescript/src/InputTextIcon.d.ts.map +1 -0
- package/lib/typescript/src/animations/shake.d.ts +32 -0
- package/lib/typescript/src/animations/shake.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +66 -0
- package/src/InputText.tsx +451 -0
- package/src/InputText.types.ts +153 -0
- package/src/InputTextErrorMessage.tsx +31 -0
- package/src/InputTextIcon.tsx +65 -0
- package/src/animations/shake.ts +76 -0
- package/src/index.stories.tsx +387 -0
- package/src/index.tsx +2 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { Pressable, StyleSheet, TouchableOpacity, View } from 'react-native'
|
|
4
|
+
|
|
5
|
+
import { Icon } from '@wecareu/icons'
|
|
6
|
+
import { useTheme } from '@wecareu/theme'
|
|
7
|
+
|
|
8
|
+
import type { InputTextIconProps } from './InputText.types'
|
|
9
|
+
|
|
10
|
+
export function InputTextIcon({
|
|
11
|
+
accessibilityLabel,
|
|
12
|
+
color,
|
|
13
|
+
name,
|
|
14
|
+
onPress,
|
|
15
|
+
size,
|
|
16
|
+
testID
|
|
17
|
+
}: InputTextIconProps): JSX.Element {
|
|
18
|
+
const theme = useTheme()
|
|
19
|
+
|
|
20
|
+
const iconColor = color ?? theme.colors.text.tertiary
|
|
21
|
+
const iconSize = size ?? theme.spacing.lg
|
|
22
|
+
const hitSlop = React.useMemo(
|
|
23
|
+
() => ({
|
|
24
|
+
bottom: theme.spacing.xs,
|
|
25
|
+
left: theme.spacing.xs,
|
|
26
|
+
right: theme.spacing.xs,
|
|
27
|
+
top: theme.spacing.xs
|
|
28
|
+
}),
|
|
29
|
+
[theme.spacing.xs]
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
if (onPress) {
|
|
33
|
+
const InteractiveComponent = Pressable ?? TouchableOpacity
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<InteractiveComponent
|
|
37
|
+
accessibilityLabel={accessibilityLabel ?? name}
|
|
38
|
+
accessibilityRole='button'
|
|
39
|
+
hitSlop={hitSlop}
|
|
40
|
+
onPress={onPress}
|
|
41
|
+
style={styles.iconPressable}
|
|
42
|
+
testID={testID}
|
|
43
|
+
>
|
|
44
|
+
<Icon color={iconColor} name={name} size={iconSize} />
|
|
45
|
+
</InteractiveComponent>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<View accessible={false} style={styles.iconWrapper} testID={testID}>
|
|
51
|
+
<Icon accessibilityLabel={accessibilityLabel} color={iconColor} name={name} size={iconSize} />
|
|
52
|
+
</View>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const styles = StyleSheet.create({
|
|
57
|
+
iconPressable: {
|
|
58
|
+
alignItems: 'center',
|
|
59
|
+
justifyContent: 'center'
|
|
60
|
+
},
|
|
61
|
+
iconWrapper: {
|
|
62
|
+
alignItems: 'center',
|
|
63
|
+
justifyContent: 'center'
|
|
64
|
+
}
|
|
65
|
+
})
|
|
@@ -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,387 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { StyleSheet, TextInput, View } from 'react-native'
|
|
4
|
+
import type { KeyboardTypeOptions } from 'react-native'
|
|
5
|
+
|
|
6
|
+
import { action } from '@storybook/addon-actions'
|
|
7
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
8
|
+
|
|
9
|
+
import { ICON_NAMES } from '@wecareu/icons'
|
|
10
|
+
|
|
11
|
+
import { InputText } from '.'
|
|
12
|
+
import type { InputTextProps } from './InputText.types'
|
|
13
|
+
|
|
14
|
+
const ICON_OPTIONS = ['none', ...ICON_NAMES] as const
|
|
15
|
+
const INPUT_TYPE_OPTIONS = ['text', 'email', 'password', 'number', 'phone', 'url'] as const
|
|
16
|
+
const KEYBOARD_OPTIONS: KeyboardTypeOptions[] = [
|
|
17
|
+
'default',
|
|
18
|
+
'email-address',
|
|
19
|
+
'numeric',
|
|
20
|
+
'phone-pad',
|
|
21
|
+
'url',
|
|
22
|
+
'visible-password'
|
|
23
|
+
]
|
|
24
|
+
const TEXT_CONTENT_OPTIONS = [
|
|
25
|
+
'none',
|
|
26
|
+
'emailAddress',
|
|
27
|
+
'username',
|
|
28
|
+
'password',
|
|
29
|
+
'oneTimeCode',
|
|
30
|
+
'telephoneNumber',
|
|
31
|
+
'URL'
|
|
32
|
+
] as const
|
|
33
|
+
|
|
34
|
+
interface StatefulInputProps extends Omit<InputTextProps, 'onChangeText' | 'value'> {
|
|
35
|
+
initialValue?: string
|
|
36
|
+
onChangeText: (value: string) => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface DefaultStoryArgs extends StatefulInputProps {
|
|
40
|
+
inputType: (typeof INPUT_TYPE_OPTIONS)[number]
|
|
41
|
+
keyboardType: KeyboardTypeOptions
|
|
42
|
+
leftIconName?: (typeof ICON_OPTIONS)[number]
|
|
43
|
+
rightIconName?: (typeof ICON_OPTIONS)[number]
|
|
44
|
+
textContentType?: (typeof TEXT_CONTENT_OPTIONS)[number]
|
|
45
|
+
validatorPattern?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type Story = StoryObj<DefaultStoryArgs>
|
|
49
|
+
|
|
50
|
+
function StatefulInput({ initialValue = '', onChangeText, ...rest }: StatefulInputProps) {
|
|
51
|
+
const [value, setValue] = React.useState(initialValue)
|
|
52
|
+
|
|
53
|
+
const handleChange = React.useCallback(
|
|
54
|
+
(nextValue: string) => {
|
|
55
|
+
setValue(nextValue)
|
|
56
|
+
onChangeText(nextValue)
|
|
57
|
+
},
|
|
58
|
+
[onChangeText]
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return <InputText {...rest} onChangeText={handleChange} value={value} />
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const meta: Meta<DefaultStoryArgs> = {
|
|
65
|
+
args: {
|
|
66
|
+
disabled: false,
|
|
67
|
+
initialValue: '',
|
|
68
|
+
inputError: false,
|
|
69
|
+
inputType: 'text',
|
|
70
|
+
keyboardType: 'default',
|
|
71
|
+
leftIconName: 'none',
|
|
72
|
+
locale: 'pt',
|
|
73
|
+
maskNumber: false,
|
|
74
|
+
maxLength: 255,
|
|
75
|
+
minLength: 3,
|
|
76
|
+
onChangeText: action('onChangeText'),
|
|
77
|
+
placeholder: 'Digite seu texto',
|
|
78
|
+
readonly: false,
|
|
79
|
+
rightIconName: 'none',
|
|
80
|
+
secureTextEntry: false,
|
|
81
|
+
textContentType: 'none',
|
|
82
|
+
validateOnBlur: true,
|
|
83
|
+
validationMessage: 'Digite ao menos 3 caracteres',
|
|
84
|
+
validatorMessage: 'Informe um valor válido',
|
|
85
|
+
validatorPattern: ''
|
|
86
|
+
},
|
|
87
|
+
argTypes: {
|
|
88
|
+
disabled: { control: 'boolean' },
|
|
89
|
+
formatter: { control: false },
|
|
90
|
+
initialValue: { control: 'text' },
|
|
91
|
+
inputError: { control: 'boolean' },
|
|
92
|
+
inputStyle: { control: false },
|
|
93
|
+
inputType: {
|
|
94
|
+
control: { type: 'select' },
|
|
95
|
+
options: INPUT_TYPE_OPTIONS
|
|
96
|
+
},
|
|
97
|
+
keyboardType: {
|
|
98
|
+
control: { type: 'select' },
|
|
99
|
+
options: KEYBOARD_OPTIONS
|
|
100
|
+
},
|
|
101
|
+
leftIcon: { control: false },
|
|
102
|
+
leftIconName: {
|
|
103
|
+
control: { type: 'select' },
|
|
104
|
+
options: ICON_OPTIONS
|
|
105
|
+
},
|
|
106
|
+
locale: {
|
|
107
|
+
control: { type: 'select' },
|
|
108
|
+
options: ['pt', 'en']
|
|
109
|
+
},
|
|
110
|
+
maskNumber: { control: 'boolean' },
|
|
111
|
+
maxLength: { control: { min: 1, step: 1, type: 'number' } },
|
|
112
|
+
minLength: { control: { min: 0, step: 1, type: 'number' } },
|
|
113
|
+
nextInputRef: { control: false },
|
|
114
|
+
onChangeText: { control: false },
|
|
115
|
+
parser: { control: false },
|
|
116
|
+
placeholder: { control: 'text' },
|
|
117
|
+
readonly: { control: 'boolean' },
|
|
118
|
+
rightIcon: { control: false },
|
|
119
|
+
rightIconName: {
|
|
120
|
+
control: { type: 'select' },
|
|
121
|
+
options: ICON_OPTIONS
|
|
122
|
+
},
|
|
123
|
+
secureTextEntry: { control: 'boolean' },
|
|
124
|
+
style: { control: false },
|
|
125
|
+
textContentType: {
|
|
126
|
+
control: { type: 'select' },
|
|
127
|
+
options: TEXT_CONTENT_OPTIONS
|
|
128
|
+
},
|
|
129
|
+
validateOnBlur: { control: 'boolean' },
|
|
130
|
+
validationMessage: { control: 'text' },
|
|
131
|
+
validator: { control: false },
|
|
132
|
+
validatorMessage: { control: 'text' },
|
|
133
|
+
validatorPattern: { control: 'text' },
|
|
134
|
+
value: { control: false }
|
|
135
|
+
},
|
|
136
|
+
component: InputText,
|
|
137
|
+
parameters: {
|
|
138
|
+
layout: 'centered'
|
|
139
|
+
},
|
|
140
|
+
tags: ['autodocs'],
|
|
141
|
+
title: 'Forms/InputText'
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default meta
|
|
145
|
+
|
|
146
|
+
export const Default: Story = {
|
|
147
|
+
render: args => {
|
|
148
|
+
const {
|
|
149
|
+
initialValue,
|
|
150
|
+
inputType,
|
|
151
|
+
keyboardType,
|
|
152
|
+
leftIconName,
|
|
153
|
+
locale,
|
|
154
|
+
maskNumber,
|
|
155
|
+
rightIconName,
|
|
156
|
+
textContentType,
|
|
157
|
+
validatorPattern,
|
|
158
|
+
validatorMessage,
|
|
159
|
+
...inputArgs
|
|
160
|
+
} = args
|
|
161
|
+
|
|
162
|
+
let derivedPlaceholder = inputArgs.placeholder
|
|
163
|
+
let derivedSecureTextEntry = inputArgs.secureTextEntry
|
|
164
|
+
let derivedKeyboardType = keyboardType
|
|
165
|
+
let derivedTextContentType = textContentType
|
|
166
|
+
let derivedValidatorPattern = validatorPattern
|
|
167
|
+
let derivedValidatorMessage = validatorMessage
|
|
168
|
+
|
|
169
|
+
switch (inputType) {
|
|
170
|
+
case 'email':
|
|
171
|
+
derivedPlaceholder = 'Email'
|
|
172
|
+
derivedSecureTextEntry = false
|
|
173
|
+
derivedKeyboardType = 'email-address'
|
|
174
|
+
derivedTextContentType = 'emailAddress'
|
|
175
|
+
derivedValidatorPattern = '.+@.+\\..+'
|
|
176
|
+
derivedValidatorMessage = validatorMessage || 'Informe um email válido'
|
|
177
|
+
break
|
|
178
|
+
case 'password':
|
|
179
|
+
derivedPlaceholder = 'Senha'
|
|
180
|
+
derivedSecureTextEntry = true
|
|
181
|
+
derivedKeyboardType = 'visible-password'
|
|
182
|
+
derivedTextContentType = 'password'
|
|
183
|
+
derivedValidatorPattern = validatorPattern
|
|
184
|
+
derivedValidatorMessage = validatorMessage || 'Informe uma senha válida'
|
|
185
|
+
break
|
|
186
|
+
case 'number':
|
|
187
|
+
derivedPlaceholder = 'Número'
|
|
188
|
+
derivedSecureTextEntry = false
|
|
189
|
+
derivedKeyboardType = 'numeric'
|
|
190
|
+
derivedTextContentType = 'none'
|
|
191
|
+
derivedValidatorPattern = validatorPattern || '^[0-9]+$'
|
|
192
|
+
derivedValidatorMessage = validatorMessage || 'Informe apenas números'
|
|
193
|
+
break
|
|
194
|
+
case 'phone':
|
|
195
|
+
derivedPlaceholder = 'Telefone'
|
|
196
|
+
derivedSecureTextEntry = false
|
|
197
|
+
derivedKeyboardType = 'phone-pad'
|
|
198
|
+
derivedTextContentType = 'telephoneNumber'
|
|
199
|
+
derivedValidatorPattern = validatorPattern || '^[0-9()\\s+-]{6,}$'
|
|
200
|
+
derivedValidatorMessage = validatorMessage || 'Informe um telefone válido'
|
|
201
|
+
break
|
|
202
|
+
case 'url':
|
|
203
|
+
derivedPlaceholder = 'URL'
|
|
204
|
+
derivedSecureTextEntry = false
|
|
205
|
+
derivedKeyboardType = 'url'
|
|
206
|
+
derivedTextContentType = 'URL'
|
|
207
|
+
derivedValidatorPattern = validatorPattern || '^(https?:\\/\\/).+'
|
|
208
|
+
derivedValidatorMessage = validatorMessage || 'Informe uma URL válida'
|
|
209
|
+
break
|
|
210
|
+
default:
|
|
211
|
+
derivedPlaceholder = derivedPlaceholder || 'Digite seu texto'
|
|
212
|
+
derivedSecureTextEntry = false
|
|
213
|
+
derivedKeyboardType = 'default'
|
|
214
|
+
derivedTextContentType = 'none'
|
|
215
|
+
derivedValidatorPattern = validatorPattern
|
|
216
|
+
derivedValidatorMessage = validatorMessage || 'Informe um valor válido'
|
|
217
|
+
break
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let patternValidator: ((value: string) => boolean) | undefined
|
|
221
|
+
|
|
222
|
+
if (derivedValidatorPattern && derivedValidatorPattern.trim().length > 0) {
|
|
223
|
+
try {
|
|
224
|
+
const regex = new RegExp(derivedValidatorPattern)
|
|
225
|
+
patternValidator = value => regex.test(value)
|
|
226
|
+
} catch {
|
|
227
|
+
patternValidator = undefined
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const leftIcon = leftIconName && leftIconName !== 'none' ? { name: leftIconName } : undefined
|
|
232
|
+
const rightIcon = rightIconName && rightIconName !== 'none' ? { name: rightIconName } : undefined
|
|
233
|
+
|
|
234
|
+
const finalValidator = patternValidator ?? inputArgs.validator
|
|
235
|
+
const finalValidatorMessage = patternValidator ? derivedValidatorMessage : inputArgs.validatorMessage
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<View style={styles.container}>
|
|
239
|
+
<StatefulInput
|
|
240
|
+
{...inputArgs}
|
|
241
|
+
initialValue={initialValue ?? ''}
|
|
242
|
+
keyboardType={derivedKeyboardType}
|
|
243
|
+
leftIcon={leftIcon}
|
|
244
|
+
locale={locale}
|
|
245
|
+
maskNumber={maskNumber}
|
|
246
|
+
placeholder={derivedPlaceholder}
|
|
247
|
+
rightIcon={rightIcon}
|
|
248
|
+
secureTextEntry={derivedSecureTextEntry}
|
|
249
|
+
textContentType={derivedTextContentType === 'none' ? undefined : derivedTextContentType}
|
|
250
|
+
type={inputType}
|
|
251
|
+
validator={finalValidator}
|
|
252
|
+
validatorMessage={finalValidatorMessage}
|
|
253
|
+
/>
|
|
254
|
+
</View>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export const WithError: Story = {
|
|
260
|
+
render: args => (
|
|
261
|
+
<View style={styles.container}>
|
|
262
|
+
<StatefulInput {...args} errorMessage='Campo obrigatório' initialValue='texto' inputError />
|
|
263
|
+
</View>
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export const Disabled: Story = {
|
|
268
|
+
render: args => (
|
|
269
|
+
<View style={styles.container}>
|
|
270
|
+
<StatefulInput {...args} disabled initialValue='texto desabilitado' />
|
|
271
|
+
</View>
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export const Readonly: Story = {
|
|
276
|
+
render: args => (
|
|
277
|
+
<View style={styles.container}>
|
|
278
|
+
<StatefulInput {...args} initialValue='valor somente leitura' readonly />
|
|
279
|
+
</View>
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export const WithLeftIcon: Story = {
|
|
284
|
+
render: args => (
|
|
285
|
+
<View style={styles.container}>
|
|
286
|
+
<StatefulInput {...args} initialValue='' leftIcon={{ name: 'search' }} placeholder='Buscar' />
|
|
287
|
+
</View>
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export const WithRightIcon: Story = {
|
|
292
|
+
render: args => (
|
|
293
|
+
<View style={styles.container}>
|
|
294
|
+
<StatefulInput {...args} initialValue='andre@wecareu.com' placeholder='Email' rightIcon={{ name: 'mail' }} />
|
|
295
|
+
</View>
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export const PasswordToggle: Story = {
|
|
300
|
+
render: args => {
|
|
301
|
+
const [secure, setSecure] = React.useState(true)
|
|
302
|
+
|
|
303
|
+
return (
|
|
304
|
+
<View style={styles.container}>
|
|
305
|
+
<StatefulInput
|
|
306
|
+
{...args}
|
|
307
|
+
initialValue=''
|
|
308
|
+
placeholder='Senha'
|
|
309
|
+
rightIcon={{
|
|
310
|
+
accessibilityLabel: secure ? 'Mostrar senha' : 'Ocultar senha',
|
|
311
|
+
name: secure ? 'eyeOff' : 'eyeOn',
|
|
312
|
+
onPress: () => setSecure(previous => !previous)
|
|
313
|
+
}}
|
|
314
|
+
secureTextEntry={secure}
|
|
315
|
+
/>
|
|
316
|
+
</View>
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export const WithMask: Story = {
|
|
322
|
+
render: args => {
|
|
323
|
+
const formatter = React.useCallback((raw: string) => {
|
|
324
|
+
const digits = raw.replace(/\D/g, '').slice(0, 11)
|
|
325
|
+
return digits
|
|
326
|
+
.replace(/(\d{3})(\d)/, '$1.$2')
|
|
327
|
+
.replace(/(\d{3})(\d)/, '$1.$2')
|
|
328
|
+
.replace(/(\d{3})(\d{1,2})$/, '$1-$2')
|
|
329
|
+
}, [])
|
|
330
|
+
|
|
331
|
+
const parser = React.useCallback((masked: string) => masked.replace(/\D/g, ''), [])
|
|
332
|
+
|
|
333
|
+
return (
|
|
334
|
+
<View style={styles.container}>
|
|
335
|
+
<StatefulInput {...args} formatter={formatter} initialValue='12345678901' placeholder='CPF' parser={parser} />
|
|
336
|
+
</View>
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export const FocusChain: Story = {
|
|
342
|
+
render: args => {
|
|
343
|
+
const secondInputRef = React.useRef<TextInput>(null)
|
|
344
|
+
const [first, setFirst] = React.useState('')
|
|
345
|
+
const [second, setSecond] = React.useState('')
|
|
346
|
+
const { onChangeText, ...rest } = args
|
|
347
|
+
|
|
348
|
+
return (
|
|
349
|
+
<View style={styles.column}>
|
|
350
|
+
<InputText
|
|
351
|
+
{...rest}
|
|
352
|
+
nextInputRef={secondInputRef}
|
|
353
|
+
onChangeText={value => {
|
|
354
|
+
setFirst(value)
|
|
355
|
+
onChangeText(value)
|
|
356
|
+
}}
|
|
357
|
+
placeholder='Primeiro campo'
|
|
358
|
+
value={first}
|
|
359
|
+
/>
|
|
360
|
+
<View style={styles.columnSpacing}>
|
|
361
|
+
<InputText
|
|
362
|
+
{...rest}
|
|
363
|
+
onChangeText={value => {
|
|
364
|
+
setSecond(value)
|
|
365
|
+
onChangeText(value)
|
|
366
|
+
}}
|
|
367
|
+
placeholder='Segundo campo'
|
|
368
|
+
ref={secondInputRef}
|
|
369
|
+
value={second}
|
|
370
|
+
/>
|
|
371
|
+
</View>
|
|
372
|
+
</View>
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const styles = StyleSheet.create({
|
|
378
|
+
column: {
|
|
379
|
+
width: '100%'
|
|
380
|
+
},
|
|
381
|
+
columnSpacing: {
|
|
382
|
+
marginTop: 16
|
|
383
|
+
},
|
|
384
|
+
container: {
|
|
385
|
+
width: 320
|
|
386
|
+
}
|
|
387
|
+
})
|
package/src/index.tsx
ADDED