@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.
Files changed (182) hide show
  1. package/.turbo/turbo-build.log +15 -15
  2. package/CHANGELOG.md +16 -0
  3. package/COMPONENT_GUIDELINES.md +111 -4
  4. package/dist/index.cjs +12370 -1455
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +1110 -11
  7. package/dist/index.d.ts +1110 -11
  8. package/dist/index.js +12324 -1455
  9. package/dist/index.js.map +1 -1
  10. package/package.json +28 -9
  11. package/src/__mocks__/asset-stub.ts +1 -0
  12. package/src/__mocks__/emotion-native.tsx +18 -0
  13. package/src/__mocks__/react-native-gesture-handler.tsx +41 -0
  14. package/src/__mocks__/react-native-reanimated.tsx +79 -0
  15. package/src/__mocks__/react-native-safe-area-context.tsx +6 -0
  16. package/src/__mocks__/react-native-svg.tsx +27 -0
  17. package/src/__mocks__/react-native-worklets.tsx +11 -0
  18. package/src/__mocks__/react-native.tsx +338 -0
  19. package/src/__mocks__/rn-primitives/avatar.tsx +24 -0
  20. package/src/__mocks__/rn-primitives/checkbox.tsx +19 -0
  21. package/src/__mocks__/rn-primitives/select.tsx +116 -0
  22. package/src/__mocks__/rn-primitives/slider.tsx +40 -0
  23. package/src/__mocks__/rn-primitives/slot.tsx +30 -0
  24. package/src/__mocks__/rn-primitives/switch.tsx +24 -0
  25. package/src/__mocks__/rn-primitives/toggle.tsx +16 -0
  26. package/src/components/atoms/Avatar/Avatar.stories.tsx +57 -49
  27. package/src/components/atoms/Avatar/Avatar.test.tsx +269 -0
  28. package/src/components/atoms/Avatar/Avatar.tsx +68 -22
  29. package/src/components/atoms/Avatar/index.ts +1 -6
  30. package/src/components/atoms/Badge/Badge.stories.tsx +5 -29
  31. package/src/components/atoms/Badge/Badge.test.tsx +90 -0
  32. package/src/components/atoms/Button/Button.test.tsx +123 -0
  33. package/src/components/atoms/Button/Button.tsx +1 -1
  34. package/src/components/atoms/CarouselControls/CarouselControls.stories.tsx +217 -0
  35. package/src/components/atoms/CarouselControls/CarouselControls.tsx +127 -0
  36. package/src/components/atoms/CarouselControls/index.ts +2 -0
  37. package/src/components/atoms/Hint/Hint.test.tsx +36 -0
  38. package/src/components/atoms/Icon/Icon.test.tsx +98 -0
  39. package/src/components/atoms/Icon/Icon.tsx +5 -1
  40. package/src/components/atoms/IconButton/IconButton.test.tsx +101 -0
  41. package/src/components/atoms/Illustration/Illustration.test.tsx +55 -0
  42. package/src/components/atoms/Input/Input.stories.tsx +129 -86
  43. package/src/components/atoms/Input/Input.test.tsx +306 -0
  44. package/src/components/atoms/Input/Input.tsx +9 -1
  45. package/src/components/atoms/Input/InputField.tsx +226 -74
  46. package/src/components/atoms/Link/Link.test.tsx +89 -0
  47. package/src/components/atoms/Logo/Logo.registry.ts +30 -5
  48. package/src/components/atoms/Logo/Logo.stories.tsx +108 -0
  49. package/src/components/atoms/Logo/Logo.test.tsx +56 -0
  50. package/src/components/atoms/Logo/assets/BCorp.tsx +113 -0
  51. package/src/components/atoms/Logo/assets/ButternutFavicon.tsx +33 -0
  52. package/src/components/atoms/Logo/assets/ButternutPrimary.tsx +294 -0
  53. package/src/components/atoms/Logo/assets/ButternutTabbedBottom.tsx +294 -0
  54. package/src/components/atoms/Logo/assets/ButternutTabbedTop.tsx +294 -0
  55. package/src/components/atoms/Logo/assets/ButternutWordmark.tsx +274 -0
  56. package/src/components/atoms/Logo/assets/PsiBufetFavicon.tsx +45 -0
  57. package/src/components/atoms/Logo/assets/PsiBufetPrimary.tsx +218 -0
  58. package/src/components/atoms/Logo/assets/PsiBufetTabbedBottom.tsx +218 -0
  59. package/src/components/atoms/Logo/assets/PsiBufetTabbedTop.tsx +218 -0
  60. package/src/components/atoms/Logo/assets/PsiBufetWordmark.tsx +195 -0
  61. package/src/components/atoms/Logo/assets/index.ts +11 -0
  62. package/src/components/atoms/NumberInput/NumberInput.stories.tsx +183 -0
  63. package/src/components/atoms/NumberInput/NumberInput.test.tsx +261 -0
  64. package/src/components/atoms/NumberInput/NumberInput.tsx +129 -0
  65. package/src/components/atoms/NumberInput/NumberInputField.tsx +77 -0
  66. package/src/components/atoms/NumberInput/index.ts +4 -0
  67. package/src/components/atoms/Spinner/Spinner.test.tsx +46 -0
  68. package/src/components/atoms/Spinner/Spinner.tsx +14 -5
  69. package/src/components/atoms/Switch/Switch.test.tsx +92 -0
  70. package/src/components/atoms/Switch/Switch.tsx +16 -13
  71. package/src/components/atoms/Tag/Tag.test.tsx +70 -0
  72. package/src/components/atoms/TextArea/TextArea.stories.tsx +303 -0
  73. package/src/components/atoms/TextArea/TextArea.test.tsx +416 -0
  74. package/src/components/atoms/TextArea/TextArea.tsx +171 -0
  75. package/src/components/atoms/TextArea/TextAreaField.tsx +304 -0
  76. package/src/components/atoms/TextArea/TextAreaLabel.tsx +103 -0
  77. package/src/components/atoms/TextArea/index.ts +6 -0
  78. package/src/components/atoms/Typography/Typography.test.tsx +94 -0
  79. package/src/components/atoms/index.ts +3 -0
  80. package/src/components/molecules/Accordion/Accordion.stories.tsx +177 -0
  81. package/src/components/molecules/Accordion/Accordion.test.tsx +185 -0
  82. package/src/components/molecules/Accordion/Accordion.tsx +284 -0
  83. package/src/components/molecules/Accordion/index.ts +6 -0
  84. package/src/components/molecules/Animated/Animated.stories.tsx +254 -0
  85. package/src/components/molecules/Animated/Animated.tsx +283 -0
  86. package/src/components/molecules/Animated/index.ts +10 -0
  87. package/src/components/molecules/ButtonDock/ButtonDock.test.tsx +83 -0
  88. package/src/components/molecules/ButtonGroup/ButtonGroup.stories.tsx +8 -14
  89. package/src/components/molecules/ButtonGroup/ButtonGroup.test.tsx +73 -0
  90. package/src/components/molecules/ButtonGroup/ButtonGroup.tsx +25 -3
  91. package/src/components/molecules/Checkbox/Checkbox.stories.tsx +72 -0
  92. package/src/components/molecules/Checkbox/Checkbox.test.tsx +117 -0
  93. package/src/components/molecules/Checkbox/Checkbox.tsx +101 -95
  94. package/src/components/molecules/CopyField/CopyField.stories.tsx +313 -0
  95. package/src/components/molecules/CopyField/CopyField.test.tsx +431 -0
  96. package/src/components/molecules/CopyField/CopyField.tsx +156 -0
  97. package/src/components/molecules/CopyField/CopyFieldInput.tsx +127 -0
  98. package/src/components/molecules/CopyField/hooks/index.ts +1 -0
  99. package/src/components/molecules/CopyField/hooks/useCopyField.ts +25 -0
  100. package/src/components/molecules/CopyField/index.ts +4 -0
  101. package/src/components/molecules/DatePicker/DatePicker.stories.tsx +298 -0
  102. package/src/components/molecules/DatePicker/DatePicker.test.tsx +201 -0
  103. package/src/components/molecules/DatePicker/DatePicker.tsx +590 -0
  104. package/src/components/molecules/DatePicker/index.ts +2 -0
  105. package/src/components/molecules/Drawer/Drawer.stories.tsx +285 -0
  106. package/src/components/molecules/Drawer/Drawer.test.tsx +180 -0
  107. package/src/components/molecules/Drawer/Drawer.tsx +187 -0
  108. package/src/components/molecules/Drawer/DrawerBody.tsx +80 -0
  109. package/src/components/molecules/Drawer/DrawerClose.tsx +76 -0
  110. package/src/components/molecules/Drawer/DrawerContent.tsx +339 -0
  111. package/src/components/molecules/Drawer/DrawerContext.ts +19 -0
  112. package/src/components/molecules/Drawer/DrawerDescription.tsx +31 -0
  113. package/src/components/molecules/Drawer/DrawerDragContext.ts +11 -0
  114. package/src/components/molecules/Drawer/DrawerFooter.tsx +49 -0
  115. package/src/components/molecules/Drawer/DrawerFooterContext.ts +6 -0
  116. package/src/components/molecules/Drawer/DrawerGrabber.tsx +62 -0
  117. package/src/components/molecules/Drawer/DrawerHeader.tsx +244 -0
  118. package/src/components/molecules/Drawer/DrawerHeaderContext.ts +13 -0
  119. package/src/components/molecules/Drawer/DrawerOverlay.tsx +53 -0
  120. package/src/components/molecules/Drawer/DrawerTitle.tsx +32 -0
  121. package/src/components/molecules/Drawer/index.ts +12 -0
  122. package/src/components/molecules/FilterTab/FilterTab.stories.tsx +210 -0
  123. package/src/components/molecules/FilterTab/FilterTab.tsx +310 -0
  124. package/src/components/molecules/FilterTab/index.ts +2 -0
  125. package/src/components/molecules/MessageCard/MessageCard.stories.tsx +169 -0
  126. package/src/components/molecules/MessageCard/MessageCard.tsx +362 -0
  127. package/src/components/molecules/MessageCard/index.ts +10 -0
  128. package/src/components/molecules/Notification/Notification.stories.tsx +219 -0
  129. package/src/components/molecules/Notification/Notification.tsx +426 -0
  130. package/src/components/molecules/Notification/index.ts +2 -0
  131. package/src/components/molecules/NumberField/NumberField.stories.tsx +231 -0
  132. package/src/components/molecules/NumberField/NumberField.tsx +186 -0
  133. package/src/components/molecules/NumberField/NumberFieldInput.tsx +287 -0
  134. package/src/components/molecules/NumberField/index.ts +2 -0
  135. package/src/components/molecules/PasswordField/PasswordField.stories.tsx +362 -0
  136. package/src/components/molecules/PasswordField/PasswordField.test.tsx +369 -0
  137. package/src/components/molecules/PasswordField/PasswordField.tsx +194 -0
  138. package/src/components/molecules/PasswordField/PasswordFieldError.tsx +52 -0
  139. package/src/components/molecules/PasswordField/PasswordFieldInput.tsx +73 -0
  140. package/src/components/molecules/PasswordField/PasswordFieldRequirements.tsx +92 -0
  141. package/src/components/molecules/PasswordField/hooks/index.ts +2 -0
  142. package/src/components/molecules/PasswordField/hooks/usePasswordField.ts +113 -0
  143. package/src/components/molecules/PasswordField/index.ts +10 -0
  144. package/src/components/molecules/PictureSelector/PictureSelector.stories.tsx +243 -0
  145. package/src/components/molecules/PictureSelector/PictureSelector.tsx +313 -0
  146. package/src/components/molecules/PictureSelector/index.ts +5 -0
  147. package/src/components/molecules/Progress/Progress.stories.tsx +145 -0
  148. package/src/components/molecules/Progress/Progress.tsx +184 -0
  149. package/src/components/molecules/Progress/index.ts +2 -0
  150. package/src/components/molecules/Radio/Radio.test.tsx +104 -0
  151. package/src/components/molecules/Radio/Radio.tsx +1 -2
  152. package/src/components/molecules/SearchField/SearchField.stories.tsx +242 -0
  153. package/src/components/molecules/SearchField/SearchField.test.tsx +318 -0
  154. package/src/components/molecules/SearchField/SearchField.tsx +143 -0
  155. package/src/components/molecules/SearchField/SearchFieldInput.tsx +63 -0
  156. package/src/components/molecules/SearchField/hooks/index.ts +1 -0
  157. package/src/components/molecules/SearchField/hooks/useSearchField.ts +56 -0
  158. package/src/components/molecules/SearchField/index.ts +4 -0
  159. package/src/components/molecules/SegmentedControl/SegmentedControl.stories.tsx +31 -8
  160. package/src/components/molecules/SegmentedControl/SegmentedControl.test.tsx +141 -0
  161. package/src/components/molecules/SegmentedControl/SegmentedControl.tsx +237 -23
  162. package/src/components/molecules/SelectField/SelectField.stories.tsx +320 -0
  163. package/src/components/molecules/SelectField/SelectField.test.tsx +254 -0
  164. package/src/components/molecules/SelectField/SelectField.tsx +236 -0
  165. package/src/components/molecules/SelectField/SelectFieldContent.tsx +85 -0
  166. package/src/components/molecules/SelectField/SelectFieldItem.tsx +133 -0
  167. package/src/components/molecules/SelectField/SelectFieldTrigger.tsx +170 -0
  168. package/src/components/molecules/SelectField/SelectFieldValue.tsx +31 -0
  169. package/src/components/molecules/SelectField/hooks/index.ts +2 -0
  170. package/src/components/molecules/SelectField/hooks/useSelectField.ts +84 -0
  171. package/src/components/molecules/SelectField/index.ts +10 -0
  172. package/src/components/molecules/Slider/Slider.test.tsx +102 -0
  173. package/src/components/molecules/Slider/Slider.tsx +293 -180
  174. package/src/components/molecules/Tooltip/Tooltip.stories.tsx +168 -0
  175. package/src/components/molecules/Tooltip/Tooltip.tsx +326 -0
  176. package/src/components/molecules/Tooltip/index.ts +2 -0
  177. package/src/components/molecules/index.ts +15 -0
  178. package/src/test-utils.tsx +20 -0
  179. package/tsconfig.json +1 -1
  180. package/tsup.config.ts +16 -2
  181. package/vitest.config.ts +114 -0
  182. 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
+ })