@utilitywarehouse/hearth-react-native 0.31.1 → 0.32.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 +1 -1
- package/.turbo/turbo-lint.log +15 -18
- package/CHANGELOG.md +62 -0
- package/build/components/Card/Card.props.d.ts +1 -1
- package/build/components/Card/CardRoot.js +19 -0
- package/build/components/Input/Input.js +13 -31
- package/build/components/Rating/Rating.d.ts +6 -0
- package/build/components/Rating/Rating.js +76 -0
- package/build/components/Rating/Rating.props.d.ts +18 -0
- package/build/components/Rating/Rating.props.js +1 -0
- package/build/components/Rating/RatingStarEmpty.d.ts +6 -0
- package/build/components/Rating/RatingStarEmpty.js +9 -0
- package/build/components/Rating/RatingStarFilled.d.ts +6 -0
- package/build/components/Rating/RatingStarFilled.js +9 -0
- package/build/components/Rating/index.d.ts +2 -0
- package/build/components/Rating/index.js +1 -0
- package/build/components/Roundel/Roundel.d.ts +6 -0
- package/build/components/Roundel/Roundel.js +40 -0
- package/build/components/Roundel/Roundel.props.d.ts +6 -0
- package/build/components/Roundel/Roundel.props.js +1 -0
- package/build/components/Roundel/index.d.ts +2 -0
- package/build/components/Roundel/index.js +1 -0
- package/build/components/StepperInput/StepperButton.d.ts +22 -0
- package/build/components/StepperInput/StepperButton.js +55 -0
- package/build/components/StepperInput/StepperInput.d.ts +6 -0
- package/build/components/StepperInput/StepperInput.js +179 -0
- package/build/components/StepperInput/StepperInput.props.d.ts +31 -0
- package/build/components/StepperInput/StepperInput.props.js +1 -0
- package/build/components/StepperInput/index.d.ts +2 -0
- package/build/components/StepperInput/index.js +1 -0
- package/build/components/Textarea/Textarea.d.ts +1 -1
- package/build/components/Textarea/Textarea.js +21 -32
- package/build/components/Textarea/Textarea.props.d.ts +11 -0
- package/build/components/VerificationInput/VerificationInput.js +12 -22
- package/build/components/index.d.ts +3 -0
- package/build/components/index.js +3 -0
- package/build/hooks/index.d.ts +1 -0
- package/build/hooks/index.js +1 -0
- package/build/hooks/useFormFieldAccessibility.d.ts +17 -0
- package/build/hooks/useFormFieldAccessibility.js +32 -0
- package/build/hooks/useFormFieldAccessibility.test.d.ts +1 -0
- package/build/hooks/useFormFieldAccessibility.test.js +56 -0
- package/docs/adding-shadows.mdx +2 -2
- package/docs/changelog.mdx +16 -0
- package/docs/components/AllComponents.web.tsx +30 -1
- package/docs/dark-mode-best-practice.mdx +328 -0
- package/package.json +6 -4
- package/src/components/Banner/Banner.stories.tsx +14 -0
- package/src/components/Card/Card.docs.mdx +16 -17
- package/src/components/Card/Card.props.ts +1 -0
- package/src/components/Card/Card.stories.tsx +35 -21
- package/src/components/Card/CardRoot.tsx +19 -0
- package/src/components/Icon/Icon.docs.mdx +1 -1
- package/src/components/Input/Input.tsx +14 -35
- package/src/components/List/List.docs.mdx +4 -2
- package/src/components/Modal/Modal.docs.mdx +58 -4
- package/src/components/NavModal/NavModal.docs.mdx +2 -2
- package/src/components/Rating/Rating.docs.mdx +178 -0
- package/src/components/Rating/Rating.figma.tsx +20 -0
- package/src/components/Rating/Rating.props.ts +22 -0
- package/src/components/Rating/Rating.stories.tsx +95 -0
- package/src/components/Rating/Rating.tsx +140 -0
- package/src/components/Rating/RatingStarEmpty.tsx +22 -0
- package/src/components/Rating/RatingStarFilled.tsx +27 -0
- package/src/components/Rating/index.ts +2 -0
- package/src/components/Roundel/Roundel.docs.mdx +48 -0
- package/src/components/Roundel/Roundel.figma.tsx +17 -0
- package/src/components/Roundel/Roundel.props.ts +8 -0
- package/src/components/Roundel/Roundel.stories.tsx +49 -0
- package/src/components/Roundel/Roundel.tsx +51 -0
- package/src/components/Roundel/index.ts +2 -0
- package/src/components/StepperInput/StepperButton.tsx +83 -0
- package/src/components/StepperInput/StepperInput.docs.mdx +121 -0
- package/src/components/StepperInput/StepperInput.figma.tsx +45 -0
- package/src/components/StepperInput/StepperInput.props.ts +39 -0
- package/src/components/StepperInput/StepperInput.stories.tsx +270 -0
- package/src/components/StepperInput/StepperInput.tsx +322 -0
- package/src/components/StepperInput/index.ts +2 -0
- package/src/components/Textarea/Textarea.docs.mdx +2 -0
- package/src/components/Textarea/Textarea.props.ts +11 -0
- package/src/components/Textarea/Textarea.stories.tsx +14 -0
- package/src/components/Textarea/Textarea.tsx +22 -34
- package/src/components/VerificationInput/VerificationInput.tsx +13 -25
- package/src/components/index.ts +3 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useFormFieldAccessibility.test.tsx +74 -0
- package/src/hooks/useFormFieldAccessibility.ts +67 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react-
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-native';
|
|
2
2
|
import { BellMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
3
|
+
import { ComponentProps } from 'react';
|
|
3
4
|
import { Card, CardAction, CardActions, CardPressHandler } from '.';
|
|
4
5
|
import { VariantTitle } from '../../../docs/components';
|
|
5
6
|
import { BodyText } from '../BodyText';
|
|
@@ -7,6 +8,8 @@ import { Button } from '../Button';
|
|
|
7
8
|
import { Flex } from '../Flex';
|
|
8
9
|
import { Heading } from '../Heading';
|
|
9
10
|
|
|
11
|
+
type CardStoryProps = ComponentProps<typeof Card>;
|
|
12
|
+
|
|
10
13
|
const meta = {
|
|
11
14
|
title: 'Stories / Card',
|
|
12
15
|
component: Card,
|
|
@@ -31,6 +34,7 @@ const meta = {
|
|
|
31
34
|
'brand',
|
|
32
35
|
'energy',
|
|
33
36
|
'broadband',
|
|
37
|
+
'highlight',
|
|
34
38
|
'mobile',
|
|
35
39
|
'insurance',
|
|
36
40
|
'cashback',
|
|
@@ -65,7 +69,7 @@ export default meta;
|
|
|
65
69
|
type Story = StoryObj<typeof meta>;
|
|
66
70
|
|
|
67
71
|
export const Playground: Story = {
|
|
68
|
-
render: ({ children, ...props }) => {
|
|
72
|
+
render: ({ children, ...props }: CardStoryProps) => {
|
|
69
73
|
return (
|
|
70
74
|
<Card {...props}>
|
|
71
75
|
<BodyText>{children as string}</BodyText>
|
|
@@ -75,7 +79,7 @@ export const Playground: Story = {
|
|
|
75
79
|
};
|
|
76
80
|
|
|
77
81
|
export const WithActions: Story = {
|
|
78
|
-
render: ({ children, ...props }) => {
|
|
82
|
+
render: ({ children, ...props }: CardStoryProps) => {
|
|
79
83
|
return (
|
|
80
84
|
<Flex gap="400">
|
|
81
85
|
<Card
|
|
@@ -139,7 +143,7 @@ export const WithOnlyAction: Story = {
|
|
|
139
143
|
args: {
|
|
140
144
|
variant: 'emphasis',
|
|
141
145
|
},
|
|
142
|
-
render: ({ ...props }) => {
|
|
146
|
+
render: ({ ...props }: CardStoryProps) => {
|
|
143
147
|
return (
|
|
144
148
|
<Card {...props} flexDirection="column" alignItems="stretch" spacing="md">
|
|
145
149
|
<CardActions>
|
|
@@ -159,7 +163,7 @@ export const Variants: Story = {
|
|
|
159
163
|
parameters: {
|
|
160
164
|
controls: { exclude: ['variant', 'colorScheme'] },
|
|
161
165
|
},
|
|
162
|
-
render: ({ children, ...props }) => {
|
|
166
|
+
render: ({ children, ...props }: CardStoryProps) => {
|
|
163
167
|
return (
|
|
164
168
|
<Flex spacing="lg">
|
|
165
169
|
<VariantTitle title="Subtle - White">
|
|
@@ -182,76 +186,86 @@ export const Variants: Story = {
|
|
|
182
186
|
<BodyText>{children as string}</BodyText>
|
|
183
187
|
</Card>
|
|
184
188
|
</VariantTitle>
|
|
185
|
-
<VariantTitle title="
|
|
189
|
+
<VariantTitle title="Brand - Subtle">
|
|
186
190
|
<Card {...props} colorScheme="brand" variant="subtle">
|
|
187
191
|
<BodyText>{children as string}</BodyText>
|
|
188
192
|
</Card>
|
|
189
193
|
</VariantTitle>
|
|
190
|
-
<VariantTitle title="
|
|
194
|
+
<VariantTitle title="Brand - Emphasis">
|
|
191
195
|
<Card {...props} colorScheme="brand" variant="emphasis">
|
|
192
196
|
<BodyText inverted>{children as string}</BodyText>
|
|
193
197
|
</Card>
|
|
194
198
|
</VariantTitle>
|
|
195
|
-
<VariantTitle title="Energy
|
|
199
|
+
<VariantTitle title="Energy - Subtle">
|
|
196
200
|
<Card {...props} colorScheme="energy" variant="subtle">
|
|
197
201
|
<BodyText>{children as string}</BodyText>
|
|
198
202
|
</Card>
|
|
199
203
|
</VariantTitle>
|
|
200
|
-
<VariantTitle title="Energy
|
|
204
|
+
<VariantTitle title="Energy - Emphasis">
|
|
201
205
|
<Card {...props} colorScheme="energy" variant="emphasis">
|
|
202
206
|
<BodyText>{children as string}</BodyText>
|
|
203
207
|
</Card>
|
|
204
208
|
</VariantTitle>
|
|
205
|
-
<VariantTitle title="Broadband
|
|
209
|
+
<VariantTitle title="Broadband - Subtle">
|
|
206
210
|
<Card {...props} colorScheme="broadband" variant="subtle">
|
|
207
211
|
<BodyText>{children as string}</BodyText>
|
|
208
212
|
</Card>
|
|
209
213
|
</VariantTitle>
|
|
210
|
-
<VariantTitle title="Broadband
|
|
214
|
+
<VariantTitle title="Broadband - Emphasis">
|
|
211
215
|
<Card {...props} colorScheme="broadband" variant="emphasis">
|
|
212
216
|
<BodyText>{children as string}</BodyText>
|
|
213
217
|
</Card>
|
|
214
218
|
</VariantTitle>
|
|
215
|
-
<VariantTitle title="Mobile
|
|
219
|
+
<VariantTitle title="Mobile - Subtle">
|
|
216
220
|
<Card {...props} colorScheme="mobile" variant="subtle">
|
|
217
221
|
<BodyText>{children as string}</BodyText>
|
|
218
222
|
</Card>
|
|
219
223
|
</VariantTitle>
|
|
220
|
-
<VariantTitle title="Mobile
|
|
224
|
+
<VariantTitle title="Mobile - Emphasis">
|
|
221
225
|
<Card {...props} colorScheme="mobile" variant="emphasis">
|
|
222
226
|
<BodyText>{children as string}</BodyText>
|
|
223
227
|
</Card>
|
|
224
228
|
</VariantTitle>
|
|
225
|
-
<VariantTitle title="Insurance
|
|
229
|
+
<VariantTitle title="Insurance - Subtle">
|
|
226
230
|
<Card {...props} colorScheme="insurance" variant="subtle">
|
|
227
231
|
<BodyText>{children as string}</BodyText>
|
|
228
232
|
</Card>
|
|
229
233
|
</VariantTitle>
|
|
230
|
-
<VariantTitle title="Insurance
|
|
234
|
+
<VariantTitle title="Insurance - Emphasis">
|
|
231
235
|
<Card {...props} colorScheme="insurance" variant="emphasis">
|
|
232
236
|
<BodyText>{children as string}</BodyText>
|
|
233
237
|
</Card>
|
|
234
238
|
</VariantTitle>
|
|
235
|
-
<VariantTitle title="Cashback
|
|
239
|
+
<VariantTitle title="Cashback - Subtle">
|
|
236
240
|
<Card {...props} colorScheme="cashback" variant="subtle">
|
|
237
241
|
<BodyText>{children as string}</BodyText>
|
|
238
242
|
</Card>
|
|
239
243
|
</VariantTitle>
|
|
240
|
-
<VariantTitle title="Cashback
|
|
244
|
+
<VariantTitle title="Cashback - Emphasis">
|
|
241
245
|
<Card {...props} colorScheme="cashback" variant="emphasis">
|
|
242
246
|
<BodyText>{children as string}</BodyText>
|
|
243
247
|
</Card>
|
|
244
248
|
</VariantTitle>
|
|
245
|
-
<VariantTitle title="Piggy
|
|
249
|
+
<VariantTitle title="Piggy - Subtle">
|
|
246
250
|
<Card {...props} colorScheme="pig" variant="subtle">
|
|
247
251
|
<BodyText>{children as string}</BodyText>
|
|
248
252
|
</Card>
|
|
249
253
|
</VariantTitle>
|
|
250
|
-
<VariantTitle title="Piggy
|
|
254
|
+
<VariantTitle title="Piggy - Emphasis">
|
|
251
255
|
<Card {...props} colorScheme="pig" variant="emphasis">
|
|
252
256
|
<BodyText>{children as string}</BodyText>
|
|
253
257
|
</Card>
|
|
254
258
|
</VariantTitle>
|
|
259
|
+
<VariantTitle title="Highlight - Subtle">
|
|
260
|
+
<Card {...props} colorScheme="highlight" variant="subtle">
|
|
261
|
+
<BodyText>{children as string}</BodyText>
|
|
262
|
+
</Card>
|
|
263
|
+
</VariantTitle>
|
|
264
|
+
<VariantTitle title="Highlight - Emphasis">
|
|
265
|
+
<Card {...props} colorScheme="highlight" variant="emphasis">
|
|
266
|
+
<BodyText>{children as string}</BodyText>
|
|
267
|
+
</Card>
|
|
268
|
+
</VariantTitle>
|
|
255
269
|
</Flex>
|
|
256
270
|
);
|
|
257
271
|
},
|
|
@@ -264,7 +278,7 @@ export const WithShadow: Story = {
|
|
|
264
278
|
parameters: {
|
|
265
279
|
controls: { exclude: ['variant'] },
|
|
266
280
|
},
|
|
267
|
-
render: ({ children, ...props }) => {
|
|
281
|
+
render: ({ children, ...props }: CardStoryProps) => {
|
|
268
282
|
return (
|
|
269
283
|
<Flex spacing="lg">
|
|
270
284
|
<VariantTitle title="Subtle - White - Shadow">
|
|
@@ -286,7 +300,7 @@ export const Interactive: Story = {
|
|
|
286
300
|
parameters: {
|
|
287
301
|
controls: { exclude: ['variant', 'colorScheme'] },
|
|
288
302
|
},
|
|
289
|
-
render: ({ children, ...props }) => {
|
|
303
|
+
render: ({ children, ...props }: CardStoryProps) => {
|
|
290
304
|
return (
|
|
291
305
|
<Flex spacing="lg">
|
|
292
306
|
<VariantTitle title="Pressable - Subtle - White">
|
|
@@ -164,6 +164,9 @@ const styles = StyleSheet.create(theme => ({
|
|
|
164
164
|
pig: {
|
|
165
165
|
borderWidth: theme.components.card.brand.borderWidth,
|
|
166
166
|
},
|
|
167
|
+
highlight: {
|
|
168
|
+
borderWidth: theme.components.card.brand.borderWidth,
|
|
169
|
+
},
|
|
167
170
|
},
|
|
168
171
|
shadowColor: {
|
|
169
172
|
functional: {
|
|
@@ -331,6 +334,22 @@ const styles = StyleSheet.create(theme => ({
|
|
|
331
334
|
borderColor: theme.color.border.strong,
|
|
332
335
|
},
|
|
333
336
|
},
|
|
337
|
+
{
|
|
338
|
+
variant: 'subtle',
|
|
339
|
+
colorScheme: 'highlight',
|
|
340
|
+
styles: {
|
|
341
|
+
backgroundColor: theme.color.surface.highlight.subtle,
|
|
342
|
+
borderColor: theme.color.border.strong,
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
variant: 'emphasis',
|
|
347
|
+
colorScheme: 'highlight',
|
|
348
|
+
styles: {
|
|
349
|
+
backgroundColor: theme.color.surface.highlight.default,
|
|
350
|
+
borderColor: theme.color.border.strong,
|
|
351
|
+
},
|
|
352
|
+
},
|
|
334
353
|
],
|
|
335
354
|
},
|
|
336
355
|
}));
|
|
@@ -34,7 +34,7 @@ Icons are often used to enhance the usability and accessibility of digital produ
|
|
|
34
34
|
You can either use the React Native components directy from our `@utilitywarehouse/hearth-react-native-icons` package or use the `Icon`
|
|
35
35
|
component from our `@utilitywarehouse/hearth-react-native` package to render the icons with utility props such as `color`.
|
|
36
36
|
|
|
37
|
-
We
|
|
37
|
+
We recommend that you use the `Icon` component to ensure that the icons are styled correctly and consistently across your application.
|
|
38
38
|
The `Icon` component also provides additional functionality such as handling different color modes and sizes as well as automatically applying
|
|
39
39
|
light and dark mode colors from the theme.
|
|
40
40
|
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
EyeSmallIcon,
|
|
10
10
|
SearchMediumIcon,
|
|
11
11
|
} from '@utilitywarehouse/hearth-react-native-icons';
|
|
12
|
-
import { useTheme } from '../../hooks';
|
|
12
|
+
import { useFormFieldAccessibility, useTheme } from '../../hooks';
|
|
13
13
|
import { BodyText } from '../BodyText';
|
|
14
14
|
import { FormField, useFormFieldContext } from '../FormField';
|
|
15
15
|
import { Spinner } from '../Spinner';
|
|
@@ -75,7 +75,7 @@ const Input = forwardRef<TextInput, InputProps>(
|
|
|
75
75
|
if (formFieldContext?.setShouldHandleAccessibility) {
|
|
76
76
|
formFieldContext.setShouldHandleAccessibility(true);
|
|
77
77
|
}
|
|
78
|
-
}, []);
|
|
78
|
+
}, [formFieldContext]);
|
|
79
79
|
|
|
80
80
|
const [fieldType, setFieldType] = useState<'password' | 'text'>(
|
|
81
81
|
type === 'password' ? 'password' : 'text'
|
|
@@ -88,6 +88,16 @@ const Input = forwardRef<TextInput, InputProps>(
|
|
|
88
88
|
|
|
89
89
|
const shouldShowPasswordToggle = type === 'password' && showPasswordToggle;
|
|
90
90
|
const shouldShowClear = clearable && !!(props as InputWithoutChildrenProps)?.value;
|
|
91
|
+
const { accessibilityHint, accessibilityLabel } = useFormFieldAccessibility({
|
|
92
|
+
label: inputLabel,
|
|
93
|
+
helperText: inputHelperText,
|
|
94
|
+
validText: inputValidText,
|
|
95
|
+
invalidText: inputInvalidText,
|
|
96
|
+
required: inputRequired,
|
|
97
|
+
validationStatus: inputValidationStatus,
|
|
98
|
+
fallbackLabel: props.accessibilityLabel,
|
|
99
|
+
fallbackHint: props.accessibilityHint,
|
|
100
|
+
});
|
|
91
101
|
|
|
92
102
|
const toggleFieldType = () => {
|
|
93
103
|
setFieldType(fieldType === 'password' ? 'text' : 'password');
|
|
@@ -107,37 +117,6 @@ const Input = forwardRef<TextInput, InputProps>(
|
|
|
107
117
|
return undefined;
|
|
108
118
|
})();
|
|
109
119
|
|
|
110
|
-
const getAccessibilityLabel = () => {
|
|
111
|
-
let accessibilityLabel = '';
|
|
112
|
-
if (inputLabel) {
|
|
113
|
-
accessibilityLabel = accessibilityLabel + inputLabel;
|
|
114
|
-
}
|
|
115
|
-
if (inputRequired) {
|
|
116
|
-
accessibilityLabel = accessibilityLabel + ', required';
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return accessibilityLabel || props.accessibilityLabel;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const getAccessibilityHint = () => {
|
|
123
|
-
let accessibilityHint = '';
|
|
124
|
-
if (inputHelperText) {
|
|
125
|
-
accessibilityHint = accessibilityHint + inputHelperText;
|
|
126
|
-
}
|
|
127
|
-
if (inputValidationStatus !== 'initial') {
|
|
128
|
-
if (accessibilityHint.length > 0) {
|
|
129
|
-
accessibilityHint = accessibilityHint + ', ';
|
|
130
|
-
}
|
|
131
|
-
if (inputValidationStatus === 'invalid' && inputInvalidText) {
|
|
132
|
-
accessibilityHint = accessibilityHint + inputInvalidText;
|
|
133
|
-
}
|
|
134
|
-
if (inputValidationStatus === 'valid' && inputValidText) {
|
|
135
|
-
accessibilityHint = accessibilityHint + inputValidText;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return accessibilityHint || props.accessibilityHint;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
120
|
return (
|
|
142
121
|
<FormField
|
|
143
122
|
label={label}
|
|
@@ -188,8 +167,8 @@ const Input = forwardRef<TextInput, InputProps>(
|
|
|
188
167
|
inputMode={getInputMode}
|
|
189
168
|
inBottomSheet={inBottomSheet}
|
|
190
169
|
{...props}
|
|
191
|
-
aria-label={
|
|
192
|
-
accessibilityHint={
|
|
170
|
+
aria-label={accessibilityLabel}
|
|
171
|
+
accessibilityHint={accessibilityHint}
|
|
193
172
|
/>
|
|
194
173
|
{shouldShowClear && (
|
|
195
174
|
<InputSlot>
|
|
@@ -32,6 +32,8 @@ import {
|
|
|
32
32
|
import { BackToTopButton, BadgeList, UsageWrap, ViewFigmaButton } from '../../../docs/components';
|
|
33
33
|
import * as Stories from './List.stories';
|
|
34
34
|
|
|
35
|
+
import StorybookLink from '../../../../../shared/storybook/StorybookLink';
|
|
36
|
+
|
|
35
37
|
<Meta title="Components / List" />
|
|
36
38
|
|
|
37
39
|
<ViewFigmaButton url="https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components-%26-Tokens?node-id=1-163" />
|
|
@@ -146,11 +148,11 @@ are ignored, so conditional list items will not affect which item loses the top
|
|
|
146
148
|
|
|
147
149
|
#### - `ListItemHeading`
|
|
148
150
|
|
|
149
|
-
Has all props of the
|
|
151
|
+
Has all props of the <StorybookLink to="typography-body-text">`BodyText` component</StorybookLink>.
|
|
150
152
|
|
|
151
153
|
#### - `ListItemHelperText`
|
|
152
154
|
|
|
153
|
-
Has all props of the
|
|
155
|
+
Has all props of the <StorybookLink to="typography-body-text">`BodyText` component</StorybookLink>.
|
|
154
156
|
|
|
155
157
|
#### - `ListItemTrailingContent`
|
|
156
158
|
|
|
@@ -12,11 +12,14 @@ import * as Stories from './Modal.stories';
|
|
|
12
12
|
|
|
13
13
|
# Modal
|
|
14
14
|
|
|
15
|
-
The `Modal` component provides a versatile dialog interface that slides up from the bottom of the screen. It's built on top of
|
|
15
|
+
The `Modal` component provides a versatile dialog interface that slides up from the bottom of the screen. It's built on top of
|
|
16
|
+
the <StorybookLink to="components-bottom-sheet">`BottomSheetModal`</StorybookLink>
|
|
17
|
+
component and includes pre-configured layouts for common modal patterns including headers, content areas, and action buttons.
|
|
16
18
|
|
|
17
|
-
The Modal component is ideal for displaying important information, collecting user input, or presenting choices that require user attention
|
|
19
|
+
The Modal component is ideal for displaying important information, collecting user input, or presenting choices that require user attention
|
|
20
|
+
without navigating away from the current screen.
|
|
18
21
|
|
|
19
|
-
If you need a modal layout inside a React Navigation modal screen, use <StorybookLink to="components-
|
|
22
|
+
If you need a modal layout inside a React Navigation modal screen, use <StorybookLink to="components-nav-modal">`NavModal`</StorybookLink> instead.
|
|
20
23
|
|
|
21
24
|
- [Playground](#playground)
|
|
22
25
|
- [Usage](#usage)
|
|
@@ -32,6 +35,8 @@ If you need a modal layout inside a React Navigation modal screen, use <Storyboo
|
|
|
32
35
|
- [Loading State](#loading-state)
|
|
33
36
|
- [Without Close Button](#without-close-button)
|
|
34
37
|
- [Single Action Modal](#single-action-modal)
|
|
38
|
+
- [Navigation Modals](#navigation-modals)
|
|
39
|
+
- [Close handlers and state management](#close-handlers-and-state-management)
|
|
35
40
|
- [Integration Notes](#integration-notes)
|
|
36
41
|
- [External Resources](#external-resources)
|
|
37
42
|
|
|
@@ -514,6 +519,53 @@ const AlertModal = () => {
|
|
|
514
519
|
|
|
515
520
|
For React Navigation modal screens, use <StorybookLink to="components-navmodal">`NavModal`</StorybookLink>. It contains the extracted screen-based modal layout, background variants, scrollable content handling, and the Android `triggerCloseAnimation()` ref used during navigation dismissal.
|
|
516
521
|
|
|
522
|
+
### Close handlers and state management
|
|
523
|
+
|
|
524
|
+
The Modal component provides multiple ways to handle closing the modal and managing state:
|
|
525
|
+
|
|
526
|
+
- Use the `onPressPrimaryButton`, `onPressSecondaryButton`, and `onPressCloseButton` props to run custom logic when buttons are pressed, such as form validation or API calls, before closing the modal.
|
|
527
|
+
- Control whether the modal should automatically close when action buttons are pressed using the `closeOnPrimaryButtonPress` and `closeOnSecondaryButtonPress` props. This allows you to keep the modal
|
|
528
|
+
open while performing async operations and only close it when those operations are complete.
|
|
529
|
+
- Use the `onChange` prop to detect when the modal is opened or closed based on the index parameter (0 for open, -1 for closed) and manage state accordingly.
|
|
530
|
+
|
|
531
|
+
```tsx
|
|
532
|
+
const MyModal = () => {
|
|
533
|
+
const modalRef = useRef<BottomSheetModal>(null);
|
|
534
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
535
|
+
|
|
536
|
+
const handleSubmit = async () => {
|
|
537
|
+
setIsSubmitting(true);
|
|
538
|
+
// Simulate API call
|
|
539
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
540
|
+
setIsSubmitting(false);
|
|
541
|
+
modalRef.current?.dismiss();
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
return (
|
|
545
|
+
<>
|
|
546
|
+
<Button onPress={() => modalRef.current?.present()}>Open Modal</Button>
|
|
547
|
+
|
|
548
|
+
<Modal
|
|
549
|
+
ref={modalRef}
|
|
550
|
+
heading="Submit Data"
|
|
551
|
+
description="Please confirm your submission"
|
|
552
|
+
primaryButtonText="Submit"
|
|
553
|
+
secondaryButtonText="Cancel"
|
|
554
|
+
onPressPrimaryButton={handleSubmit}
|
|
555
|
+
closeOnPrimaryButtonPress={false} // Keep modal open while submitting
|
|
556
|
+
onChange={index => {
|
|
557
|
+
if (index === -1) {
|
|
558
|
+
// Modal closed, reset state if needed
|
|
559
|
+
setIsSubmitting(false);
|
|
560
|
+
}
|
|
561
|
+
}}
|
|
562
|
+
loading={isSubmitting}
|
|
563
|
+
/>
|
|
564
|
+
</>
|
|
565
|
+
);
|
|
566
|
+
};
|
|
567
|
+
```
|
|
568
|
+
|
|
517
569
|
## Integration Notes
|
|
518
570
|
|
|
519
571
|
### BottomSheetModalProvider
|
|
@@ -567,4 +619,6 @@ modalRef.current?.snapToIndex(1);
|
|
|
567
619
|
|
|
568
620
|
## External Resources
|
|
569
621
|
|
|
570
|
-
This component is built on top of [@gorhom/bottom-sheet](https://gorhom.github.io/react-native-bottom-sheet/) (v5). For more information about the
|
|
622
|
+
This component is built on top of [@gorhom/bottom-sheet](https://gorhom.github.io/react-native-bottom-sheet/) (v5). For more information about the
|
|
623
|
+
underlying BottomSheet functionality, please refer to the <StorybookLink to="components-bottomsheet">BottomSheet documentation</StorybookLink> and
|
|
624
|
+
the [official documentation](https://gorhom.dev/react-native-bottom-sheet/).
|
|
@@ -6,13 +6,13 @@ import modaliOSVideo from '../../../docs/assets/modal-ios.mp4';
|
|
|
6
6
|
import { BackToTopButton, ViewFigmaButton } from '../../../docs/components';
|
|
7
7
|
import * as Stories from './NavModal.stories';
|
|
8
8
|
|
|
9
|
-
<Meta title="Components /
|
|
9
|
+
<Meta title="Components / Nav Modal" />
|
|
10
10
|
|
|
11
11
|
<ViewFigmaButton url="https://www.figma.com/design/dLI9bmyMr42LV7dtFeW27J/Hearth-Patterns---Guides?node-id=6314-9103&t=oq3NaPLaAu3di6Db-4" />
|
|
12
12
|
|
|
13
13
|
<BackToTopButton />
|
|
14
14
|
|
|
15
|
-
#
|
|
15
|
+
# Nav Modal
|
|
16
16
|
|
|
17
17
|
The `NavModal` component is the screen-based modal layout for navigation flows. Use it when a screen is already being presented by React Navigation with `presentation: 'modal'` or `presentation: 'fullScreenModal'` and you want Hearth's modal structure, actions, and Android close animation support.
|
|
18
18
|
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import { Box, Center, Rating } from '../..';
|
|
3
|
+
import { BackToTopButton, UsageWrap, ViewFigmaButton } from '../../../docs/components';
|
|
4
|
+
import * as Stories from './Rating.stories';
|
|
5
|
+
|
|
6
|
+
<Meta title="Components / Rating" />
|
|
7
|
+
|
|
8
|
+
<BackToTopButton />
|
|
9
|
+
|
|
10
|
+
<ViewFigmaButton url="https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=10620-4185" />
|
|
11
|
+
|
|
12
|
+
# Rating
|
|
13
|
+
|
|
14
|
+
Use Rating to collect a star-based score with an optional descriptive label.
|
|
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
|
+
<Box>
|
|
33
|
+
<Rating value={0} labels={{ 0: 'Not rated' }} />
|
|
34
|
+
</Box>
|
|
35
|
+
</Center>
|
|
36
|
+
</UsageWrap>
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { useState } from 'react';
|
|
40
|
+
import { Rating } from '@utilitywarehouse/hearth-react-native';
|
|
41
|
+
|
|
42
|
+
const MyComponent = () => {
|
|
43
|
+
const [rating, setRating] = useState<0 | 1 | 2 | 3 | 4 | 5>(0);
|
|
44
|
+
|
|
45
|
+
return <Rating value={rating} onChange={setRating} labels={{ 0: 'Not rated' }} />;
|
|
46
|
+
};
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Props
|
|
50
|
+
|
|
51
|
+
| Property | Type | Description | Default |
|
|
52
|
+
| -------------- | -------------------------------------- | ------------------------------------------- | ----------- |
|
|
53
|
+
| `value` | `0 \| 1 \| 2 \| 3 \| 4 \| 5` | Current rating value. | `0` |
|
|
54
|
+
| `defaultValue` | `0 \| 1 \| 2 \| 3 \| 4 \| 5` | Initial rating value when uncontrolled. | `0` |
|
|
55
|
+
| `onChange` | `(value: RatingValue) => void` | Called when a star is selected. | `undefined` |
|
|
56
|
+
| `disabled` | `boolean` | Disables the rating input. | `false` |
|
|
57
|
+
| `labels` | `Partial<Record<RatingValue, string>>` | Override labels for specific rating values. | `undefined` |
|
|
58
|
+
| `hideLabel` | `boolean` | Hide the label text below the stars. | `false` |
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
### Default rating
|
|
63
|
+
|
|
64
|
+
Use the default labels for a quick feedback prompt.
|
|
65
|
+
|
|
66
|
+
<UsageWrap>
|
|
67
|
+
<Center>
|
|
68
|
+
<Box>
|
|
69
|
+
<Rating value={3} />
|
|
70
|
+
</Box>
|
|
71
|
+
</Center>
|
|
72
|
+
</UsageWrap>
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import { Rating } from '@utilitywarehouse/hearth-react-native';
|
|
76
|
+
|
|
77
|
+
const MyComponent = () => <Rating value={3} />;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Empty rating
|
|
81
|
+
|
|
82
|
+
Use `value={0}` to show a zero-star state.
|
|
83
|
+
|
|
84
|
+
<UsageWrap>
|
|
85
|
+
<Center>
|
|
86
|
+
<Box>
|
|
87
|
+
<Rating value={0} labels={{ 0: 'Not rated' }} />
|
|
88
|
+
</Box>
|
|
89
|
+
</Center>
|
|
90
|
+
</UsageWrap>
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { Rating } from '@utilitywarehouse/hearth-react-native';
|
|
94
|
+
|
|
95
|
+
const MyComponent = () => <Rating value={0} labels={{ 0: 'Not rated' }} />;
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Custom labels
|
|
99
|
+
|
|
100
|
+
Provide custom copy for each rating value.
|
|
101
|
+
|
|
102
|
+
<UsageWrap>
|
|
103
|
+
<Center>
|
|
104
|
+
<Box>
|
|
105
|
+
<Rating
|
|
106
|
+
value={5}
|
|
107
|
+
labels={{
|
|
108
|
+
0: 'Not rated',
|
|
109
|
+
1: 'Terrible',
|
|
110
|
+
2: 'Poor',
|
|
111
|
+
3: 'OK',
|
|
112
|
+
4: 'Great',
|
|
113
|
+
5: 'Outstanding',
|
|
114
|
+
}}
|
|
115
|
+
/>
|
|
116
|
+
</Box>
|
|
117
|
+
</Center>
|
|
118
|
+
</UsageWrap>
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import { Rating } from '@utilitywarehouse/hearth-react-native';
|
|
122
|
+
|
|
123
|
+
const MyComponent = () => (
|
|
124
|
+
<Rating
|
|
125
|
+
value={5}
|
|
126
|
+
labels={{
|
|
127
|
+
0: 'Not rated',
|
|
128
|
+
1: 'Terrible',
|
|
129
|
+
2: 'Poor',
|
|
130
|
+
3: 'OK',
|
|
131
|
+
4: 'Great',
|
|
132
|
+
5: 'Outstanding',
|
|
133
|
+
}}
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Hidden label
|
|
139
|
+
|
|
140
|
+
Use `hideLabel` when the context already explains the rating.
|
|
141
|
+
|
|
142
|
+
<UsageWrap>
|
|
143
|
+
<Center>
|
|
144
|
+
<Box>
|
|
145
|
+
<Rating value={2} hideLabel />
|
|
146
|
+
</Box>
|
|
147
|
+
</Center>
|
|
148
|
+
</UsageWrap>
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { Rating } from '@utilitywarehouse/hearth-react-native';
|
|
152
|
+
|
|
153
|
+
const MyComponent = () => <Rating value={2} hideLabel />;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Disabled
|
|
157
|
+
|
|
158
|
+
Use `disabled` to show a read-only rating.
|
|
159
|
+
|
|
160
|
+
<UsageWrap>
|
|
161
|
+
<Center>
|
|
162
|
+
<Box>
|
|
163
|
+
<Rating value={4} disabled />
|
|
164
|
+
</Box>
|
|
165
|
+
</Center>
|
|
166
|
+
</UsageWrap>
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
import { Rating } from '@utilitywarehouse/hearth-react-native';
|
|
170
|
+
|
|
171
|
+
const MyComponent = () => <Rating value={4} disabled />;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Accessibility
|
|
175
|
+
|
|
176
|
+
- Rating uses a `radiogroup` container with `radio` items for each star.
|
|
177
|
+
- Each star announces a descriptive label (e.g., "Rate Okay"). Override labels with the `labels` prop to match your content.
|
|
178
|
+
- Provide `accessibilityLabel` when the default label text is not sufficient for your screen reader context.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import figma from '@figma/code-connect';
|
|
2
|
+
import { Rating } from '../';
|
|
3
|
+
|
|
4
|
+
figma.connect(
|
|
5
|
+
Rating,
|
|
6
|
+
'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=10620-4185',
|
|
7
|
+
{
|
|
8
|
+
props: {
|
|
9
|
+
value: figma.enum('Rating', {
|
|
10
|
+
'0 Star': 0,
|
|
11
|
+
'1 Star': 1,
|
|
12
|
+
'2 Star': 2,
|
|
13
|
+
'3 Star': 3,
|
|
14
|
+
'4 Star': 4,
|
|
15
|
+
'5 Star': 5,
|
|
16
|
+
}),
|
|
17
|
+
},
|
|
18
|
+
example: props => <Rating value={props.value} />,
|
|
19
|
+
}
|
|
20
|
+
);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ViewProps } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export type RatingValue = 0 | 1 | 2 | 3 | 4 | 5;
|
|
4
|
+
|
|
5
|
+
export type RatingLabels = Partial<Record<RatingValue, string>>;
|
|
6
|
+
|
|
7
|
+
export interface RatingProps extends Omit<ViewProps, 'children'> {
|
|
8
|
+
/** Current rating value. */
|
|
9
|
+
value?: RatingValue;
|
|
10
|
+
/** Initial rating value when uncontrolled. */
|
|
11
|
+
defaultValue?: RatingValue;
|
|
12
|
+
/** Called when a star is selected. */
|
|
13
|
+
onChange?: (value: RatingValue) => void;
|
|
14
|
+
/** Disables the rating input. */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
/** Override labels for specific rating values. */
|
|
17
|
+
labels?: RatingLabels;
|
|
18
|
+
/** Hide the label text below the stars. */
|
|
19
|
+
hideLabel?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default RatingProps;
|