@butternutbox/pawprint-native 0.0.1 → 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.
- package/.turbo/turbo-build.log +15 -15
- package/CHANGELOG.md +16 -0
- package/COMPONENT_GUIDELINES.md +111 -4
- package/dist/index.cjs +12370 -1455
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1110 -11
- package/dist/index.d.ts +1110 -11
- package/dist/index.js +12324 -1455
- package/dist/index.js.map +1 -1
- package/package.json +28 -9
- package/src/__mocks__/asset-stub.ts +1 -0
- package/src/__mocks__/emotion-native.tsx +18 -0
- package/src/__mocks__/react-native-gesture-handler.tsx +41 -0
- package/src/__mocks__/react-native-reanimated.tsx +79 -0
- package/src/__mocks__/react-native-safe-area-context.tsx +6 -0
- package/src/__mocks__/react-native-svg.tsx +27 -0
- package/src/__mocks__/react-native-worklets.tsx +11 -0
- package/src/__mocks__/react-native.tsx +338 -0
- package/src/__mocks__/rn-primitives/avatar.tsx +24 -0
- package/src/__mocks__/rn-primitives/checkbox.tsx +19 -0
- package/src/__mocks__/rn-primitives/select.tsx +116 -0
- package/src/__mocks__/rn-primitives/slider.tsx +40 -0
- package/src/__mocks__/rn-primitives/slot.tsx +30 -0
- package/src/__mocks__/rn-primitives/switch.tsx +24 -0
- package/src/__mocks__/rn-primitives/toggle.tsx +16 -0
- package/src/components/atoms/Avatar/Avatar.stories.tsx +57 -49
- package/src/components/atoms/Avatar/Avatar.test.tsx +269 -0
- package/src/components/atoms/Avatar/Avatar.tsx +68 -22
- package/src/components/atoms/Avatar/index.ts +1 -6
- package/src/components/atoms/Badge/Badge.stories.tsx +5 -29
- package/src/components/atoms/Badge/Badge.test.tsx +90 -0
- package/src/components/atoms/Button/Button.test.tsx +123 -0
- package/src/components/atoms/Button/Button.tsx +1 -1
- package/src/components/atoms/CarouselControls/CarouselControls.stories.tsx +217 -0
- package/src/components/atoms/CarouselControls/CarouselControls.tsx +127 -0
- package/src/components/atoms/CarouselControls/index.ts +2 -0
- package/src/components/atoms/Hint/Hint.test.tsx +36 -0
- package/src/components/atoms/Icon/Icon.test.tsx +98 -0
- package/src/components/atoms/Icon/Icon.tsx +5 -1
- package/src/components/atoms/IconButton/IconButton.test.tsx +101 -0
- package/src/components/atoms/Illustration/Illustration.test.tsx +55 -0
- package/src/components/atoms/Input/Input.stories.tsx +129 -86
- package/src/components/atoms/Input/Input.test.tsx +306 -0
- package/src/components/atoms/Input/Input.tsx +9 -1
- package/src/components/atoms/Input/InputField.tsx +226 -74
- package/src/components/atoms/Link/Link.test.tsx +89 -0
- package/src/components/atoms/Logo/Logo.registry.ts +30 -5
- package/src/components/atoms/Logo/Logo.stories.tsx +108 -0
- package/src/components/atoms/Logo/Logo.test.tsx +56 -0
- package/src/components/atoms/Logo/assets/BCorp.tsx +113 -0
- package/src/components/atoms/Logo/assets/ButternutFavicon.tsx +33 -0
- package/src/components/atoms/Logo/assets/ButternutPrimary.tsx +294 -0
- package/src/components/atoms/Logo/assets/ButternutTabbedBottom.tsx +294 -0
- package/src/components/atoms/Logo/assets/ButternutTabbedTop.tsx +294 -0
- package/src/components/atoms/Logo/assets/ButternutWordmark.tsx +274 -0
- package/src/components/atoms/Logo/assets/PsiBufetFavicon.tsx +45 -0
- package/src/components/atoms/Logo/assets/PsiBufetPrimary.tsx +218 -0
- package/src/components/atoms/Logo/assets/PsiBufetTabbedBottom.tsx +218 -0
- package/src/components/atoms/Logo/assets/PsiBufetTabbedTop.tsx +218 -0
- package/src/components/atoms/Logo/assets/PsiBufetWordmark.tsx +195 -0
- package/src/components/atoms/Logo/assets/index.ts +11 -0
- package/src/components/atoms/NumberInput/NumberInput.stories.tsx +183 -0
- package/src/components/atoms/NumberInput/NumberInput.test.tsx +261 -0
- package/src/components/atoms/NumberInput/NumberInput.tsx +129 -0
- package/src/components/atoms/NumberInput/NumberInputField.tsx +77 -0
- package/src/components/atoms/NumberInput/index.ts +4 -0
- package/src/components/atoms/Spinner/Spinner.test.tsx +46 -0
- package/src/components/atoms/Spinner/Spinner.tsx +14 -5
- package/src/components/atoms/Switch/Switch.test.tsx +92 -0
- package/src/components/atoms/Switch/Switch.tsx +16 -13
- package/src/components/atoms/Tag/Tag.test.tsx +70 -0
- package/src/components/atoms/TextArea/TextArea.stories.tsx +303 -0
- package/src/components/atoms/TextArea/TextArea.test.tsx +416 -0
- package/src/components/atoms/TextArea/TextArea.tsx +171 -0
- package/src/components/atoms/TextArea/TextAreaField.tsx +304 -0
- package/src/components/atoms/TextArea/TextAreaLabel.tsx +103 -0
- package/src/components/atoms/TextArea/index.ts +6 -0
- package/src/components/atoms/Typography/Typography.test.tsx +94 -0
- package/src/components/atoms/index.ts +3 -0
- package/src/components/molecules/Accordion/Accordion.stories.tsx +177 -0
- package/src/components/molecules/Accordion/Accordion.test.tsx +185 -0
- package/src/components/molecules/Accordion/Accordion.tsx +284 -0
- package/src/components/molecules/Accordion/index.ts +6 -0
- package/src/components/molecules/Animated/Animated.stories.tsx +254 -0
- package/src/components/molecules/Animated/Animated.tsx +283 -0
- package/src/components/molecules/Animated/index.ts +10 -0
- package/src/components/molecules/ButtonDock/ButtonDock.test.tsx +83 -0
- package/src/components/molecules/ButtonGroup/ButtonGroup.stories.tsx +8 -14
- package/src/components/molecules/ButtonGroup/ButtonGroup.test.tsx +73 -0
- package/src/components/molecules/ButtonGroup/ButtonGroup.tsx +25 -3
- package/src/components/molecules/Checkbox/Checkbox.stories.tsx +72 -0
- package/src/components/molecules/Checkbox/Checkbox.test.tsx +117 -0
- package/src/components/molecules/Checkbox/Checkbox.tsx +101 -95
- package/src/components/molecules/CopyField/CopyField.stories.tsx +313 -0
- package/src/components/molecules/CopyField/CopyField.test.tsx +431 -0
- package/src/components/molecules/CopyField/CopyField.tsx +156 -0
- package/src/components/molecules/CopyField/CopyFieldInput.tsx +127 -0
- package/src/components/molecules/CopyField/hooks/index.ts +1 -0
- package/src/components/molecules/CopyField/hooks/useCopyField.ts +25 -0
- package/src/components/molecules/CopyField/index.ts +4 -0
- package/src/components/molecules/DatePicker/DatePicker.stories.tsx +298 -0
- package/src/components/molecules/DatePicker/DatePicker.test.tsx +201 -0
- package/src/components/molecules/DatePicker/DatePicker.tsx +590 -0
- package/src/components/molecules/DatePicker/index.ts +2 -0
- package/src/components/molecules/Drawer/Drawer.stories.tsx +285 -0
- package/src/components/molecules/Drawer/Drawer.test.tsx +180 -0
- package/src/components/molecules/Drawer/Drawer.tsx +187 -0
- package/src/components/molecules/Drawer/DrawerBody.tsx +80 -0
- package/src/components/molecules/Drawer/DrawerClose.tsx +76 -0
- package/src/components/molecules/Drawer/DrawerContent.tsx +339 -0
- package/src/components/molecules/Drawer/DrawerContext.ts +19 -0
- package/src/components/molecules/Drawer/DrawerDescription.tsx +31 -0
- package/src/components/molecules/Drawer/DrawerDragContext.ts +11 -0
- package/src/components/molecules/Drawer/DrawerFooter.tsx +49 -0
- package/src/components/molecules/Drawer/DrawerFooterContext.ts +6 -0
- package/src/components/molecules/Drawer/DrawerGrabber.tsx +62 -0
- package/src/components/molecules/Drawer/DrawerHeader.tsx +244 -0
- package/src/components/molecules/Drawer/DrawerHeaderContext.ts +13 -0
- package/src/components/molecules/Drawer/DrawerOverlay.tsx +53 -0
- package/src/components/molecules/Drawer/DrawerTitle.tsx +32 -0
- package/src/components/molecules/Drawer/index.ts +12 -0
- package/src/components/molecules/FilterTab/FilterTab.stories.tsx +210 -0
- package/src/components/molecules/FilterTab/FilterTab.tsx +310 -0
- package/src/components/molecules/FilterTab/index.ts +2 -0
- package/src/components/molecules/MessageCard/MessageCard.stories.tsx +169 -0
- package/src/components/molecules/MessageCard/MessageCard.tsx +362 -0
- package/src/components/molecules/MessageCard/index.ts +10 -0
- package/src/components/molecules/Notification/Notification.stories.tsx +219 -0
- package/src/components/molecules/Notification/Notification.tsx +426 -0
- package/src/components/molecules/Notification/index.ts +2 -0
- package/src/components/molecules/NumberField/NumberField.stories.tsx +231 -0
- package/src/components/molecules/NumberField/NumberField.tsx +186 -0
- package/src/components/molecules/NumberField/NumberFieldInput.tsx +287 -0
- package/src/components/molecules/NumberField/index.ts +2 -0
- package/src/components/molecules/PasswordField/PasswordField.stories.tsx +362 -0
- package/src/components/molecules/PasswordField/PasswordField.test.tsx +369 -0
- package/src/components/molecules/PasswordField/PasswordField.tsx +194 -0
- package/src/components/molecules/PasswordField/PasswordFieldError.tsx +52 -0
- package/src/components/molecules/PasswordField/PasswordFieldInput.tsx +73 -0
- package/src/components/molecules/PasswordField/PasswordFieldRequirements.tsx +92 -0
- package/src/components/molecules/PasswordField/hooks/index.ts +2 -0
- package/src/components/molecules/PasswordField/hooks/usePasswordField.ts +113 -0
- package/src/components/molecules/PasswordField/index.ts +10 -0
- package/src/components/molecules/PictureSelector/PictureSelector.stories.tsx +243 -0
- package/src/components/molecules/PictureSelector/PictureSelector.tsx +313 -0
- package/src/components/molecules/PictureSelector/index.ts +5 -0
- package/src/components/molecules/Progress/Progress.stories.tsx +145 -0
- package/src/components/molecules/Progress/Progress.tsx +184 -0
- package/src/components/molecules/Progress/index.ts +2 -0
- package/src/components/molecules/Radio/Radio.test.tsx +104 -0
- package/src/components/molecules/Radio/Radio.tsx +1 -2
- package/src/components/molecules/SearchField/SearchField.stories.tsx +242 -0
- package/src/components/molecules/SearchField/SearchField.test.tsx +318 -0
- package/src/components/molecules/SearchField/SearchField.tsx +143 -0
- package/src/components/molecules/SearchField/SearchFieldInput.tsx +63 -0
- package/src/components/molecules/SearchField/hooks/index.ts +1 -0
- package/src/components/molecules/SearchField/hooks/useSearchField.ts +56 -0
- package/src/components/molecules/SearchField/index.ts +4 -0
- package/src/components/molecules/SegmentedControl/SegmentedControl.stories.tsx +31 -8
- package/src/components/molecules/SegmentedControl/SegmentedControl.test.tsx +141 -0
- package/src/components/molecules/SegmentedControl/SegmentedControl.tsx +237 -23
- package/src/components/molecules/SelectField/SelectField.stories.tsx +320 -0
- package/src/components/molecules/SelectField/SelectField.test.tsx +254 -0
- package/src/components/molecules/SelectField/SelectField.tsx +236 -0
- package/src/components/molecules/SelectField/SelectFieldContent.tsx +85 -0
- package/src/components/molecules/SelectField/SelectFieldItem.tsx +133 -0
- package/src/components/molecules/SelectField/SelectFieldTrigger.tsx +170 -0
- package/src/components/molecules/SelectField/SelectFieldValue.tsx +31 -0
- package/src/components/molecules/SelectField/hooks/index.ts +2 -0
- package/src/components/molecules/SelectField/hooks/useSelectField.ts +84 -0
- package/src/components/molecules/SelectField/index.ts +10 -0
- package/src/components/molecules/Slider/Slider.test.tsx +102 -0
- package/src/components/molecules/Slider/Slider.tsx +293 -180
- package/src/components/molecules/Tooltip/Tooltip.stories.tsx +168 -0
- package/src/components/molecules/Tooltip/Tooltip.tsx +326 -0
- package/src/components/molecules/Tooltip/index.ts +2 -0
- package/src/components/molecules/index.ts +15 -0
- package/src/test-utils.tsx +20 -0
- package/tsconfig.json +1 -1
- package/tsup.config.ts +16 -2
- package/vitest.config.ts +114 -0
- package/vitest.setup.ts +16 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react"
|
|
2
2
|
import { View, StyleSheet } from "react-native"
|
|
3
3
|
import { Input } from "./Input"
|
|
4
4
|
import type { InputProps } from "./Input"
|
|
5
|
-
import {
|
|
5
|
+
import type { InputState } from "./InputField"
|
|
6
|
+
import { Icon } from "../Icon"
|
|
7
|
+
import { Search, Info } from "@butternutbox/pawprint-icons/core"
|
|
6
8
|
|
|
7
9
|
export default {
|
|
8
10
|
title: "Atoms/Input",
|
|
@@ -12,10 +14,6 @@ export default {
|
|
|
12
14
|
control: { type: "text" },
|
|
13
15
|
description: "Label text"
|
|
14
16
|
},
|
|
15
|
-
placeholder: {
|
|
16
|
-
control: { type: "text" },
|
|
17
|
-
description: "Placeholder text"
|
|
18
|
-
},
|
|
19
17
|
description: {
|
|
20
18
|
control: { type: "text" },
|
|
21
19
|
description: "Help text below input"
|
|
@@ -24,107 +22,156 @@ export default {
|
|
|
24
22
|
control: { type: "text" },
|
|
25
23
|
description: "Error message"
|
|
26
24
|
},
|
|
27
|
-
state: {
|
|
28
|
-
control: { type: "select" },
|
|
29
|
-
options: ["default", "error", "success"],
|
|
30
|
-
description: "Visual state of the input"
|
|
31
|
-
},
|
|
32
25
|
optionalText: {
|
|
33
26
|
control: { type: "text" },
|
|
34
|
-
description: "Optional
|
|
27
|
+
description: "Optional text to display next to label"
|
|
35
28
|
},
|
|
36
29
|
disabled: {
|
|
37
30
|
control: { type: "boolean" },
|
|
38
|
-
description: "
|
|
31
|
+
description: "Disables the input"
|
|
39
32
|
}
|
|
40
33
|
}
|
|
41
34
|
}
|
|
42
35
|
|
|
43
36
|
export const Default = (args: InputProps) => <Input {...args} />
|
|
44
37
|
Default.args = {
|
|
45
|
-
label: "
|
|
46
|
-
placeholder: "
|
|
47
|
-
description: "
|
|
48
|
-
state: "default"
|
|
38
|
+
label: "Label",
|
|
39
|
+
placeholder: "Placeholder",
|
|
40
|
+
description: "Help text"
|
|
49
41
|
}
|
|
50
42
|
|
|
43
|
+
export const WithIcons = () => (
|
|
44
|
+
<View style={styles.column}>
|
|
45
|
+
<Input
|
|
46
|
+
label="Leading Icon"
|
|
47
|
+
placeholder="Placeholder"
|
|
48
|
+
leadingIcon={<Icon icon={Search} size="md" />}
|
|
49
|
+
description="Help text"
|
|
50
|
+
/>
|
|
51
|
+
<Input
|
|
52
|
+
label="Trailing Icon"
|
|
53
|
+
placeholder="Placeholder"
|
|
54
|
+
trailingIcon={<Icon icon={Search} size="md" />}
|
|
55
|
+
description="Help text"
|
|
56
|
+
/>
|
|
57
|
+
<Input
|
|
58
|
+
label="Both Icons"
|
|
59
|
+
placeholder="Placeholder"
|
|
60
|
+
leadingIcon={<Icon icon={Search} size="md" />}
|
|
61
|
+
trailingIcon={<Icon icon={Info} size="md" />}
|
|
62
|
+
description="Help text"
|
|
63
|
+
/>
|
|
64
|
+
</View>
|
|
65
|
+
)
|
|
66
|
+
|
|
51
67
|
export const States = () => (
|
|
52
68
|
<View style={styles.column}>
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
69
|
+
<Input
|
|
70
|
+
label="Default State"
|
|
71
|
+
placeholder="Enter text"
|
|
72
|
+
description="Normal input state"
|
|
73
|
+
/>
|
|
74
|
+
<Input
|
|
75
|
+
label="Error State"
|
|
76
|
+
placeholder="Enter text"
|
|
77
|
+
state="error"
|
|
78
|
+
description="Manually set to error state"
|
|
79
|
+
/>
|
|
80
|
+
<Input
|
|
81
|
+
label="Error with Custom Message"
|
|
82
|
+
placeholder="Enter text"
|
|
83
|
+
state="error"
|
|
84
|
+
description="Manually set to error state"
|
|
85
|
+
error="Custom error message"
|
|
86
|
+
/>
|
|
87
|
+
<Input
|
|
88
|
+
label="Success State"
|
|
89
|
+
placeholder="Enter text"
|
|
90
|
+
state="success"
|
|
91
|
+
description="Manually set to success state"
|
|
92
|
+
/>
|
|
93
|
+
<Input
|
|
94
|
+
label="Disabled"
|
|
95
|
+
placeholder="Enter text"
|
|
96
|
+
description="Help text"
|
|
97
|
+
editable={false}
|
|
98
|
+
/>
|
|
99
|
+
</View>
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
export const KeyboardTypes = () => (
|
|
103
|
+
<View style={styles.column}>
|
|
104
|
+
<Input label="Default" keyboardType="default" placeholder="Enter text" />
|
|
105
|
+
<Input
|
|
106
|
+
label="Email"
|
|
107
|
+
keyboardType="email-address"
|
|
108
|
+
placeholder="you@butternutbox.com"
|
|
109
|
+
/>
|
|
110
|
+
<Input label="Numeric" keyboardType="numeric" placeholder="Enter number" />
|
|
111
|
+
<Input
|
|
112
|
+
label="Phone"
|
|
113
|
+
keyboardType="phone-pad"
|
|
114
|
+
placeholder="+44 20 1234 5678"
|
|
115
|
+
/>
|
|
116
|
+
<Input
|
|
117
|
+
label="URL"
|
|
118
|
+
keyboardType="url"
|
|
119
|
+
placeholder="https://butternutbox.com"
|
|
120
|
+
/>
|
|
121
|
+
</View>
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
export const CustomStateValidationWithSuccess = () => {
|
|
125
|
+
const [username, setUsername] = React.useState("")
|
|
126
|
+
const [usernameState, setUsernameState] =
|
|
127
|
+
React.useState<InputState>("default")
|
|
128
|
+
|
|
129
|
+
const validateUsername = (value: string) => {
|
|
130
|
+
if (!value) {
|
|
131
|
+
setUsernameState("default")
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const hasMinLength = value.length >= 3
|
|
136
|
+
const hasMaxLength = value.length <= 20
|
|
137
|
+
const isValidFormat = /^[a-z0-9_]+$/.test(value)
|
|
138
|
+
|
|
139
|
+
setUsernameState(
|
|
140
|
+
hasMinLength && hasMaxLength && isValidFormat ? "success" : "error"
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const handleChange = (newValue: string) => {
|
|
145
|
+
setUsername(newValue)
|
|
146
|
+
validateUsername(newValue)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<View style={styles.column}>
|
|
101
151
|
<Input
|
|
102
|
-
label="
|
|
103
|
-
placeholder="
|
|
104
|
-
|
|
105
|
-
|
|
152
|
+
label="Username"
|
|
153
|
+
placeholder="Enter username"
|
|
154
|
+
value={username}
|
|
155
|
+
onValueChange={handleChange}
|
|
156
|
+
state={usernameState}
|
|
157
|
+
description="3-20 chars, lowercase letters, numbers, and underscores"
|
|
158
|
+
error="Custom validation error"
|
|
106
159
|
/>
|
|
107
160
|
</View>
|
|
108
|
-
|
|
109
|
-
|
|
161
|
+
)
|
|
162
|
+
}
|
|
110
163
|
|
|
111
164
|
export const Controlled = () => {
|
|
112
|
-
const [value, setValue] = useState("")
|
|
113
|
-
const hasError = value.length > 0 && !value.includes("@")
|
|
165
|
+
const [value, setValue] = React.useState("")
|
|
114
166
|
|
|
115
167
|
return (
|
|
116
168
|
<View style={styles.column}>
|
|
117
|
-
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
118
|
-
Controlled: {value || "(empty)"}
|
|
119
|
-
</Typography>
|
|
120
169
|
<Input
|
|
121
|
-
label="
|
|
122
|
-
placeholder="you@butternutbox.com"
|
|
170
|
+
label="Controlled Input"
|
|
123
171
|
value={value}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
description="Type to see validation"
|
|
172
|
+
onValueChange={(newValue) => setValue(newValue)}
|
|
173
|
+
placeholder="Type something..."
|
|
174
|
+
description={`You typed: ${value.length} characters`}
|
|
128
175
|
/>
|
|
129
176
|
</View>
|
|
130
177
|
)
|
|
@@ -134,9 +181,5 @@ const styles = StyleSheet.create({
|
|
|
134
181
|
column: {
|
|
135
182
|
flexDirection: "column",
|
|
136
183
|
gap: 24
|
|
137
|
-
},
|
|
138
|
-
section: {
|
|
139
|
-
flexDirection: "column",
|
|
140
|
-
gap: 8
|
|
141
184
|
}
|
|
142
185
|
})
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { TextInput } from "react-native"
|
|
3
|
+
import { screen } from "@testing-library/react"
|
|
4
|
+
import userEvent from "@testing-library/user-event"
|
|
5
|
+
import { describe, it, expect, vi } from "vitest"
|
|
6
|
+
import { renderWithTheme } from "../../../test-utils"
|
|
7
|
+
import { Input } from "./Input"
|
|
8
|
+
|
|
9
|
+
describe("Input", () => {
|
|
10
|
+
describe("when using simple props API", () => {
|
|
11
|
+
it("renders label when provided", () => {
|
|
12
|
+
renderWithTheme(<Input label="Email" placeholder="Enter email" />)
|
|
13
|
+
expect(screen.getByText("Email")).toBeInTheDocument()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it("renders description when provided", () => {
|
|
17
|
+
renderWithTheme(
|
|
18
|
+
<Input
|
|
19
|
+
label="Email"
|
|
20
|
+
placeholder="Enter email"
|
|
21
|
+
description="We'll never share your email"
|
|
22
|
+
/>
|
|
23
|
+
)
|
|
24
|
+
expect(
|
|
25
|
+
screen.getByText("We'll never share your email")
|
|
26
|
+
).toBeInTheDocument()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("renders error message when error prop and state='error' are provided", () => {
|
|
30
|
+
renderWithTheme(
|
|
31
|
+
<Input
|
|
32
|
+
label="Email"
|
|
33
|
+
placeholder="Enter email"
|
|
34
|
+
state="error"
|
|
35
|
+
error="Invalid email address"
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
expect(screen.getByText("Invalid email address")).toBeInTheDocument()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("does not render error message when error prop is provided without state='error'", () => {
|
|
42
|
+
renderWithTheme(
|
|
43
|
+
<Input
|
|
44
|
+
label="Email"
|
|
45
|
+
placeholder="Enter email"
|
|
46
|
+
error="Invalid email address"
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
expect(
|
|
50
|
+
screen.queryByText("Invalid email address")
|
|
51
|
+
).not.toBeInTheDocument()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it("shows optionalText when provided", () => {
|
|
55
|
+
renderWithTheme(
|
|
56
|
+
<Input
|
|
57
|
+
label="Email"
|
|
58
|
+
placeholder="Enter email"
|
|
59
|
+
optionalText="(optional)"
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
expect(screen.getByText("(optional)")).toBeInTheDocument()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it("renders input with placeholder", () => {
|
|
66
|
+
renderWithTheme(<Input label="Email" placeholder="Enter email" />)
|
|
67
|
+
expect(screen.getByPlaceholderText("Enter email")).toBeInTheDocument()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it("accepts user input", async () => {
|
|
71
|
+
const user = userEvent.setup()
|
|
72
|
+
renderWithTheme(<Input label="Email" placeholder="Enter email" />)
|
|
73
|
+
|
|
74
|
+
const input = screen.getByPlaceholderText("Enter email")
|
|
75
|
+
await user.type(input, "test@example.com")
|
|
76
|
+
expect(input).toHaveValue("test@example.com")
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe("when using controlled mode", () => {
|
|
81
|
+
it("updates value when parent updates", async () => {
|
|
82
|
+
const user = userEvent.setup()
|
|
83
|
+
|
|
84
|
+
function ControlledInput() {
|
|
85
|
+
const [value, setValue] = React.useState("")
|
|
86
|
+
return (
|
|
87
|
+
<Input
|
|
88
|
+
label="Email"
|
|
89
|
+
value={value}
|
|
90
|
+
onValueChange={(newValue) => setValue(newValue)}
|
|
91
|
+
placeholder="Enter email"
|
|
92
|
+
/>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
renderWithTheme(<ControlledInput />)
|
|
97
|
+
|
|
98
|
+
const input = screen.getByPlaceholderText("Enter email")
|
|
99
|
+
await user.type(input, "test@example.com")
|
|
100
|
+
expect(input).toHaveValue("test@example.com")
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it("calls onValueChange when value changes", async () => {
|
|
104
|
+
const user = userEvent.setup()
|
|
105
|
+
const onValueChange = vi.fn()
|
|
106
|
+
|
|
107
|
+
renderWithTheme(
|
|
108
|
+
<Input
|
|
109
|
+
label="Email"
|
|
110
|
+
value=""
|
|
111
|
+
onValueChange={onValueChange}
|
|
112
|
+
placeholder="Enter email"
|
|
113
|
+
/>
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
const input = screen.getByPlaceholderText("Enter email")
|
|
117
|
+
await user.type(input, "a")
|
|
118
|
+
expect(onValueChange).toHaveBeenCalledWith("a")
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe("when disabled", () => {
|
|
123
|
+
it("disables the input", () => {
|
|
124
|
+
renderWithTheme(
|
|
125
|
+
<Input label="Email" placeholder="Enter email" editable={false} />
|
|
126
|
+
)
|
|
127
|
+
expect(screen.getByPlaceholderText("Enter email")).toBeDisabled()
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it("does not accept user input when disabled", async () => {
|
|
131
|
+
const user = userEvent.setup()
|
|
132
|
+
renderWithTheme(
|
|
133
|
+
<Input label="Email" placeholder="Enter email" editable={false} />
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
const input = screen.getByPlaceholderText("Enter email")
|
|
137
|
+
await user.type(input, "test")
|
|
138
|
+
expect(input).toHaveValue("")
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
describe("when using compound component API", () => {
|
|
143
|
+
it("renders compound components", () => {
|
|
144
|
+
renderWithTheme(
|
|
145
|
+
<Input.Root>
|
|
146
|
+
<Input.Label>Email</Input.Label>
|
|
147
|
+
<Input.Field placeholder="Enter email" />
|
|
148
|
+
<Input.Description>Help text</Input.Description>
|
|
149
|
+
</Input.Root>
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
expect(screen.getByText("Email")).toBeInTheDocument()
|
|
153
|
+
expect(screen.getByPlaceholderText("Enter email")).toBeInTheDocument()
|
|
154
|
+
expect(screen.getByText("Help text")).toBeInTheDocument()
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it("renders error in compound mode", () => {
|
|
158
|
+
renderWithTheme(
|
|
159
|
+
<Input.Root>
|
|
160
|
+
<Input.Label state="error">Email</Input.Label>
|
|
161
|
+
<Input.Field placeholder="Enter email" state="error" />
|
|
162
|
+
<Input.Error>Invalid email</Input.Error>
|
|
163
|
+
</Input.Root>
|
|
164
|
+
)
|
|
165
|
+
expect(screen.getByText("Invalid email")).toBeInTheDocument()
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe("keyboard types", () => {
|
|
170
|
+
it("renders with email keyboard type", () => {
|
|
171
|
+
renderWithTheme(
|
|
172
|
+
<Input
|
|
173
|
+
label="Email"
|
|
174
|
+
keyboardType="email-address"
|
|
175
|
+
placeholder="Enter email"
|
|
176
|
+
/>
|
|
177
|
+
)
|
|
178
|
+
expect(screen.getByPlaceholderText("Enter email")).toBeInTheDocument()
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it("renders with numeric keyboard type", () => {
|
|
182
|
+
renderWithTheme(
|
|
183
|
+
<Input
|
|
184
|
+
label="Amount"
|
|
185
|
+
keyboardType="numeric"
|
|
186
|
+
placeholder="Enter amount"
|
|
187
|
+
/>
|
|
188
|
+
)
|
|
189
|
+
expect(screen.getByPlaceholderText("Enter amount")).toBeInTheDocument()
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it("renders with phone keyboard type", () => {
|
|
193
|
+
renderWithTheme(
|
|
194
|
+
<Input
|
|
195
|
+
label="Phone"
|
|
196
|
+
keyboardType="phone-pad"
|
|
197
|
+
placeholder="Enter phone"
|
|
198
|
+
/>
|
|
199
|
+
)
|
|
200
|
+
expect(screen.getByPlaceholderText("Enter phone")).toBeInTheDocument()
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
describe("validation states", () => {
|
|
205
|
+
it.each(["default", "error", "success"] as const)(
|
|
206
|
+
"renders %s state without errors",
|
|
207
|
+
(state) => {
|
|
208
|
+
renderWithTheme(
|
|
209
|
+
<Input label="Field" state={state} placeholder="Enter value" />
|
|
210
|
+
)
|
|
211
|
+
expect(screen.getByPlaceholderText("Enter value")).toBeInTheDocument()
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
it("shows error message with error state", () => {
|
|
216
|
+
renderWithTheme(
|
|
217
|
+
<Input
|
|
218
|
+
label="Email"
|
|
219
|
+
placeholder="Enter email"
|
|
220
|
+
state="error"
|
|
221
|
+
error="Invalid email address"
|
|
222
|
+
/>
|
|
223
|
+
)
|
|
224
|
+
expect(screen.getByText("Invalid email address")).toBeInTheDocument()
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
describe("when rendering icons", () => {
|
|
229
|
+
it("renders leading icon", () => {
|
|
230
|
+
renderWithTheme(
|
|
231
|
+
<Input.Root>
|
|
232
|
+
<Input.Field
|
|
233
|
+
placeholder="Search"
|
|
234
|
+
leadingIcon={<span data-testid="leading-icon">icon</span>}
|
|
235
|
+
/>
|
|
236
|
+
</Input.Root>
|
|
237
|
+
)
|
|
238
|
+
expect(screen.getByTestId("leading-icon")).toBeInTheDocument()
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it("renders trailing icon", () => {
|
|
242
|
+
renderWithTheme(
|
|
243
|
+
<Input.Root>
|
|
244
|
+
<Input.Field
|
|
245
|
+
placeholder="Search"
|
|
246
|
+
trailingIcon={<span data-testid="trailing-icon">icon</span>}
|
|
247
|
+
/>
|
|
248
|
+
</Input.Root>
|
|
249
|
+
)
|
|
250
|
+
expect(screen.getByTestId("trailing-icon")).toBeInTheDocument()
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it("renders action icon", () => {
|
|
254
|
+
renderWithTheme(
|
|
255
|
+
<Input.Root>
|
|
256
|
+
<Input.Field
|
|
257
|
+
placeholder="Search"
|
|
258
|
+
actionIcon={<span data-testid="action-icon">X</span>}
|
|
259
|
+
/>
|
|
260
|
+
</Input.Root>
|
|
261
|
+
)
|
|
262
|
+
expect(screen.getByTestId("action-icon")).toBeInTheDocument()
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it("hides state icons when hideStateIcons is true", () => {
|
|
266
|
+
const { container } = renderWithTheme(
|
|
267
|
+
<Input.Root>
|
|
268
|
+
<Input.Field placeholder="Email" state="error" hideStateIcons />
|
|
269
|
+
</Input.Root>
|
|
270
|
+
)
|
|
271
|
+
expect(container.querySelectorAll("svg")).toHaveLength(0)
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
describe("compound component API with states", () => {
|
|
276
|
+
it("renders error state in compound mode", () => {
|
|
277
|
+
renderWithTheme(
|
|
278
|
+
<Input.Root>
|
|
279
|
+
<Input.Label state="error">Email</Input.Label>
|
|
280
|
+
<Input.Field placeholder="Enter email" state="error" />
|
|
281
|
+
<Input.Error>Invalid email</Input.Error>
|
|
282
|
+
</Input.Root>
|
|
283
|
+
)
|
|
284
|
+
expect(screen.getByText("Invalid email")).toBeInTheDocument()
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it("renders success state in compound mode", () => {
|
|
288
|
+
renderWithTheme(
|
|
289
|
+
<Input.Root>
|
|
290
|
+
<Input.Label state="success">Email</Input.Label>
|
|
291
|
+
<Input.Field placeholder="Enter email" state="success" />
|
|
292
|
+
<Input.Description state="success">Email verified</Input.Description>
|
|
293
|
+
</Input.Root>
|
|
294
|
+
)
|
|
295
|
+
expect(screen.getByText("Email verified")).toBeInTheDocument()
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
describe("when using with ref", () => {
|
|
300
|
+
it("forwards ref to input element", () => {
|
|
301
|
+
const ref = React.createRef<TextInput>()
|
|
302
|
+
renderWithTheme(<Input ref={ref} label="Test" />)
|
|
303
|
+
expect(ref.current).toBeTruthy()
|
|
304
|
+
})
|
|
305
|
+
})
|
|
306
|
+
})
|
|
@@ -12,6 +12,7 @@ type InputOwnProps = {
|
|
|
12
12
|
error?: string
|
|
13
13
|
state?: InputState
|
|
14
14
|
optionalText?: string
|
|
15
|
+
onValueChange?: (value: string) => void
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export type InputProps = InputOwnProps &
|
|
@@ -41,6 +42,8 @@ const StyledRoot = styled(View)(({ theme }) => {
|
|
|
41
42
|
* error="Invalid email address"
|
|
42
43
|
* state="error"
|
|
43
44
|
* optionalText="(optional)"
|
|
45
|
+
* leadingIcon={<Icon icon={Search} size="md" />}
|
|
46
|
+
* onValueChange={(value) => setValue(value)}
|
|
44
47
|
* />
|
|
45
48
|
*
|
|
46
49
|
* **Compound Component API:**
|
|
@@ -60,6 +63,7 @@ const InputRoot = React.forwardRef<View, InputProps>(
|
|
|
60
63
|
error,
|
|
61
64
|
state = "default",
|
|
62
65
|
optionalText,
|
|
66
|
+
onValueChange,
|
|
63
67
|
children,
|
|
64
68
|
// InputField props
|
|
65
69
|
...inputFieldProps
|
|
@@ -79,7 +83,11 @@ const InputRoot = React.forwardRef<View, InputProps>(
|
|
|
79
83
|
{label}
|
|
80
84
|
</InputLabel>
|
|
81
85
|
)}
|
|
82
|
-
<InputField
|
|
86
|
+
<InputField
|
|
87
|
+
state={state}
|
|
88
|
+
onChangeText={onValueChange}
|
|
89
|
+
{...inputFieldProps}
|
|
90
|
+
/>
|
|
83
91
|
{description && (
|
|
84
92
|
<InputDescription state={state}>{description}</InputDescription>
|
|
85
93
|
)}
|