@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.
Files changed (87) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +15 -18
  3. package/CHANGELOG.md +62 -0
  4. package/build/components/Card/Card.props.d.ts +1 -1
  5. package/build/components/Card/CardRoot.js +19 -0
  6. package/build/components/Input/Input.js +13 -31
  7. package/build/components/Rating/Rating.d.ts +6 -0
  8. package/build/components/Rating/Rating.js +76 -0
  9. package/build/components/Rating/Rating.props.d.ts +18 -0
  10. package/build/components/Rating/Rating.props.js +1 -0
  11. package/build/components/Rating/RatingStarEmpty.d.ts +6 -0
  12. package/build/components/Rating/RatingStarEmpty.js +9 -0
  13. package/build/components/Rating/RatingStarFilled.d.ts +6 -0
  14. package/build/components/Rating/RatingStarFilled.js +9 -0
  15. package/build/components/Rating/index.d.ts +2 -0
  16. package/build/components/Rating/index.js +1 -0
  17. package/build/components/Roundel/Roundel.d.ts +6 -0
  18. package/build/components/Roundel/Roundel.js +40 -0
  19. package/build/components/Roundel/Roundel.props.d.ts +6 -0
  20. package/build/components/Roundel/Roundel.props.js +1 -0
  21. package/build/components/Roundel/index.d.ts +2 -0
  22. package/build/components/Roundel/index.js +1 -0
  23. package/build/components/StepperInput/StepperButton.d.ts +22 -0
  24. package/build/components/StepperInput/StepperButton.js +55 -0
  25. package/build/components/StepperInput/StepperInput.d.ts +6 -0
  26. package/build/components/StepperInput/StepperInput.js +179 -0
  27. package/build/components/StepperInput/StepperInput.props.d.ts +31 -0
  28. package/build/components/StepperInput/StepperInput.props.js +1 -0
  29. package/build/components/StepperInput/index.d.ts +2 -0
  30. package/build/components/StepperInput/index.js +1 -0
  31. package/build/components/Textarea/Textarea.d.ts +1 -1
  32. package/build/components/Textarea/Textarea.js +21 -32
  33. package/build/components/Textarea/Textarea.props.d.ts +11 -0
  34. package/build/components/VerificationInput/VerificationInput.js +12 -22
  35. package/build/components/index.d.ts +3 -0
  36. package/build/components/index.js +3 -0
  37. package/build/hooks/index.d.ts +1 -0
  38. package/build/hooks/index.js +1 -0
  39. package/build/hooks/useFormFieldAccessibility.d.ts +17 -0
  40. package/build/hooks/useFormFieldAccessibility.js +32 -0
  41. package/build/hooks/useFormFieldAccessibility.test.d.ts +1 -0
  42. package/build/hooks/useFormFieldAccessibility.test.js +56 -0
  43. package/docs/adding-shadows.mdx +2 -2
  44. package/docs/changelog.mdx +16 -0
  45. package/docs/components/AllComponents.web.tsx +30 -1
  46. package/docs/dark-mode-best-practice.mdx +328 -0
  47. package/package.json +6 -4
  48. package/src/components/Banner/Banner.stories.tsx +14 -0
  49. package/src/components/Card/Card.docs.mdx +16 -17
  50. package/src/components/Card/Card.props.ts +1 -0
  51. package/src/components/Card/Card.stories.tsx +35 -21
  52. package/src/components/Card/CardRoot.tsx +19 -0
  53. package/src/components/Icon/Icon.docs.mdx +1 -1
  54. package/src/components/Input/Input.tsx +14 -35
  55. package/src/components/List/List.docs.mdx +4 -2
  56. package/src/components/Modal/Modal.docs.mdx +58 -4
  57. package/src/components/NavModal/NavModal.docs.mdx +2 -2
  58. package/src/components/Rating/Rating.docs.mdx +178 -0
  59. package/src/components/Rating/Rating.figma.tsx +20 -0
  60. package/src/components/Rating/Rating.props.ts +22 -0
  61. package/src/components/Rating/Rating.stories.tsx +95 -0
  62. package/src/components/Rating/Rating.tsx +140 -0
  63. package/src/components/Rating/RatingStarEmpty.tsx +22 -0
  64. package/src/components/Rating/RatingStarFilled.tsx +27 -0
  65. package/src/components/Rating/index.ts +2 -0
  66. package/src/components/Roundel/Roundel.docs.mdx +48 -0
  67. package/src/components/Roundel/Roundel.figma.tsx +17 -0
  68. package/src/components/Roundel/Roundel.props.ts +8 -0
  69. package/src/components/Roundel/Roundel.stories.tsx +49 -0
  70. package/src/components/Roundel/Roundel.tsx +51 -0
  71. package/src/components/Roundel/index.ts +2 -0
  72. package/src/components/StepperInput/StepperButton.tsx +83 -0
  73. package/src/components/StepperInput/StepperInput.docs.mdx +121 -0
  74. package/src/components/StepperInput/StepperInput.figma.tsx +45 -0
  75. package/src/components/StepperInput/StepperInput.props.ts +39 -0
  76. package/src/components/StepperInput/StepperInput.stories.tsx +270 -0
  77. package/src/components/StepperInput/StepperInput.tsx +322 -0
  78. package/src/components/StepperInput/index.ts +2 -0
  79. package/src/components/Textarea/Textarea.docs.mdx +2 -0
  80. package/src/components/Textarea/Textarea.props.ts +11 -0
  81. package/src/components/Textarea/Textarea.stories.tsx +14 -0
  82. package/src/components/Textarea/Textarea.tsx +22 -34
  83. package/src/components/VerificationInput/VerificationInput.tsx +13 -25
  84. package/src/components/index.ts +3 -0
  85. package/src/hooks/index.ts +1 -0
  86. package/src/hooks/useFormFieldAccessibility.test.tsx +74 -0
  87. package/src/hooks/useFormFieldAccessibility.ts +67 -0
@@ -1,5 +1,6 @@
1
- import { Meta, StoryObj } from '@storybook/react-vite';
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="Purple - Subtle">
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="Purple - Emphasis">
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 Blue - Subtle">
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 Blue - Emphasis">
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 Green - Subtle">
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 Green - Emphasis">
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 Rose - Subtle">
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 Rose - Emphasis">
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 Orange - Subtle">
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 Orange - Emphasis">
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 Lilac - Subtle">
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 Lilac - Emphasis">
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 Pink - Subtle">
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 Pink - Emphasis">
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 reccomend that you use the `Icon` component to ensure that the icons are styled correctly and consistently across your application.
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={getAccessibilityLabel()}
192
- accessibilityHint={getAccessibilityHint()}
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 [`BodyText` component](?path=/docs/typography-body-text--docs).
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 [`BodyText` component](?path=/docs/typography-body-text--docs).
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 the `BottomSheetModal` component and includes pre-configured layouts for common modal patterns including headers, content areas, and action buttons.
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 without navigating away from the current screen.
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-navmodal">`NavModal`</StorybookLink> instead.
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 underlying BottomSheet functionality, please refer to the [BottomSheet documentation](./BottomSheet.docs.mdx) and the [official documentation](https://gorhom.dev/react-native-bottom-sheet/).
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 / NavModal" />
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
- # NavModal
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;