@utilitywarehouse/hearth-react-native 0.27.2 → 0.28.0-testid-fix-1
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 +5 -4
- package/.turbo/turbo-lint.log +70 -69
- package/CHANGELOG.md +149 -0
- package/build/components/Button/ButtonRoot.js +8 -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/DatePicker/TimePicker.d.ts +3 -0
- package/build/components/DatePicker/TimePicker.js +84 -0
- package/build/components/DatePicker/time-picker/animated-math.d.ts +4 -0
- package/build/components/DatePicker/time-picker/animated-math.js +19 -0
- package/build/components/DatePicker/time-picker/period-native.d.ts +6 -0
- package/build/components/DatePicker/time-picker/period-native.js +17 -0
- package/build/components/DatePicker/time-picker/period-picker.d.ts +6 -0
- package/build/components/DatePicker/time-picker/period-picker.js +10 -0
- package/build/components/DatePicker/time-picker/period-web.d.ts +6 -0
- package/build/components/DatePicker/time-picker/period-web.js +21 -0
- package/build/components/DatePicker/time-picker/wheel-native.d.ts +8 -0
- package/build/components/DatePicker/time-picker/wheel-native.js +19 -0
- package/build/components/DatePicker/time-picker/wheel-picker/index.d.ts +2 -0
- package/build/components/DatePicker/time-picker/wheel-picker/index.js +2 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.d.ts +16 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.js +97 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.d.ts +21 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.js +88 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.d.ts +23 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.js +21 -0
- package/build/components/DatePicker/time-picker/wheel-web.d.ts +8 -0
- package/build/components/DatePicker/time-picker/wheel-web.js +146 -0
- package/build/components/DatePicker/time-picker/wheel.d.ts +8 -0
- package/build/components/DatePicker/time-picker/wheel.js +10 -0
- package/build/components/List/List.js +2 -2
- package/build/components/Modal/Modal.js +31 -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.d.ts +1 -1
- package/build/components/Select/Select.js +6 -5
- package/build/components/Select/Select.props.d.ts +4 -0
- package/build/components/Select/SelectOption.d.ts +1 -1
- package/build/components/Select/SelectOption.js +2 -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/VerificationInput/VerificationInput.js +3 -3
- 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 +4 -4
- package/src/components/Button/Button.stories.tsx +43 -7
- package/src/components/Button/ButtonRoot.tsx +8 -0
- 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/List/List.tsx +5 -4
- package/src/components/Modal/Modal.tsx +67 -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.props.ts +4 -0
- package/src/components/Select/Select.tsx +35 -28
- package/src/components/Select/SelectOption.tsx +2 -0
- 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/VerificationInput/VerificationInput.tsx +3 -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-testid-fix-1",
|
|
4
4
|
"description": "Utility Warehouse React Native UI library",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -56,11 +56,11 @@
|
|
|
56
56
|
"vite": "^7.1.3",
|
|
57
57
|
"vite-plugin-svgr": "^4.5.0",
|
|
58
58
|
"vitest": "^3.2.4",
|
|
59
|
-
"@utilitywarehouse/hearth-fonts": "^0.0.4",
|
|
60
|
-
"@utilitywarehouse/hearth-react-icons": "^0.8.0",
|
|
61
59
|
"@utilitywarehouse/hearth-react-native-icons": "^0.8.0",
|
|
60
|
+
"@utilitywarehouse/hearth-fonts": "^0.0.4",
|
|
62
61
|
"@utilitywarehouse/hearth-svg-assets": "^0.5.0",
|
|
63
|
-
"@utilitywarehouse/hearth-tokens": "^0.2.4"
|
|
62
|
+
"@utilitywarehouse/hearth-tokens": "^0.2.4",
|
|
63
|
+
"@utilitywarehouse/hearth-react-icons": "^0.8.0"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"@gorhom/bottom-sheet": "^5.0.0",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react-
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-native';
|
|
2
2
|
import * as Icons from '@utilitywarehouse/hearth-react-native-icons';
|
|
3
3
|
import { AddSmallIcon, ChevronRightSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
4
4
|
import { Platform } from 'react-native';
|
|
@@ -92,7 +92,7 @@ type Story = StoryObj<typeof meta>;
|
|
|
92
92
|
|
|
93
93
|
export const Playground: Story = {
|
|
94
94
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
|
95
|
-
render: ({ icon: _icon, children: _, ...args }) => {
|
|
95
|
+
render: ({ icon: _icon, children: _, ...args }: StoryObj<typeof meta.args>) => {
|
|
96
96
|
// @ts-expect-error - This is a playground
|
|
97
97
|
const icon = _icon === 'none' ? undefined : Icons[_icon];
|
|
98
98
|
return <Button {...args} icon={icon} />;
|
|
@@ -104,7 +104,7 @@ export const Variants: Story = {
|
|
|
104
104
|
controls: { exclude: ['variant'] },
|
|
105
105
|
},
|
|
106
106
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
|
107
|
-
render: ({ icon: _icon, children: _, ...args }) => {
|
|
107
|
+
render: ({ icon: _icon, children: _, ...args }: StoryObj<typeof meta.args>) => {
|
|
108
108
|
// @ts-expect-error - This is a playground
|
|
109
109
|
const icon = _icon === 'none' ? undefined : Icons[_icon];
|
|
110
110
|
return (
|
|
@@ -123,11 +123,9 @@ export const Variants: Story = {
|
|
|
123
123
|
{args.colorScheme !== 'highlight' && (
|
|
124
124
|
<>
|
|
125
125
|
<VariantTitle title="Outline" invert={args.inverted}>
|
|
126
|
-
{/* @ts-expect-error - story loop types don't match */}
|
|
127
126
|
<Button {...args} variant="outline" icon={icon} />
|
|
128
127
|
</VariantTitle>
|
|
129
128
|
<VariantTitle title="Ghost" invert={args.inverted}>
|
|
130
|
-
{/* @ts-expect-error - story loop types don't match */}
|
|
131
129
|
<Button {...args} variant="ghost" icon={icon} />
|
|
132
130
|
</VariantTitle>
|
|
133
131
|
</>
|
|
@@ -138,6 +136,44 @@ export const Variants: Story = {
|
|
|
138
136
|
},
|
|
139
137
|
};
|
|
140
138
|
|
|
139
|
+
export const PaddingNone: Story = {
|
|
140
|
+
parameters: {
|
|
141
|
+
controls: {
|
|
142
|
+
include: ['text', 'size', 'inverted', 'icon', 'iconPosition'],
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
|
146
|
+
render: ({ icon: _icon, children: _, ...args }: StoryObj<typeof meta.args>) => {
|
|
147
|
+
// @ts-expect-error - This is a playground
|
|
148
|
+
const icon = _icon === 'none' ? undefined : Icons[_icon];
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<Flex direction="column" spacing="lg">
|
|
152
|
+
<VariantTitle title="Default Padding" invert={args.inverted}>
|
|
153
|
+
<Flex direction="row" align="center" spacing="none">
|
|
154
|
+
<Box backgroundColor="brand" width="100" height="100" />
|
|
155
|
+
<Button
|
|
156
|
+
{...args}
|
|
157
|
+
colorScheme="functional"
|
|
158
|
+
variant="ghost"
|
|
159
|
+
icon={icon}
|
|
160
|
+
paddingNone={false}
|
|
161
|
+
/>
|
|
162
|
+
<Box backgroundColor="brand" width="100" height="100" />
|
|
163
|
+
</Flex>
|
|
164
|
+
</VariantTitle>
|
|
165
|
+
<VariantTitle title="No Padding (paddingNone)" invert={args.inverted}>
|
|
166
|
+
<Flex direction="row" align="center" spacing="none">
|
|
167
|
+
<Box backgroundColor="brand" width="100" height="100" />
|
|
168
|
+
<Button {...args} colorScheme="functional" variant="ghost" icon={icon} paddingNone />
|
|
169
|
+
<Box backgroundColor="brand" width="100" height="100" />
|
|
170
|
+
</Flex>
|
|
171
|
+
</VariantTitle>
|
|
172
|
+
</Flex>
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
141
177
|
type ColorScheme = ButtonProps['colorScheme'];
|
|
142
178
|
type Variant = ButtonProps['variant'];
|
|
143
179
|
|
|
@@ -145,7 +181,7 @@ export const KitchenSink: Story = {
|
|
|
145
181
|
parameters: {
|
|
146
182
|
controls: { include: ['text', 'size', 'inverted'] },
|
|
147
183
|
},
|
|
148
|
-
render: ({ text, inverted, size }) => {
|
|
184
|
+
render: ({ text, inverted, size }: StoryObj<typeof meta.args>) => {
|
|
149
185
|
const schemes: Array<ColorScheme> = ['highlight', 'destructive', 'affirmative', 'functional'];
|
|
150
186
|
const variants: Array<Variant> = ['emphasis', 'solid', 'outline', 'ghost'];
|
|
151
187
|
return (
|
|
@@ -174,7 +210,7 @@ export const KitchenSink: Story = {
|
|
|
174
210
|
.map(variant => (
|
|
175
211
|
<Box key={variant} mb="100">
|
|
176
212
|
<Box mb="100">
|
|
177
|
-
<DetailText size="lg"
|
|
213
|
+
<DetailText size="lg" inverted={inverted}>
|
|
178
214
|
{scheme} - {variant}
|
|
179
215
|
</DetailText>
|
|
180
216
|
</Box>
|
|
@@ -132,6 +132,14 @@ const styles = StyleSheet.create(theme => ({
|
|
|
132
132
|
paddingHorizontal: 0,
|
|
133
133
|
},
|
|
134
134
|
},
|
|
135
|
+
{
|
|
136
|
+
size: 'md',
|
|
137
|
+
paddingNone: true,
|
|
138
|
+
variant: 'ghost',
|
|
139
|
+
styles: {
|
|
140
|
+
paddingHorizontal: 0,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
135
143
|
// Variant Color Schemes
|
|
136
144
|
// Emphasis
|
|
137
145
|
// Emphasis Yellow
|
|
@@ -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;
|