@butternutbox/pawprint-native 1.6.3 → 2.0.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 (44) hide show
  1. package/.turbo/turbo-build.log +9 -9
  2. package/CHANGELOG.md +52 -0
  3. package/COMPONENT_GUIDELINES.md +57 -1
  4. package/dist/index.cjs +1459 -1380
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +81 -117
  7. package/dist/index.d.ts +81 -117
  8. package/dist/index.js +1206 -1125
  9. package/dist/index.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/__mocks__/react-native.tsx +33 -5
  12. package/src/__mocks__/rn-primitives/portal.tsx +71 -0
  13. package/src/components/atoms/Input/InputField.tsx +12 -2
  14. package/src/components/molecules/Drawer/Drawer.test.tsx +54 -2
  15. package/src/components/molecules/Drawer/DrawerBody.tsx +2 -17
  16. package/src/components/molecules/Drawer/DrawerContent.tsx +49 -2
  17. package/src/components/molecules/Drawer/DrawerOverlay.tsx +28 -16
  18. package/src/components/molecules/SelectField/AsyncSearch.test.tsx +373 -0
  19. package/src/components/molecules/SelectField/SelectDrawerField.stories.tsx +192 -0
  20. package/src/components/molecules/SelectField/SelectDrawerField.test.tsx +210 -0
  21. package/src/components/molecules/SelectField/SelectDrawerField.tsx +245 -0
  22. package/src/components/molecules/SelectField/SelectField.stories.tsx +221 -421
  23. package/src/components/molecules/SelectField/SelectField.test.tsx +214 -662
  24. package/src/components/molecules/SelectField/SelectField.tsx +269 -539
  25. package/src/components/molecules/SelectField/SelectFieldDropdown.tsx +90 -0
  26. package/src/components/molecules/SelectField/SelectFieldList.tsx +88 -0
  27. package/src/components/molecules/SelectField/SelectFieldOption.tsx +90 -0
  28. package/src/components/molecules/SelectField/core/index.ts +8 -0
  29. package/src/components/molecules/SelectField/core/listHeight.test.ts +22 -0
  30. package/src/components/molecules/SelectField/core/listHeight.ts +8 -0
  31. package/src/components/molecules/SelectField/core/options.ts +35 -0
  32. package/src/components/molecules/SelectField/core/useSelectOptions.ts +116 -0
  33. package/src/components/molecules/SelectField/index.ts +12 -9
  34. package/src/index.ts +0 -1
  35. package/src/test-utils.tsx +5 -1
  36. package/vitest.config.ts +7 -0
  37. package/src/components/molecules/SelectField/SelectFieldContent.tsx +0 -81
  38. package/src/components/molecules/SelectField/SelectFieldItem.tsx +0 -133
  39. package/src/components/molecules/SelectField/SelectFieldTrigger.tsx +0 -188
  40. package/src/components/molecules/SelectField/SelectFieldValue.tsx +0 -33
  41. package/src/components/molecules/SelectField/hooks/index.ts +0 -2
  42. package/src/components/molecules/SelectField/hooks/useSelectField.ts +0 -84
  43. package/src/hooks/index.ts +0 -1
  44. package/src/hooks/useDrawerKeyboardAvoidance.ts +0 -29
@@ -1,478 +1,278 @@
1
1
  import React, { useState } from "react"
2
- import { View, StyleSheet } from "react-native"
3
- import { SelectField, useSelectField } from "."
4
- import { Typography } from "../../atoms/Typography"
2
+ import { ScrollView, StyleSheet, View } from "react-native"
3
+ import { Info, Lock, Search, Settings } from "@butternutbox/pawprint-icons/core"
4
+ import { SelectField } from "."
5
+ import type { SelectFieldOptionData } from "."
5
6
  import { Icon } from "../../atoms/Icon"
6
- import { Search, Settings, Lock, Info } from "@butternutbox/pawprint-icons/core"
7
- import type { Option } from "@rn-primitives/select"
8
-
9
- const StoryWrapper = ({ children }: { children: React.ReactNode }) => (
10
- <>{children}</>
11
- )
7
+ import { Typography } from "../../atoms/Typography"
12
8
 
13
9
  export default {
14
10
  title: "Molecules/SelectField",
15
- component: SelectField,
16
- parameters: {
17
- docs: {
18
- description: {
19
- component:
20
- "Select field component for choosing a value from a dropdown list. Built on @rn-primitives/select with design system styling. Supports both simple props API and compound component API."
21
- }
22
- }
11
+ component: SelectField
12
+ }
13
+
14
+ const COUNTRIES: SelectFieldOptionData[] = [
15
+ { value: "uk", label: "United Kingdom" },
16
+ { value: "us", label: "United States" },
17
+ { value: "ca", label: "Canada" },
18
+ { value: "au", label: "Australia" },
19
+ { value: "de", label: "Germany" },
20
+ { value: "fr", label: "France" },
21
+ { value: "nl", label: "Netherlands" },
22
+ { value: "ie", label: "Ireland" }
23
+ ]
24
+
25
+ const SEARCH_TYPES: SelectFieldOptionData[] = [
26
+ {
27
+ value: "products",
28
+ label: "Products",
29
+ leadingIcon: <Icon icon={Search} size="md" />
23
30
  },
24
- argTypes: {
25
- label: {
26
- control: { type: "text" },
27
- description: "Label text"
28
- },
29
- placeholder: {
30
- control: { type: "text" },
31
- description: "Placeholder text"
32
- },
33
- description: {
34
- control: { type: "text" },
35
- description: "Help text below select"
36
- },
37
- error: {
38
- control: { type: "text" },
39
- description: "Error message"
40
- },
41
- state: {
42
- control: { type: "select" },
43
- options: ["default", "error", "success"],
44
- description: "Visual state of the select"
45
- },
46
- optionalText: {
47
- control: { type: "text" },
48
- description: "Optional text to display next to label"
49
- },
50
- disabled: {
51
- control: { type: "boolean" },
52
- description: "Disables the select"
53
- }
31
+ {
32
+ value: "orders",
33
+ label: "Orders",
34
+ leadingIcon: <Icon icon={Settings} size="md" />
35
+ },
36
+ {
37
+ value: "admin",
38
+ label: "Admin Panel",
39
+ leadingIcon: <Icon icon={Lock} size="md" />,
40
+ disabled: true
41
+ },
42
+ {
43
+ value: "help",
44
+ label: "Help & Support",
45
+ leadingIcon: <Icon icon={Info} size="md" />
54
46
  }
55
- }
47
+ ]
48
+
49
+ const StoryWrapper = ({ children }: { children: React.ReactNode }) => (
50
+ <ScrollView contentContainerStyle={styles.wrapper}>{children}</ScrollView>
51
+ )
52
+
53
+ const Section = ({
54
+ title,
55
+ children
56
+ }: {
57
+ title: string
58
+ children: React.ReactNode
59
+ }) => (
60
+ <View style={styles.section}>
61
+ <Typography size="sm" weight="semiBold" color="tertiary">
62
+ {title}
63
+ </Typography>
64
+ {children}
65
+ </View>
66
+ )
56
67
 
57
68
  export const Default = () => (
58
69
  <StoryWrapper>
59
- <View style={styles.container}>
70
+ <Section title="Default">
71
+ <SelectField
72
+ label="Country"
73
+ placeholder="Select a country"
74
+ description="Start typing to filter the list"
75
+ options={COUNTRIES}
76
+ />
77
+ </Section>
78
+ </StoryWrapper>
79
+ )
80
+
81
+ export const WithOptionIcons = () => (
82
+ <StoryWrapper>
83
+ <Section title="Per-option leading icons, one disabled">
60
84
  <SelectField
61
85
  label="Search Type"
62
86
  placeholder="Select search type"
63
- description="Choose how you want to search"
64
87
  leadingIcon={<Icon icon={Search} size="md" />}
65
- >
66
- <SelectField.Item
67
- value="products"
68
- leadingIcon={<Icon icon={Search} size="md" />}
69
- hintText="Search all products"
70
- >
71
- Products
72
- </SelectField.Item>
73
- <SelectField.Item
74
- value="orders"
75
- leadingIcon={<Icon icon={Settings} size="md" />}
76
- hintText="Search order history"
77
- >
78
- Orders
79
- </SelectField.Item>
80
- <SelectField.Item
81
- value="admin"
82
- leadingIcon={<Icon icon={Lock} size="md" />}
83
- hintText="Admin access required"
84
- disabled
85
- >
86
- Admin Panel
87
- </SelectField.Item>
88
- <SelectField.Item
89
- value="help"
90
- leadingIcon={<Icon icon={Info} size="md" />}
91
- >
92
- Help & Support
93
- </SelectField.Item>
94
- </SelectField>
95
- </View>
88
+ options={SEARCH_TYPES}
89
+ />
90
+ </Section>
96
91
  </StoryWrapper>
97
92
  )
98
93
 
99
94
  export const States = () => (
100
95
  <StoryWrapper>
101
- <View style={styles.column}>
102
- <View style={styles.section}>
103
- <Typography size="sm" weight="semiBold" color="tertiary">
104
- Default State
105
- </Typography>
106
- <SelectField
107
- label="Default State"
108
- placeholder="Select an option"
109
- description="Normal select state"
110
- >
111
- <SelectField.Item value="option1">Option 1</SelectField.Item>
112
- <SelectField.Item value="option2">Option 2</SelectField.Item>
113
- <SelectField.Item value="option3">Option 3</SelectField.Item>
114
- </SelectField>
115
- </View>
116
-
117
- <View style={styles.section}>
118
- <Typography size="sm" weight="semiBold" color="tertiary">
119
- Error State
120
- </Typography>
121
- <SelectField
122
- label="Error State"
123
- placeholder="Select an option"
124
- state="error"
125
- error="This field is required"
126
- description="Error state"
127
- >
128
- <SelectField.Item value="option1">Option 1</SelectField.Item>
129
- <SelectField.Item value="option2">Option 2</SelectField.Item>
130
- </SelectField>
131
- </View>
132
-
133
- <View style={styles.section}>
134
- <Typography size="sm" weight="semiBold" color="tertiary">
135
- Success State
136
- </Typography>
137
- <SelectField
138
- label="Success State"
139
- placeholder="Select an option"
140
- state="success"
141
- description="Success state"
142
- defaultValue="option1"
143
- >
144
- <SelectField.Item value="option1">Option 1</SelectField.Item>
145
- <SelectField.Item value="option2">Option 2</SelectField.Item>
146
- </SelectField>
147
- </View>
148
-
149
- <View style={styles.section}>
150
- <Typography size="sm" weight="semiBold" color="tertiary">
151
- Disabled
152
- </Typography>
153
- <SelectField
154
- label="Disabled"
155
- placeholder="Select an option"
156
- description="Disabled select field"
157
- disabled
158
- >
159
- <SelectField.Item value="option1">Option 1</SelectField.Item>
160
- <SelectField.Item value="option2">Option 2</SelectField.Item>
161
- </SelectField>
162
- </View>
163
- </View>
96
+ <Section title="Default">
97
+ <SelectField
98
+ label="Country"
99
+ placeholder="Select a country"
100
+ description="Normal state"
101
+ options={COUNTRIES}
102
+ />
103
+ </Section>
104
+ <Section title="Error">
105
+ <SelectField
106
+ label="Country"
107
+ placeholder="Select a country"
108
+ error="Please choose a country"
109
+ options={COUNTRIES}
110
+ />
111
+ </Section>
112
+ <Section title="Error with a value (clear takes the trailing slot)">
113
+ <SelectField
114
+ label="Country"
115
+ defaultValue="uk"
116
+ error="This country is not served yet"
117
+ options={COUNTRIES}
118
+ />
119
+ </Section>
120
+ <Section title="Success">
121
+ <SelectField
122
+ label="Country"
123
+ state="success"
124
+ defaultValue="uk"
125
+ description="Looks good"
126
+ options={COUNTRIES}
127
+ />
128
+ </Section>
129
+ <Section title="Disabled">
130
+ <SelectField
131
+ label="Country"
132
+ placeholder="Select a country"
133
+ description="Disabled field"
134
+ disabled
135
+ options={COUNTRIES}
136
+ />
137
+ </Section>
164
138
  </StoryWrapper>
165
139
  )
166
140
 
167
141
  export const WithOptionalText = () => (
168
142
  <StoryWrapper>
169
- <View style={styles.column}>
170
- <View style={styles.section}>
171
- <Typography size="sm" weight="semiBold" color="tertiary">
172
- With Optional Text
173
- </Typography>
174
- <SelectField
175
- label="Country"
176
- optionalText="(optional)"
177
- placeholder="Select a country"
178
- description="Optional select field"
179
- >
180
- <SelectField.Item value="uk">United Kingdom</SelectField.Item>
181
- <SelectField.Item value="us">United States</SelectField.Item>
182
- <SelectField.Item value="ca">Canada</SelectField.Item>
183
- </SelectField>
184
- </View>
185
- </View>
143
+ <Section title="Optional label suffix">
144
+ <SelectField
145
+ label="Country"
146
+ optionalText="(optional)"
147
+ placeholder="Select a country"
148
+ options={COUNTRIES}
149
+ />
150
+ </Section>
186
151
  </StoryWrapper>
187
152
  )
188
153
 
189
- export const CustomValidation = () => {
190
- const [value, setValue] = useState<Option | string | null>(null)
191
-
192
- // Extract actual value for validation
193
- const actualValue =
194
- value && typeof value === "object" && "value" in value ? value.value : value
195
-
196
- return (
197
- <StoryWrapper>
198
- <View style={styles.column}>
199
- <View style={styles.section}>
200
- <Typography size="sm" weight="semiBold" color="tertiary">
201
- Custom Validation
202
- </Typography>
203
- <SelectField
204
- label="Product Category"
205
- placeholder="Select category"
206
- value={value}
207
- onValueChange={(newValue) => setValue(newValue)}
208
- description="Select a valid category"
209
- error={
210
- actualValue === "invalid"
211
- ? "This option is not allowed"
212
- : undefined
213
- }
214
- state={actualValue === "invalid" ? "error" : "default"}
215
- >
216
- <SelectField.Item value="food">Food</SelectField.Item>
217
- <SelectField.Item value="toys">Toys</SelectField.Item>
218
- <SelectField.Item value="invalid">Invalid Option</SelectField.Item>
219
- </SelectField>
220
- </View>
221
- </View>
222
- </StoryWrapper>
223
- )
224
- }
225
-
226
- export const CustomStateValidationWithSuccess = () => {
227
- const selectProps = useSelectField({
228
- validationRule: {
229
- test: (value) => {
230
- if (!value) return false
231
- // Handle object with value property
232
- const actualValue =
233
- typeof value === "object" && value !== null && "value" in value
234
- ? (value as { value: string }).value
235
- : value
236
- return actualValue !== "invalid"
237
- },
238
- message: "Please select a valid option"
239
- }
240
- })
241
-
242
- return (
243
- <StoryWrapper>
244
- <View style={styles.column}>
245
- <View style={styles.section}>
246
- <Typography size="sm" weight="semiBold" color="tertiary">
247
- Real-time Validation with Success State
248
- </Typography>
249
- <SelectField
250
- {...selectProps}
251
- label="Product Category"
252
- placeholder="Select category"
253
- description="Select a valid category (not 'Invalid Option')"
254
- >
255
- <SelectField.Item value="food">Food</SelectField.Item>
256
- <SelectField.Item value="toys">Toys</SelectField.Item>
257
- <SelectField.Item value="accessories">Accessories</SelectField.Item>
258
- <SelectField.Item value="invalid">Invalid Option</SelectField.Item>
259
- </SelectField>
260
- </View>
261
- </View>
262
- </StoryWrapper>
263
- )
264
- }
265
-
266
154
  export const Controlled = () => {
267
- const selectProps = useSelectField()
268
-
269
- // Extract the actual value for display (handle object with value property)
270
- const displayValue = (() => {
271
- const val = selectProps.value
272
- if (!val) return "(none)"
273
- if (typeof val === "object" && val !== null && "value" in val) {
274
- return (val as { value: string }).value
275
- }
276
- return String(val)
277
- })()
155
+ const [country, setCountry] = useState<string | null>("ca")
278
156
 
279
157
  return (
280
158
  <StoryWrapper>
281
- <View style={styles.column}>
282
- <View style={styles.section}>
283
- <Typography size="sm" weight="semiBold" color="tertiary">
284
- Controlled: {displayValue}
285
- </Typography>
286
- <SelectField
287
- {...selectProps}
288
- label="Country"
289
- placeholder="Select a country"
290
- description={`Current value: "${displayValue}"`}
291
- >
292
- <SelectField.Item value="uk">United Kingdom</SelectField.Item>
293
- <SelectField.Item value="us">United States</SelectField.Item>
294
- <SelectField.Item value="ca">Canada</SelectField.Item>
295
- <SelectField.Item value="au">Australia</SelectField.Item>
296
- </SelectField>
297
- </View>
298
- </View>
159
+ <Section title="Controlled">
160
+ <SelectField
161
+ label="Country"
162
+ placeholder="Select a country"
163
+ value={country}
164
+ onValueChange={setCountry}
165
+ options={COUNTRIES}
166
+ />
167
+ <Typography size="sm" color="tertiary">
168
+ {`value: ${country ?? "null"}`}
169
+ </Typography>
170
+ </Section>
299
171
  </StoryWrapper>
300
172
  )
301
173
  }
302
174
 
303
- export const Searchable = () => (
175
+ export const NoResults = () => (
304
176
  <StoryWrapper>
305
- <View style={styles.column}>
306
- <View style={styles.section}>
307
- <Typography size="sm" weight="semiBold" color="tertiary">
308
- Searchable Select
309
- </Typography>
310
- <SelectField
311
- label="Country"
312
- placeholder="Select a country"
313
- description="Type to filter countries"
314
- searchable
315
- searchPlaceholder="Search countries..."
316
- >
317
- <SelectField.Item value="au">Australia</SelectField.Item>
318
- <SelectField.Item value="br">Brazil</SelectField.Item>
319
- <SelectField.Item value="ca">Canada</SelectField.Item>
320
- <SelectField.Item value="dk">Denmark</SelectField.Item>
321
- <SelectField.Item value="eg">Egypt</SelectField.Item>
322
- <SelectField.Item value="fr">France</SelectField.Item>
323
- <SelectField.Item value="de">Germany</SelectField.Item>
324
- <SelectField.Item value="in">India</SelectField.Item>
325
- <SelectField.Item value="jp">Japan</SelectField.Item>
326
- <SelectField.Item value="mx">Mexico</SelectField.Item>
327
- <SelectField.Item value="nl">Netherlands</SelectField.Item>
328
- <SelectField.Item value="nz">New Zealand</SelectField.Item>
329
- <SelectField.Item value="no">Norway</SelectField.Item>
330
- <SelectField.Item value="pl">Poland</SelectField.Item>
331
- <SelectField.Item value="pt">Portugal</SelectField.Item>
332
- <SelectField.Item value="es">Spain</SelectField.Item>
333
- <SelectField.Item value="se">Sweden</SelectField.Item>
334
- <SelectField.Item value="ch">Switzerland</SelectField.Item>
335
- <SelectField.Item value="uk">United Kingdom</SelectField.Item>
336
- <SelectField.Item value="us">United States</SelectField.Item>
337
- </SelectField>
338
- </View>
339
- </View>
177
+ <Section title="Type something unmatched to see the no-results row">
178
+ <SelectField
179
+ label="Country"
180
+ placeholder="Select a country"
181
+ noResultsText="No countries found"
182
+ options={COUNTRIES}
183
+ />
184
+ </Section>
340
185
  </StoryWrapper>
341
186
  )
342
187
 
343
- export const SearchableControlled = () => {
344
- const [value, setValue] = useState<Option | string | null>(null)
345
-
346
- const displayValue = (() => {
347
- if (!value) return "(none)"
348
- if (typeof value === "object" && "value" in value) return value.value
349
- return String(value)
350
- })()
351
-
352
- return (
353
- <StoryWrapper>
354
- <View style={styles.column}>
355
- <View style={styles.section}>
356
- <Typography size="sm" weight="semiBold" color="tertiary">
357
- Controlled value: {displayValue}
358
- </Typography>
359
- <SelectField
360
- label="Country"
361
- placeholder="Select a country"
362
- description="Select a country — the clear button resets the controlled value"
363
- searchable
364
- searchPlaceholder="Search countries..."
365
- value={value}
366
- onValueChange={(newValue) => setValue(newValue)}
367
- >
368
- <SelectField.Item value="au">Australia</SelectField.Item>
369
- <SelectField.Item value="ca">Canada</SelectField.Item>
370
- <SelectField.Item value="fr">France</SelectField.Item>
371
- <SelectField.Item value="de">Germany</SelectField.Item>
372
- <SelectField.Item value="jp">Japan</SelectField.Item>
373
- <SelectField.Item value="uk">United Kingdom</SelectField.Item>
374
- <SelectField.Item value="us">United States</SelectField.Item>
375
- </SelectField>
376
- </View>
377
- </View>
378
- </StoryWrapper>
379
- )
380
- }
188
+ export const LongLabels = () => (
189
+ <StoryWrapper>
190
+ <Section title="Long labels truncate to one line">
191
+ <SelectField
192
+ label="Region"
193
+ placeholder="Select a region"
194
+ options={[
195
+ {
196
+ value: "long",
197
+ label:
198
+ "A deliberately long option label that will not fit on one line"
199
+ },
200
+ { value: "short", label: "Short" }
201
+ ]}
202
+ />
203
+ </Section>
204
+ </StoryWrapper>
205
+ )
381
206
 
382
- export const SearchableDefaultPlaceholder = () => (
207
+ export const NearScreenBottom = () => (
383
208
  <StoryWrapper>
384
- <View style={styles.column}>
385
- <View style={styles.section}>
386
- <Typography size="sm" weight="semiBold" color="tertiary">
387
- Default Search Placeholder
388
- </Typography>
389
- <SelectField label="Pet breed" placeholder="Select a breed" searchable>
390
- <SelectField.Item value="labrador">
391
- Labrador Retriever
392
- </SelectField.Item>
393
- <SelectField.Item value="german-shepherd">
394
- German Shepherd
395
- </SelectField.Item>
396
- <SelectField.Item value="golden-retriever">
397
- Golden Retriever
398
- </SelectField.Item>
399
- <SelectField.Item value="bulldog">Bulldog</SelectField.Item>
400
- <SelectField.Item value="poodle">Poodle</SelectField.Item>
401
- <SelectField.Item value="beagle">Beagle</SelectField.Item>
402
- <SelectField.Item value="rottweiler">Rottweiler</SelectField.Item>
403
- <SelectField.Item value="yorkshire-terrier">
404
- Yorkshire Terrier
405
- </SelectField.Item>
406
- </SelectField>
407
- </View>
408
- </View>
209
+ <View style={styles.spacer} />
210
+ <Section title="Flips above when there is no room below">
211
+ <SelectField
212
+ label="Country"
213
+ placeholder="Select a country"
214
+ options={COUNTRIES}
215
+ />
216
+ </Section>
409
217
  </StoryWrapper>
410
218
  )
411
219
 
412
- const ALL_BREEDS = [
413
- { value: "labrador", label: "Labrador Retriever" },
414
- { value: "german-shepherd", label: "German Shepherd" },
415
- { value: "golden-retriever", label: "Golden Retriever" },
416
- { value: "bulldog", label: "Bulldog" },
417
- { value: "poodle", label: "Poodle" },
418
- { value: "beagle", label: "Beagle" },
419
- { value: "rottweiler", label: "Rottweiler" },
420
- { value: "dachshund", label: "Dachshund" },
421
- { value: "yorkshire-terrier", label: "Yorkshire Terrier" },
422
- { value: "boxer", label: "Boxer" }
423
- ]
220
+ const ALL_BREEDS: SelectFieldOptionData[] = [
221
+ "Border Collie",
222
+ "Boston Terrier",
223
+ "Cocker Spaniel",
224
+ "Flat-Coated Retriever",
225
+ "French Bulldog",
226
+ "German Shepherd",
227
+ "Golden Retriever",
228
+ "Jack Russell Terrier",
229
+ "Labradoodle",
230
+ "Labrador Retriever",
231
+ "Springer Spaniel",
232
+ "Yorkshire Terrier"
233
+ ].map((label) => ({ value: label.toLowerCase().replace(/\s+/g, "-"), label }))
424
234
 
425
- export const SearchableAsyncQuery = () => {
426
- const searchBreeds = (query: string) =>
427
- new Promise<typeof ALL_BREEDS>((resolve, reject) => {
428
- setTimeout(() => {
429
- // Type "fail" to see the error state.
430
- if (query.toLowerCase().includes("fail")) {
431
- reject(new Error("Simulated request failure"))
432
- return
433
- }
434
- resolve(
435
- ALL_BREEDS.filter((breed) =>
436
- breed.label.toLowerCase().includes(query.toLowerCase())
437
- )
235
+ const searchBreeds = (query: string) =>
236
+ new Promise<SelectFieldOptionData[]>((resolve, reject) => {
237
+ setTimeout(() => {
238
+ if (query.toLowerCase().includes("fail")) {
239
+ reject(new Error("Simulated request failure"))
240
+ return
241
+ }
242
+ resolve(
243
+ ALL_BREEDS.filter((breed) =>
244
+ breed.label.toLowerCase().includes(query.toLowerCase())
438
245
  )
439
- }, 600)
440
- })
246
+ )
247
+ }, 600)
248
+ })
441
249
 
442
- return (
443
- <StoryWrapper>
444
- <View style={styles.column}>
445
- <View style={styles.section}>
446
- <Typography size="sm" weight="semiBold" color="tertiary">
447
- Async Query (spinner, no-results, error)
448
- </Typography>
449
- <SelectField
450
- label="Breed"
451
- placeholder="Select a breed"
452
- description="Type to search — 'fail' triggers the error state"
453
- searchable
454
- searchPlaceholder="Search breeds..."
455
- noResultsText="No breeds found"
456
- errorText="Couldn't load breeds"
457
- onSearchQuery={searchBreeds}
458
- />
459
- </View>
460
- </View>
461
- </StoryWrapper>
462
- )
463
- }
250
+ export const AsyncSearch = () => (
251
+ <StoryWrapper>
252
+ <SelectField
253
+ label="Breed"
254
+ placeholder="Select a breed"
255
+ description="Type to search — 'fail' triggers the error state"
256
+ noResultsText="No breeds found"
257
+ errorText="Couldn't load breeds"
258
+ onSearchQuery={searchBreeds}
259
+ options={[]}
260
+ />
261
+ </StoryWrapper>
262
+ )
464
263
 
465
264
  const styles = StyleSheet.create({
466
- container: {
467
- width: 320
468
- },
469
- column: {
265
+ wrapper: {
470
266
  flexDirection: "column",
471
267
  gap: 24,
472
- width: 320
268
+ padding: 16,
269
+ width: 360
473
270
  },
474
271
  section: {
475
272
  flexDirection: "column",
476
273
  gap: 8
274
+ },
275
+ spacer: {
276
+ height: 520
477
277
  }
478
278
  })