@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.
- package/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +52 -0
- package/COMPONENT_GUIDELINES.md +57 -1
- package/dist/index.cjs +1459 -1380
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -117
- package/dist/index.d.ts +81 -117
- package/dist/index.js +1206 -1125
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__mocks__/react-native.tsx +33 -5
- package/src/__mocks__/rn-primitives/portal.tsx +71 -0
- package/src/components/atoms/Input/InputField.tsx +12 -2
- package/src/components/molecules/Drawer/Drawer.test.tsx +54 -2
- package/src/components/molecules/Drawer/DrawerBody.tsx +2 -17
- package/src/components/molecules/Drawer/DrawerContent.tsx +49 -2
- package/src/components/molecules/Drawer/DrawerOverlay.tsx +28 -16
- package/src/components/molecules/SelectField/AsyncSearch.test.tsx +373 -0
- package/src/components/molecules/SelectField/SelectDrawerField.stories.tsx +192 -0
- package/src/components/molecules/SelectField/SelectDrawerField.test.tsx +210 -0
- package/src/components/molecules/SelectField/SelectDrawerField.tsx +245 -0
- package/src/components/molecules/SelectField/SelectField.stories.tsx +221 -421
- package/src/components/molecules/SelectField/SelectField.test.tsx +214 -662
- package/src/components/molecules/SelectField/SelectField.tsx +269 -539
- package/src/components/molecules/SelectField/SelectFieldDropdown.tsx +90 -0
- package/src/components/molecules/SelectField/SelectFieldList.tsx +88 -0
- package/src/components/molecules/SelectField/SelectFieldOption.tsx +90 -0
- package/src/components/molecules/SelectField/core/index.ts +8 -0
- package/src/components/molecules/SelectField/core/listHeight.test.ts +22 -0
- package/src/components/molecules/SelectField/core/listHeight.ts +8 -0
- package/src/components/molecules/SelectField/core/options.ts +35 -0
- package/src/components/molecules/SelectField/core/useSelectOptions.ts +116 -0
- package/src/components/molecules/SelectField/index.ts +12 -9
- package/src/index.ts +0 -1
- package/src/test-utils.tsx +5 -1
- package/vitest.config.ts +7 -0
- package/src/components/molecules/SelectField/SelectFieldContent.tsx +0 -81
- package/src/components/molecules/SelectField/SelectFieldItem.tsx +0 -133
- package/src/components/molecules/SelectField/SelectFieldTrigger.tsx +0 -188
- package/src/components/molecules/SelectField/SelectFieldValue.tsx +0 -33
- package/src/components/molecules/SelectField/hooks/index.ts +0 -2
- package/src/components/molecules/SelectField/hooks/useSelectField.ts +0 -84
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useDrawerKeyboardAvoidance.ts +0 -29
|
@@ -1,478 +1,278 @@
|
|
|
1
1
|
import React, { useState } from "react"
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
<
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
<
|
|
170
|
-
<
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
|
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
|
-
<
|
|
282
|
-
<
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
175
|
+
export const NoResults = () => (
|
|
304
176
|
<StoryWrapper>
|
|
305
|
-
<
|
|
306
|
-
<
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
|
207
|
+
export const NearScreenBottom = () => (
|
|
383
208
|
<StoryWrapper>
|
|
384
|
-
<View style={styles.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
|
|
440
|
-
})
|
|
246
|
+
)
|
|
247
|
+
}, 600)
|
|
248
|
+
})
|
|
441
249
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
467
|
-
width: 320
|
|
468
|
-
},
|
|
469
|
-
column: {
|
|
265
|
+
wrapper: {
|
|
470
266
|
flexDirection: "column",
|
|
471
267
|
gap: 24,
|
|
472
|
-
|
|
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
|
})
|