@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
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import React, { useState } from "react"
|
|
2
|
+
import { View, StyleSheet } from "react-native"
|
|
3
|
+
import { PasswordField } from "./PasswordField"
|
|
4
|
+
import { Typography } from "../../atoms/Typography"
|
|
5
|
+
import type { PasswordRequirement } from "./PasswordFieldRequirements"
|
|
6
|
+
import { usePasswordField } from "./hooks/usePasswordField"
|
|
7
|
+
import type { InputState } from "../../atoms/Input/InputField"
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
title: "Molecules/PasswordField",
|
|
11
|
+
component: PasswordField,
|
|
12
|
+
parameters: {
|
|
13
|
+
docs: {
|
|
14
|
+
description: {
|
|
15
|
+
component:
|
|
16
|
+
"Password field component with visibility toggle and optional requirements list. Supports both simple props API and compound component API."
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
argTypes: {
|
|
21
|
+
label: {
|
|
22
|
+
control: { type: "text" },
|
|
23
|
+
description: "Label text"
|
|
24
|
+
},
|
|
25
|
+
placeholder: {
|
|
26
|
+
control: { type: "text" },
|
|
27
|
+
description: "Placeholder text"
|
|
28
|
+
},
|
|
29
|
+
description: {
|
|
30
|
+
control: { type: "text" },
|
|
31
|
+
description: "Help text below input"
|
|
32
|
+
},
|
|
33
|
+
error: {
|
|
34
|
+
control: { type: "text" },
|
|
35
|
+
description: "Error message"
|
|
36
|
+
},
|
|
37
|
+
state: {
|
|
38
|
+
control: { type: "select" },
|
|
39
|
+
options: ["default", "error", "success"],
|
|
40
|
+
description: "Visual state of the input"
|
|
41
|
+
},
|
|
42
|
+
optionalText: {
|
|
43
|
+
control: { type: "text" },
|
|
44
|
+
description: "Optional text to display next to label"
|
|
45
|
+
},
|
|
46
|
+
showRequirements: {
|
|
47
|
+
control: { type: "boolean" },
|
|
48
|
+
description: "Show password requirements list"
|
|
49
|
+
},
|
|
50
|
+
editable: {
|
|
51
|
+
control: { type: "boolean" },
|
|
52
|
+
description: "Controls whether the input is editable"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const Default = () => {
|
|
58
|
+
const passwordProps = usePasswordField({
|
|
59
|
+
validationRules: [
|
|
60
|
+
{ test: (v) => v.length >= 8, message: "At least 8 characters" },
|
|
61
|
+
{ test: (v) => /[A-Z]/.test(v), message: "One uppercase letter" },
|
|
62
|
+
{ test: (v) => /[a-z]/.test(v), message: "One lowercase letter" },
|
|
63
|
+
{ test: (v) => /\d/.test(v), message: "One number" }
|
|
64
|
+
]
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<View style={styles.column}>
|
|
69
|
+
<View style={styles.section}>
|
|
70
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
71
|
+
Using usePasswordField Hook
|
|
72
|
+
</Typography>
|
|
73
|
+
<PasswordField
|
|
74
|
+
{...passwordProps}
|
|
75
|
+
label="Create Password"
|
|
76
|
+
placeholder="Enter password"
|
|
77
|
+
description="The hook handles state, requirements, and validation automatically"
|
|
78
|
+
/>
|
|
79
|
+
</View>
|
|
80
|
+
</View>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const States = () => {
|
|
85
|
+
const defaultProps = usePasswordField()
|
|
86
|
+
const typingProps = usePasswordField({ initialValue: "password123" })
|
|
87
|
+
const validatedProps = usePasswordField({ initialValue: "SecurePass123!" })
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<View style={styles.column}>
|
|
91
|
+
<View style={styles.section}>
|
|
92
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
93
|
+
Default
|
|
94
|
+
</Typography>
|
|
95
|
+
<PasswordField
|
|
96
|
+
{...defaultProps}
|
|
97
|
+
label="Default"
|
|
98
|
+
placeholder="Enter your password"
|
|
99
|
+
description="Help text"
|
|
100
|
+
/>
|
|
101
|
+
</View>
|
|
102
|
+
<View style={styles.section}>
|
|
103
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
104
|
+
Disabled
|
|
105
|
+
</Typography>
|
|
106
|
+
<PasswordField
|
|
107
|
+
label="Disabled"
|
|
108
|
+
placeholder="Enter your password"
|
|
109
|
+
description="Help text"
|
|
110
|
+
editable={false}
|
|
111
|
+
/>
|
|
112
|
+
</View>
|
|
113
|
+
<View style={styles.section}>
|
|
114
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
115
|
+
With Value
|
|
116
|
+
</Typography>
|
|
117
|
+
<PasswordField
|
|
118
|
+
{...typingProps}
|
|
119
|
+
label="With Value"
|
|
120
|
+
placeholder="Enter your password"
|
|
121
|
+
description="Help text"
|
|
122
|
+
/>
|
|
123
|
+
</View>
|
|
124
|
+
<View style={styles.section}>
|
|
125
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
126
|
+
Success State
|
|
127
|
+
</Typography>
|
|
128
|
+
<PasswordField
|
|
129
|
+
{...validatedProps}
|
|
130
|
+
label="Success State"
|
|
131
|
+
placeholder="Enter your password"
|
|
132
|
+
state="success"
|
|
133
|
+
description="Password meets all requirements"
|
|
134
|
+
/>
|
|
135
|
+
</View>
|
|
136
|
+
<View style={styles.section}>
|
|
137
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
138
|
+
Error State
|
|
139
|
+
</Typography>
|
|
140
|
+
<PasswordField
|
|
141
|
+
{...typingProps}
|
|
142
|
+
label="Error State"
|
|
143
|
+
placeholder="Enter your password"
|
|
144
|
+
state="error"
|
|
145
|
+
error="Password is too weak"
|
|
146
|
+
description="Help text"
|
|
147
|
+
/>
|
|
148
|
+
</View>
|
|
149
|
+
</View>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const MultipleErrors = () => (
|
|
154
|
+
<View style={styles.column}>
|
|
155
|
+
<View style={styles.section}>
|
|
156
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
157
|
+
Multiple Errors
|
|
158
|
+
</Typography>
|
|
159
|
+
<PasswordField
|
|
160
|
+
label="Create Password"
|
|
161
|
+
placeholder="Enter password"
|
|
162
|
+
value="weak"
|
|
163
|
+
state="error"
|
|
164
|
+
error={[
|
|
165
|
+
"Must be at least 8 characters",
|
|
166
|
+
"Must contain an uppercase letter",
|
|
167
|
+
"Must contain a number"
|
|
168
|
+
]}
|
|
169
|
+
/>
|
|
170
|
+
</View>
|
|
171
|
+
</View>
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
export const ManualValidation = () => {
|
|
175
|
+
const [password, setPassword] = useState("")
|
|
176
|
+
const [passwordState, setPasswordState] = useState<InputState>("default")
|
|
177
|
+
|
|
178
|
+
const handleChange = (newValue: string) => {
|
|
179
|
+
setPassword(newValue)
|
|
180
|
+
|
|
181
|
+
if (!newValue) {
|
|
182
|
+
setPasswordState("default")
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const hasMinLength = newValue.length >= 8
|
|
187
|
+
const hasUppercase = /[A-Z]/.test(newValue)
|
|
188
|
+
const hasLowercase = /[a-z]/.test(newValue)
|
|
189
|
+
const hasNumber = /\d/.test(newValue)
|
|
190
|
+
|
|
191
|
+
const allRequirementsMet =
|
|
192
|
+
hasMinLength && hasUppercase && hasLowercase && hasNumber
|
|
193
|
+
|
|
194
|
+
setPasswordState(allRequirementsMet ? "success" : "error")
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const hasMinLength = password.length >= 8
|
|
198
|
+
const hasUppercase = /[A-Z]/.test(password)
|
|
199
|
+
const hasLowercase = /[a-z]/.test(password)
|
|
200
|
+
const hasNumber = /\d/.test(password)
|
|
201
|
+
|
|
202
|
+
const requirements: PasswordRequirement[] = [
|
|
203
|
+
{ text: "At least 8 characters", satisfied: hasMinLength },
|
|
204
|
+
{ text: "One uppercase letter", satisfied: hasUppercase },
|
|
205
|
+
{ text: "One lowercase letter", satisfied: hasLowercase },
|
|
206
|
+
{ text: "One number", satisfied: hasNumber }
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<View style={styles.column}>
|
|
211
|
+
<View style={styles.section}>
|
|
212
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
213
|
+
Manual Validation
|
|
214
|
+
</Typography>
|
|
215
|
+
<PasswordField
|
|
216
|
+
label="Create Password"
|
|
217
|
+
placeholder="Enter password"
|
|
218
|
+
value={password}
|
|
219
|
+
onValueChange={handleChange}
|
|
220
|
+
state={passwordState}
|
|
221
|
+
description="Manual validation approach without using the hook"
|
|
222
|
+
requirements={requirements}
|
|
223
|
+
showRequirements={password.length > 0}
|
|
224
|
+
/>
|
|
225
|
+
</View>
|
|
226
|
+
</View>
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export const CompoundComponentAPI = () => {
|
|
231
|
+
const [password, setPassword] = useState("")
|
|
232
|
+
const [passwordState, setPasswordState] = useState<InputState>("default")
|
|
233
|
+
|
|
234
|
+
const handleChange = (newValue: string) => {
|
|
235
|
+
setPassword(newValue)
|
|
236
|
+
|
|
237
|
+
if (!newValue) {
|
|
238
|
+
setPasswordState("default")
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const hasMinLength = newValue.length >= 8
|
|
243
|
+
const hasUppercase = /[A-Z]/.test(newValue)
|
|
244
|
+
const hasLowercase = /[a-z]/.test(newValue)
|
|
245
|
+
const hasNumber = /\d/.test(newValue)
|
|
246
|
+
|
|
247
|
+
const allRequirementsMet =
|
|
248
|
+
hasMinLength && hasUppercase && hasLowercase && hasNumber
|
|
249
|
+
|
|
250
|
+
setPasswordState(allRequirementsMet ? "success" : "error")
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const hasMinLength = password.length >= 8
|
|
254
|
+
const hasUppercase = /[A-Z]/.test(password)
|
|
255
|
+
const hasLowercase = /[a-z]/.test(password)
|
|
256
|
+
const hasNumber = /\d/.test(password)
|
|
257
|
+
|
|
258
|
+
const requirements: PasswordRequirement[] = [
|
|
259
|
+
{ text: "At least 8 characters", satisfied: hasMinLength },
|
|
260
|
+
{ text: "One uppercase letter", satisfied: hasUppercase },
|
|
261
|
+
{ text: "One lowercase letter", satisfied: hasLowercase },
|
|
262
|
+
{ text: "One number", satisfied: hasNumber }
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
const allRequirementsMet =
|
|
266
|
+
hasMinLength && hasUppercase && hasLowercase && hasNumber
|
|
267
|
+
const showRequirements = password.length > 0 && !allRequirementsMet
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<View style={styles.column}>
|
|
271
|
+
<View style={styles.section}>
|
|
272
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
273
|
+
Compound Component API
|
|
274
|
+
</Typography>
|
|
275
|
+
<PasswordField.Root>
|
|
276
|
+
<PasswordField.Label state={passwordState}>
|
|
277
|
+
Password
|
|
278
|
+
</PasswordField.Label>
|
|
279
|
+
<PasswordField.Field
|
|
280
|
+
placeholder="Enter password"
|
|
281
|
+
value={password}
|
|
282
|
+
onValueChange={handleChange}
|
|
283
|
+
state={passwordState}
|
|
284
|
+
/>
|
|
285
|
+
<PasswordField.Description state={passwordState}>
|
|
286
|
+
Must be at least 8 characters
|
|
287
|
+
</PasswordField.Description>
|
|
288
|
+
{showRequirements && (
|
|
289
|
+
<PasswordField.Requirements requirements={requirements} />
|
|
290
|
+
)}
|
|
291
|
+
{passwordState === "error" && password.length > 0 && (
|
|
292
|
+
<PasswordField.Error>
|
|
293
|
+
Password does not meet requirements
|
|
294
|
+
</PasswordField.Error>
|
|
295
|
+
)}
|
|
296
|
+
</PasswordField.Root>
|
|
297
|
+
</View>
|
|
298
|
+
</View>
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export const WithOptionalText = () => (
|
|
303
|
+
<View style={styles.column}>
|
|
304
|
+
<View style={styles.section}>
|
|
305
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
306
|
+
With Optional Text
|
|
307
|
+
</Typography>
|
|
308
|
+
<PasswordField
|
|
309
|
+
label="Password"
|
|
310
|
+
optionalText="(optional)"
|
|
311
|
+
placeholder="Enter password"
|
|
312
|
+
description="Optional password field"
|
|
313
|
+
/>
|
|
314
|
+
</View>
|
|
315
|
+
</View>
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
export const PasswordConfirmation = () => {
|
|
319
|
+
const [password, setPassword] = useState("")
|
|
320
|
+
const [confirmPassword, setConfirmPassword] = useState("")
|
|
321
|
+
const hasError = confirmPassword.length > 0 && password !== confirmPassword
|
|
322
|
+
|
|
323
|
+
return (
|
|
324
|
+
<View style={styles.column}>
|
|
325
|
+
<View style={styles.section}>
|
|
326
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
327
|
+
Password Confirmation
|
|
328
|
+
</Typography>
|
|
329
|
+
<PasswordField
|
|
330
|
+
label="Password"
|
|
331
|
+
placeholder="Enter password"
|
|
332
|
+
value={password}
|
|
333
|
+
onValueChange={setPassword}
|
|
334
|
+
state="default"
|
|
335
|
+
/>
|
|
336
|
+
<PasswordField
|
|
337
|
+
label="Confirm Password"
|
|
338
|
+
placeholder="Re-enter password"
|
|
339
|
+
value={confirmPassword}
|
|
340
|
+
onValueChange={setConfirmPassword}
|
|
341
|
+
state={hasError ? "error" : "default"}
|
|
342
|
+
error={hasError ? "Passwords do not match" : undefined}
|
|
343
|
+
/>
|
|
344
|
+
</View>
|
|
345
|
+
</View>
|
|
346
|
+
)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const styles = StyleSheet.create({
|
|
350
|
+
container: {
|
|
351
|
+
width: 320
|
|
352
|
+
},
|
|
353
|
+
column: {
|
|
354
|
+
flexDirection: "column",
|
|
355
|
+
gap: 24,
|
|
356
|
+
width: 320
|
|
357
|
+
},
|
|
358
|
+
section: {
|
|
359
|
+
flexDirection: "column",
|
|
360
|
+
gap: 8
|
|
361
|
+
}
|
|
362
|
+
})
|