@utilitywarehouse/hearth-react-native 0.27.3 → 0.28.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 +1 -1
- package/.turbo/turbo-lint.log +18 -19
- package/CHANGELOG.md +110 -0
- package/build/components/Combobox/Combobox.context.d.ts +13 -0
- package/build/components/Combobox/Combobox.context.js +9 -0
- package/build/components/Combobox/Combobox.d.ts +6 -0
- package/build/components/Combobox/Combobox.js +246 -0
- package/build/components/Combobox/Combobox.props.d.ts +180 -0
- package/build/components/Combobox/Combobox.props.js +1 -0
- package/build/components/Combobox/ComboboxOption.d.ts +6 -0
- package/build/components/Combobox/ComboboxOption.js +56 -0
- package/build/components/Combobox/index.d.ts +4 -0
- package/build/components/Combobox/index.js +3 -0
- package/build/components/Modal/Modal.js +26 -42
- package/build/components/Modal/Modal.web.js +3 -3
- package/build/components/Pagination/Pagination.d.ts +6 -0
- package/build/components/Pagination/Pagination.js +125 -0
- package/build/components/Pagination/Pagination.props.d.ts +26 -0
- package/build/components/Pagination/Pagination.props.js +1 -0
- package/build/components/Pagination/Pagination.utils.d.ts +2 -0
- package/build/components/Pagination/Pagination.utils.js +20 -0
- package/build/components/Pagination/Pagination.utils.test.d.ts +1 -0
- package/build/components/Pagination/Pagination.utils.test.js +16 -0
- package/build/components/Pagination/index.d.ts +2 -0
- package/build/components/Pagination/index.js +1 -0
- package/build/components/SafeAreaView/SafeAreaView.d.ts +5 -0
- package/build/components/SafeAreaView/SafeAreaView.js +117 -0
- package/build/components/SafeAreaView/SafeAreaView.props.d.ts +17 -0
- package/build/components/SafeAreaView/SafeAreaView.props.js +1 -0
- package/build/components/SafeAreaView/index.d.ts +2 -0
- package/build/components/SafeAreaView/index.js +1 -0
- package/build/components/Select/Select.js +3 -2
- package/build/components/Table/Table.context.d.ts +12 -0
- package/build/components/Table/Table.context.js +9 -0
- package/build/components/Table/Table.d.ts +6 -0
- package/build/components/Table/Table.js +71 -0
- package/build/components/Table/Table.props.d.ts +56 -0
- package/build/components/Table/Table.props.js +1 -0
- package/build/components/Table/Table.utils.d.ts +5 -0
- package/build/components/Table/Table.utils.js +48 -0
- package/build/components/Table/Table.utils.test.d.ts +1 -0
- package/build/components/Table/Table.utils.test.js +71 -0
- package/build/components/Table/TableBody.d.ts +6 -0
- package/build/components/Table/TableBody.js +16 -0
- package/build/components/Table/TableCell.d.ts +10 -0
- package/build/components/Table/TableCell.js +44 -0
- package/build/components/Table/TableHeader.d.ts +6 -0
- package/build/components/Table/TableHeader.js +24 -0
- package/build/components/Table/TableHeaderCell.d.ts +10 -0
- package/build/components/Table/TableHeaderCell.js +97 -0
- package/build/components/Table/TablePagination.d.ts +6 -0
- package/build/components/Table/TablePagination.js +7 -0
- package/build/components/Table/TableRow.d.ts +8 -0
- package/build/components/Table/TableRow.js +25 -0
- package/build/components/Table/index.d.ts +8 -0
- package/build/components/Table/index.js +7 -0
- package/build/components/Timeline/Timeline.d.ts +6 -0
- package/build/components/Timeline/Timeline.js +34 -0
- package/build/components/Timeline/Timeline.props.d.ts +47 -0
- package/build/components/Timeline/Timeline.props.js +1 -0
- package/build/components/Timeline/TimelineItem.d.ts +6 -0
- package/build/components/Timeline/TimelineItem.js +235 -0
- package/build/components/Timeline/index.d.ts +3 -0
- package/build/components/Timeline/index.js +2 -0
- package/build/components/index.d.ts +5 -0
- package/build/components/index.js +5 -0
- package/build/tokens/components/dark/timeline.d.ts +2 -2
- package/build/tokens/components/dark/timeline.js +2 -2
- package/docs/components/AllComponents.web.tsx +106 -23
- package/docs/llm-docs/unistyles-llms-full.txt +1132 -534
- package/docs/llm-docs/unistyles-llms-small.txt +37 -37
- package/package.json +2 -2
- package/src/components/Combobox/Combobox.context.ts +26 -0
- package/src/components/Combobox/Combobox.docs.mdx +277 -0
- package/src/components/Combobox/Combobox.figma.tsx +60 -0
- package/src/components/Combobox/Combobox.props.ts +187 -0
- package/src/components/Combobox/Combobox.stories.tsx +233 -0
- package/src/components/Combobox/Combobox.tsx +446 -0
- package/src/components/Combobox/ComboboxOption.tsx +100 -0
- package/src/components/Combobox/index.ts +9 -0
- package/src/components/Modal/Modal.tsx +52 -74
- package/src/components/Modal/Modal.web.tsx +3 -3
- package/src/components/Pagination/Pagination.docs.mdx +99 -0
- package/src/components/Pagination/Pagination.figma.tsx +20 -0
- package/src/components/Pagination/Pagination.props.ts +28 -0
- package/src/components/Pagination/Pagination.stories.tsx +88 -0
- package/src/components/Pagination/Pagination.tsx +248 -0
- package/src/components/Pagination/Pagination.utils.test.ts +20 -0
- package/src/components/Pagination/Pagination.utils.ts +37 -0
- package/src/components/Pagination/index.ts +2 -0
- package/src/components/SafeAreaView/SafeAreaView.props.ts +20 -0
- package/src/components/SafeAreaView/SafeAreaView.tsx +173 -0
- package/src/components/SafeAreaView/index.ts +2 -0
- package/src/components/Select/Select.tsx +30 -27
- package/src/components/Table/Table.context.tsx +23 -0
- package/src/components/Table/Table.docs.mdx +239 -0
- package/src/components/Table/Table.figma.tsx +65 -0
- package/src/components/Table/Table.props.ts +65 -0
- package/src/components/Table/Table.stories.tsx +399 -0
- package/src/components/Table/Table.tsx +127 -0
- package/src/components/Table/Table.utils.test.ts +82 -0
- package/src/components/Table/Table.utils.ts +72 -0
- package/src/components/Table/TableBody.tsx +25 -0
- package/src/components/Table/TableCell.tsx +67 -0
- package/src/components/Table/TableHeader.tsx +41 -0
- package/src/components/Table/TableHeaderCell.tsx +136 -0
- package/src/components/Table/TablePagination.tsx +10 -0
- package/src/components/Table/TableRow.tsx +42 -0
- package/src/components/Table/index.ts +16 -0
- package/src/components/Timeline/Timeline.docs.mdx +177 -0
- package/src/components/Timeline/Timeline.figma.tsx +89 -0
- package/src/components/Timeline/Timeline.props.ts +51 -0
- package/src/components/Timeline/Timeline.stories.tsx +102 -0
- package/src/components/Timeline/Timeline.tsx +48 -0
- package/src/components/Timeline/TimelineItem.tsx +293 -0
- package/src/components/Timeline/index.ts +9 -0
- package/src/components/index.ts +5 -0
- package/src/tokens/components/dark/timeline.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utilitywarehouse/hearth-react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Utility Warehouse React Native UI library",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"vitest": "^3.2.4",
|
|
59
59
|
"@utilitywarehouse/hearth-fonts": "^0.0.4",
|
|
60
60
|
"@utilitywarehouse/hearth-react-icons": "^0.8.0",
|
|
61
|
-
"@utilitywarehouse/hearth-react-native-icons": "^0.8.0",
|
|
62
61
|
"@utilitywarehouse/hearth-svg-assets": "^0.5.0",
|
|
62
|
+
"@utilitywarehouse/hearth-react-native-icons": "^0.8.0",
|
|
63
63
|
"@utilitywarehouse/hearth-tokens": "^0.2.4"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ComboboxSelection {
|
|
4
|
+
label: string;
|
|
5
|
+
value: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ComboboxContextValue {
|
|
9
|
+
close: () => void;
|
|
10
|
+
search: string;
|
|
11
|
+
selectedValue?: string | null;
|
|
12
|
+
selectOption: (option: ComboboxSelection) => void;
|
|
13
|
+
setSearch: (value: string) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const ComboboxContext = createContext<ComboboxContextValue | undefined>(undefined);
|
|
17
|
+
|
|
18
|
+
export const useComboboxContext = () => {
|
|
19
|
+
const context = useContext(ComboboxContext);
|
|
20
|
+
|
|
21
|
+
if (!context) {
|
|
22
|
+
throw new Error('useComboboxContext must be used within a Combobox');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return context;
|
|
26
|
+
};
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import { BottomSheetFlatList, Box, Center, Combobox, ComboboxOption } from '../../';
|
|
3
|
+
import { BackToTopButton, UsageWrap, ViewFigmaButton } from '../../../docs/components';
|
|
4
|
+
import * as Stories from './Combobox.stories';
|
|
5
|
+
|
|
6
|
+
<Meta title="Forms / Combobox" />
|
|
7
|
+
|
|
8
|
+
<BackToTopButton />
|
|
9
|
+
|
|
10
|
+
<ViewFigmaButton url="https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=9359-2923&t=c7xg5X0N2EL0t87h-4" />
|
|
11
|
+
|
|
12
|
+
# Combobox
|
|
13
|
+
|
|
14
|
+
The `Combobox` component lets people search and select from a list inside a bottom sheet. It keeps the trigger text and the bottom sheet search input in sync, and can render either the built-in options list or fully custom sheet content.
|
|
15
|
+
|
|
16
|
+
- [Playground](#playground)
|
|
17
|
+
- [Usage](#usage)
|
|
18
|
+
- [Props](#props)
|
|
19
|
+
- [Examples](#examples)
|
|
20
|
+
- [Accessibility](#accessibility)
|
|
21
|
+
|
|
22
|
+
## Playground
|
|
23
|
+
|
|
24
|
+
<Canvas of={Stories.Playground} />
|
|
25
|
+
|
|
26
|
+
<Controls of={Stories.Playground} />
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
<UsageWrap>
|
|
31
|
+
<Center>
|
|
32
|
+
<Combobox
|
|
33
|
+
label="Country"
|
|
34
|
+
placeholder="Search for a country"
|
|
35
|
+
searchPlaceholder="Search for a country"
|
|
36
|
+
options={[
|
|
37
|
+
{ label: 'United Kingdom', value: 'uk' },
|
|
38
|
+
{ label: 'United States', value: 'us' },
|
|
39
|
+
{ label: 'Canada', value: 'ca' },
|
|
40
|
+
]}
|
|
41
|
+
value="uk"
|
|
42
|
+
/>
|
|
43
|
+
</Center>
|
|
44
|
+
</UsageWrap>
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { Combobox } from '@utilitywarehouse/hearth-react-native';
|
|
48
|
+
import { useState } from 'react';
|
|
49
|
+
|
|
50
|
+
const countries = [
|
|
51
|
+
{ label: 'United Kingdom', value: 'uk' },
|
|
52
|
+
{ label: 'United States', value: 'us' },
|
|
53
|
+
{ label: 'Canada', value: 'ca' },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const MyComponent = () => {
|
|
57
|
+
const [value, setValue] = useState<string | null>(null);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Combobox
|
|
61
|
+
label="Country"
|
|
62
|
+
placeholder="Search for a country"
|
|
63
|
+
searchPlaceholder="Search for a country"
|
|
64
|
+
options={countries}
|
|
65
|
+
value={value}
|
|
66
|
+
onValueChange={setValue}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Props
|
|
73
|
+
|
|
74
|
+
The `Combobox` component inherits all of the React Native [`View` props](https://reactnative.dev/docs/view).
|
|
75
|
+
|
|
76
|
+
| Prop | Type | Default | Description |
|
|
77
|
+
| -------------------- | ----------------------------------- | -------------------- | ---------------------------------------------------------------------------- |
|
|
78
|
+
| `options` | `ComboboxOptionItemProps[]` | `[]` | Array of options for the built-in bottom sheet list. |
|
|
79
|
+
| `value` | `string \| null` | `-` | Currently selected value. |
|
|
80
|
+
| `onValueChange` | `(value: string \| null) => void` | `-` | Callback when the selected value changes. |
|
|
81
|
+
| `inputValue` | `string` | `-` | Controlled search value for the trigger and bottom sheet input. |
|
|
82
|
+
| `onInputValueChange` | `(value: string) => void` | `-` | Callback when the search value changes. |
|
|
83
|
+
| `placeholder` | `string` | `'Search'` | Placeholder shown in the trigger when empty. |
|
|
84
|
+
| `searchPlaceholder` | `string` | `'Search'` | Placeholder shown in the bottom sheet search input. |
|
|
85
|
+
| `menuHeading` | `string` | `-` | Optional heading shown at the top of the bottom sheet. |
|
|
86
|
+
| `children` | `ReactNode \| (props) => ReactNode` | `-` | Custom bottom sheet content, or a render function for custom list rendering. |
|
|
87
|
+
| `loading` | `boolean` | `false` | Displays a loading spinner in the trigger and sheet input. |
|
|
88
|
+
| `noOptionsFoundText` | `string` | `'No options found'` | Empty state text for the built-in options list. |
|
|
89
|
+
| `getValueLabel` | `(value) => string` | `-` | Resolve a label for selected values that are not present in `options`. |
|
|
90
|
+
| `filterOption` | `(option, search) => boolean` | `-` | Override the default label and keyword filtering logic. |
|
|
91
|
+
|
|
92
|
+
## Examples
|
|
93
|
+
|
|
94
|
+
### Static Searchable List
|
|
95
|
+
|
|
96
|
+
<Canvas of={Stories.StaticSearchableList} />
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { Combobox } from '@utilitywarehouse/hearth-react-native';
|
|
100
|
+
import { useState } from 'react';
|
|
101
|
+
|
|
102
|
+
const countries = [
|
|
103
|
+
{ label: 'United Kingdom', value: 'uk', keywords: ['britain', 'england'] },
|
|
104
|
+
{ label: 'United States', value: 'us', keywords: ['america', 'usa'] },
|
|
105
|
+
{ label: 'Canada', value: 'ca' },
|
|
106
|
+
{ label: 'Australia', value: 'au' },
|
|
107
|
+
{ label: 'France', value: 'fr' },
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
const MyComponent = () => {
|
|
111
|
+
const [value, setValue] = useState<string | null>(null);
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<Combobox
|
|
115
|
+
label="Country"
|
|
116
|
+
helperText="Search a fixed list of countries"
|
|
117
|
+
placeholder="Search for a country"
|
|
118
|
+
searchPlaceholder="Search for a country"
|
|
119
|
+
menuHeading="Choose a country"
|
|
120
|
+
options={countries}
|
|
121
|
+
value={value}
|
|
122
|
+
onValueChange={setValue}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Dynamic Items
|
|
129
|
+
|
|
130
|
+
<Canvas of={Stories.DynamicItems} />
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import { Combobox } from '@utilitywarehouse/hearth-react-native';
|
|
134
|
+
import { useEffect, useState } from 'react';
|
|
135
|
+
|
|
136
|
+
const allProducts = [
|
|
137
|
+
{ label: 'Broadband', value: 'broadband' },
|
|
138
|
+
{ label: 'Energy', value: 'energy' },
|
|
139
|
+
{ label: 'Mobile', value: 'mobile' },
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
const MyComponent = () => {
|
|
143
|
+
const [value, setValue] = useState<string | null>(null);
|
|
144
|
+
const [inputValue, setInputValue] = useState('');
|
|
145
|
+
const [loading, setLoading] = useState(false);
|
|
146
|
+
const [options, setOptions] = useState(allProducts);
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
setLoading(true);
|
|
150
|
+
|
|
151
|
+
const timeout = setTimeout(() => {
|
|
152
|
+
const query = inputValue.trim().toLowerCase();
|
|
153
|
+
setOptions(allProducts.filter(option => option.label.toLowerCase().includes(query)));
|
|
154
|
+
setLoading(false);
|
|
155
|
+
}, 300);
|
|
156
|
+
|
|
157
|
+
return () => clearTimeout(timeout);
|
|
158
|
+
}, [inputValue]);
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<Combobox
|
|
162
|
+
label="Products"
|
|
163
|
+
options={options}
|
|
164
|
+
value={value}
|
|
165
|
+
onValueChange={setValue}
|
|
166
|
+
inputValue={inputValue}
|
|
167
|
+
onInputValueChange={setInputValue}
|
|
168
|
+
loading={loading}
|
|
169
|
+
getValueLabel={selectedValue =>
|
|
170
|
+
allProducts.find(option => option.value === selectedValue)?.label ?? ''
|
|
171
|
+
}
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Custom Bottom Sheet Flat List
|
|
178
|
+
|
|
179
|
+
<Canvas of={Stories.CustomFlatList} />
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
import {
|
|
183
|
+
BottomSheetFlatList,
|
|
184
|
+
Combobox,
|
|
185
|
+
ComboboxOption,
|
|
186
|
+
} from '@utilitywarehouse/hearth-react-native';
|
|
187
|
+
|
|
188
|
+
const cities = [
|
|
189
|
+
{ label: 'London', value: 'london' },
|
|
190
|
+
{ label: 'Liverpool', value: 'liverpool' },
|
|
191
|
+
{ label: 'Leeds', value: 'leeds' },
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const MyComponent = () => {
|
|
195
|
+
return (
|
|
196
|
+
<Combobox label="Cities" placeholder="Search a city">
|
|
197
|
+
{({ search }) => {
|
|
198
|
+
const query = search.trim().toLowerCase();
|
|
199
|
+
const filteredCities = cities.filter(option => option.label.toLowerCase().includes(query));
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<BottomSheetFlatList
|
|
203
|
+
data={filteredCities}
|
|
204
|
+
keyExtractor={item => item.value}
|
|
205
|
+
renderItem={({ item }) => <ComboboxOption label={item.label} value={item.value} />}
|
|
206
|
+
/>
|
|
207
|
+
);
|
|
208
|
+
}}
|
|
209
|
+
</Combobox>
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Inline Custom Content
|
|
215
|
+
|
|
216
|
+
<UsageWrap>
|
|
217
|
+
<Center>
|
|
218
|
+
<Combobox label="Inline options" placeholder="Search for a country">
|
|
219
|
+
<Box>
|
|
220
|
+
<ComboboxOption label="United Kingdom" value="uk" />
|
|
221
|
+
<ComboboxOption label="United States" value="us" />
|
|
222
|
+
<ComboboxOption label="Canada" value="ca" />
|
|
223
|
+
</Box>
|
|
224
|
+
</Combobox>
|
|
225
|
+
</Center>
|
|
226
|
+
</UsageWrap>
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
import { Box, Combobox, ComboboxOption } from '@utilitywarehouse/hearth-react-native';
|
|
230
|
+
import { useState } from 'react';
|
|
231
|
+
|
|
232
|
+
const countries = [
|
|
233
|
+
{ label: 'United Kingdom', value: 'uk' },
|
|
234
|
+
{ label: 'United States', value: 'us' },
|
|
235
|
+
{ label: 'Canada', value: 'ca' },
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
const MyComponent = () => {
|
|
239
|
+
const [value, setValue] = useState<string | null>(null);
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<Combobox
|
|
243
|
+
label="Inline options"
|
|
244
|
+
helperText="Compose your own sheet content using ComboboxOption"
|
|
245
|
+
placeholder="Search a country"
|
|
246
|
+
searchPlaceholder="Search a country"
|
|
247
|
+
value={value}
|
|
248
|
+
onValueChange={setValue}
|
|
249
|
+
>
|
|
250
|
+
<Box>
|
|
251
|
+
{countries.map(option => (
|
|
252
|
+
<ComboboxOption key={option.value} label={option.label} value={option.value} />
|
|
253
|
+
))}
|
|
254
|
+
</Box>
|
|
255
|
+
</Combobox>
|
|
256
|
+
);
|
|
257
|
+
};
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Accessibility
|
|
261
|
+
|
|
262
|
+
The `Combobox` is implemented as a button that opens a bottom sheet. By default:
|
|
263
|
+
|
|
264
|
+
- The trigger uses an accessible label derived from the `label` prop (and, where appropriate, the current value or `placeholder`) and is exposed with `accessibilityRole="button"`.
|
|
265
|
+
- When the bottom sheet is open, the trigger reflects its state via `accessibilityState={{ expanded: true }}`; when the sheet is closed, it reports `expanded: false`. Screen readers should announce this state change as “expanded” / “collapsed”.
|
|
266
|
+
- The clear control (shown when a value is selected and clearing is allowed) is exposed as a separate button with an accessibility label such as “Clear selection”, so that screen reader users understand its purpose.
|
|
267
|
+
|
|
268
|
+
When you rely on the built‑in options list via the `options` prop, each option is rendered with appropriate roles and labels so that it can be discovered and activated with assistive technology.
|
|
269
|
+
|
|
270
|
+
When you provide fully custom sheet content instead of using `ComboboxOption`:
|
|
271
|
+
|
|
272
|
+
- Make sure interactive elements inside the sheet have the correct `accessibilityRole` (for example, `button` or `checkbox`) and meaningful `accessibilityLabel`s.
|
|
273
|
+
- Ensure that the primary search field inside the sheet remains focusable and clearly labelled with `searchPlaceholder` or a custom accessible label.
|
|
274
|
+
- Provide clear labelling or headings for grouped content, and ensure focus order inside the sheet is logical.
|
|
275
|
+
- Keep a visible and accessible way to dismiss the sheet (for example, a close button with an appropriate accessibility label).
|
|
276
|
+
|
|
277
|
+
These practices help ensure that the combobox remains understandable and operable for screen reader and keyboard users, regardless of whether you use the built‑in options list or custom content.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import figma from '@figma/code-connect';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Combobox } from '../';
|
|
4
|
+
|
|
5
|
+
figma.connect(
|
|
6
|
+
Combobox,
|
|
7
|
+
'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=9359%3A2923',
|
|
8
|
+
{
|
|
9
|
+
props: {
|
|
10
|
+
label: figma.string('Label'),
|
|
11
|
+
placeholder: figma.enum('Value type', {
|
|
12
|
+
Empty: figma.string('Value'),
|
|
13
|
+
}),
|
|
14
|
+
disabled: figma.enum('Variant', {
|
|
15
|
+
Disabled: true,
|
|
16
|
+
}),
|
|
17
|
+
validationStatus: figma.enum('Variant', {
|
|
18
|
+
Invalid: 'invalid',
|
|
19
|
+
}),
|
|
20
|
+
invalidText: figma.enum('Variant', {
|
|
21
|
+
Invalid: figma.string('Validation'),
|
|
22
|
+
}),
|
|
23
|
+
helperText: figma.boolean('Helper text?', {
|
|
24
|
+
true: figma.string('Helper text'),
|
|
25
|
+
}),
|
|
26
|
+
required: figma.boolean('Optional?', {
|
|
27
|
+
true: false,
|
|
28
|
+
}),
|
|
29
|
+
loading: figma.enum('Variant', {
|
|
30
|
+
Loading: true,
|
|
31
|
+
}),
|
|
32
|
+
value: figma.enum('Value type', {
|
|
33
|
+
Filled: figma.string('Value'),
|
|
34
|
+
}),
|
|
35
|
+
},
|
|
36
|
+
example: props => {
|
|
37
|
+
const [value, setValue] = useState<string | null>(props.value ? '1' : null);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Combobox
|
|
41
|
+
label={props.label}
|
|
42
|
+
placeholder={props.placeholder}
|
|
43
|
+
disabled={props.disabled}
|
|
44
|
+
validationStatus={props.validationStatus}
|
|
45
|
+
invalidText={props.invalidText}
|
|
46
|
+
helperText={props.helperText}
|
|
47
|
+
required={props.required}
|
|
48
|
+
loading={props.loading}
|
|
49
|
+
options={[
|
|
50
|
+
{ label: 'Option 1', value: '1' },
|
|
51
|
+
{ label: 'Option 2', value: '2' },
|
|
52
|
+
{ label: 'Option 3', value: '3' },
|
|
53
|
+
]}
|
|
54
|
+
value={value}
|
|
55
|
+
onValueChange={setValue}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
);
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { BottomSheetFlatListProps } from '@gorhom/bottom-sheet/lib/typescript/components/bottomSheetScrollable/types';
|
|
2
|
+
import { ComponentType, ReactNode } from 'react';
|
|
3
|
+
import { ViewProps } from 'react-native';
|
|
4
|
+
import { BottomSheetProps } from '../BottomSheet';
|
|
5
|
+
import { ComboboxSelection } from './Combobox.context';
|
|
6
|
+
|
|
7
|
+
type ValidationStatus = 'initial' | 'valid' | 'invalid';
|
|
8
|
+
|
|
9
|
+
export interface ComboboxOptionItemProps {
|
|
10
|
+
/**
|
|
11
|
+
* Label shown for this option.
|
|
12
|
+
*/
|
|
13
|
+
label: string;
|
|
14
|
+
/**
|
|
15
|
+
* Value returned when this option is selected.
|
|
16
|
+
*/
|
|
17
|
+
value: string;
|
|
18
|
+
/**
|
|
19
|
+
* Optional icon shown before the option label.
|
|
20
|
+
*/
|
|
21
|
+
leadingIcon?: ComponentType;
|
|
22
|
+
/**
|
|
23
|
+
* Optional icon shown after the option label.
|
|
24
|
+
*/
|
|
25
|
+
trailingIcon?: ComponentType;
|
|
26
|
+
/**
|
|
27
|
+
* Additional searchable terms for this option.
|
|
28
|
+
*/
|
|
29
|
+
keywords?: string[];
|
|
30
|
+
/**
|
|
31
|
+
* Whether this option is disabled.
|
|
32
|
+
*/
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ComboboxRenderContentProps {
|
|
37
|
+
/**
|
|
38
|
+
* Current search value used by both the trigger and bottom sheet input.
|
|
39
|
+
*/
|
|
40
|
+
search: string;
|
|
41
|
+
/**
|
|
42
|
+
* Update the search value.
|
|
43
|
+
*/
|
|
44
|
+
setSearch: (value: string) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Close the bottom sheet.
|
|
47
|
+
*/
|
|
48
|
+
close: () => void;
|
|
49
|
+
/**
|
|
50
|
+
* Select an option, update the trigger text and close the bottom sheet.
|
|
51
|
+
*/
|
|
52
|
+
selectOption: (option: ComboboxSelection) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Currently selected value.
|
|
55
|
+
*/
|
|
56
|
+
selectedValue?: string | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ComboboxOptionProps extends ComboboxOptionItemProps {
|
|
60
|
+
/**
|
|
61
|
+
* Whether this option is selected.
|
|
62
|
+
*/
|
|
63
|
+
selected?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Custom press handler for this option.
|
|
66
|
+
*/
|
|
67
|
+
onPress?: (value: string) => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type ComboboxChildren = ReactNode | ((props: ComboboxRenderContentProps) => ReactNode);
|
|
71
|
+
|
|
72
|
+
interface ComboboxProps extends Omit<ViewProps, 'children'> {
|
|
73
|
+
/**
|
|
74
|
+
* Array of options to render using the default bottom sheet list.
|
|
75
|
+
*/
|
|
76
|
+
options?: ComboboxOptionItemProps[];
|
|
77
|
+
/**
|
|
78
|
+
* Currently selected value.
|
|
79
|
+
*/
|
|
80
|
+
value?: string | null;
|
|
81
|
+
/**
|
|
82
|
+
* Callback fired when the selected value changes.
|
|
83
|
+
*/
|
|
84
|
+
onValueChange?: (value: string | null) => void;
|
|
85
|
+
/**
|
|
86
|
+
* Controlled search value used by the trigger and bottom sheet input.
|
|
87
|
+
*/
|
|
88
|
+
inputValue?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Callback fired when the search value changes.
|
|
91
|
+
*/
|
|
92
|
+
onInputValueChange?: (value: string) => void;
|
|
93
|
+
/**
|
|
94
|
+
* Label shown above the combobox.
|
|
95
|
+
*/
|
|
96
|
+
label?: string;
|
|
97
|
+
/**
|
|
98
|
+
* The variant of the label text.
|
|
99
|
+
*
|
|
100
|
+
* @default 'body'
|
|
101
|
+
*/
|
|
102
|
+
labelVariant?: 'heading' | 'body';
|
|
103
|
+
/**
|
|
104
|
+
* Helper text shown below the label.
|
|
105
|
+
*/
|
|
106
|
+
helperText?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Optional helper icon.
|
|
109
|
+
*/
|
|
110
|
+
helperIcon?: ComponentType;
|
|
111
|
+
/**
|
|
112
|
+
* Text shown for invalid state.
|
|
113
|
+
*/
|
|
114
|
+
invalidText?: string;
|
|
115
|
+
/**
|
|
116
|
+
* Text shown for valid state.
|
|
117
|
+
*/
|
|
118
|
+
validText?: string;
|
|
119
|
+
/**
|
|
120
|
+
* Placeholder shown when the combobox is empty.
|
|
121
|
+
*
|
|
122
|
+
* @default 'Search'
|
|
123
|
+
*/
|
|
124
|
+
placeholder?: string;
|
|
125
|
+
/**
|
|
126
|
+
* Placeholder shown in the bottom sheet search input.
|
|
127
|
+
*
|
|
128
|
+
* @default 'Search'
|
|
129
|
+
*/
|
|
130
|
+
searchPlaceholder?: string;
|
|
131
|
+
/**
|
|
132
|
+
* Whether the combobox is disabled.
|
|
133
|
+
*/
|
|
134
|
+
disabled?: boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Validation status for the combobox.
|
|
137
|
+
*
|
|
138
|
+
* @default 'initial'
|
|
139
|
+
*/
|
|
140
|
+
validationStatus?: ValidationStatus;
|
|
141
|
+
/**
|
|
142
|
+
* Whether the combobox is required.
|
|
143
|
+
*
|
|
144
|
+
* @default true
|
|
145
|
+
*/
|
|
146
|
+
required?: boolean;
|
|
147
|
+
/**
|
|
148
|
+
* Heading displayed above the bottom sheet search input.
|
|
149
|
+
*/
|
|
150
|
+
menuHeading?: string;
|
|
151
|
+
/**
|
|
152
|
+
* Custom bottom sheet content, or a render function for fully custom list rendering.
|
|
153
|
+
*/
|
|
154
|
+
children?: ComboboxChildren;
|
|
155
|
+
/**
|
|
156
|
+
* Props passed to the BottomSheetModal.
|
|
157
|
+
*/
|
|
158
|
+
bottomSheetProps?: Partial<BottomSheetProps>;
|
|
159
|
+
/**
|
|
160
|
+
* Text shown when no options match the current search.
|
|
161
|
+
*
|
|
162
|
+
* @default 'No options found'
|
|
163
|
+
*/
|
|
164
|
+
noOptionsFoundText?: string;
|
|
165
|
+
/**
|
|
166
|
+
* Props passed to the default BottomSheetFlatList renderer.
|
|
167
|
+
*/
|
|
168
|
+
listProps?: Partial<BottomSheetFlatListProps<ComboboxOptionItemProps>>;
|
|
169
|
+
/**
|
|
170
|
+
* Whether the combobox should show a loading spinner.
|
|
171
|
+
*/
|
|
172
|
+
loading?: boolean;
|
|
173
|
+
/**
|
|
174
|
+
* Whether the combobox is readonly.
|
|
175
|
+
*/
|
|
176
|
+
readonly?: boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Resolve a label for a selected value when the current options array does not contain it.
|
|
179
|
+
*/
|
|
180
|
+
getValueLabel?: (value: string | null | undefined) => string;
|
|
181
|
+
/**
|
|
182
|
+
* Override the default filtering behaviour for the default options list.
|
|
183
|
+
*/
|
|
184
|
+
filterOption?: (option: ComboboxOptionItemProps, search: string) => boolean;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export default ComboboxProps;
|