@utilitywarehouse/hearth-react-native 0.29.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +13 -13
  3. package/CHANGELOG.md +82 -0
  4. package/build/components/BottomSheet/BottomSheet.context.d.ts +1 -0
  5. package/build/components/BottomSheet/BottomSheet.context.js +3 -1
  6. package/build/components/BottomSheet/BottomSheet.js +5 -1
  7. package/build/components/BottomSheet/BottomSheetFlatList.js +16 -2
  8. package/build/components/BottomSheet/BottomSheetModal.js +5 -1
  9. package/build/components/BottomSheet/BottomSheetModalProvider.d.ts +9 -0
  10. package/build/components/BottomSheet/BottomSheetModalProvider.js +14 -0
  11. package/build/components/BottomSheet/BottomSheetScrollView.js +16 -2
  12. package/build/components/BottomSheet/BottomSheetView.js +16 -2
  13. package/build/components/BottomSheet/index.d.ts +3 -1
  14. package/build/components/BottomSheet/index.js +2 -1
  15. package/build/components/Combobox/Combobox.js +3 -1
  16. package/build/components/DescriptionList/DescriptionListItem.js +1 -2
  17. package/build/components/ExpandableCard/ExpandableCardTriggerRoot.js +2 -2
  18. package/build/components/Heading/Heading.js +1 -1
  19. package/build/components/List/List.js +2 -2
  20. package/build/components/List/ListAction/ListAction.js +1 -1
  21. package/build/components/List/ListItem/ListItemRoot.js +2 -2
  22. package/build/components/Modal/Modal.d.ts +1 -1
  23. package/build/components/Modal/Modal.js +56 -10
  24. package/build/components/Modal/Modal.shared.types.d.ts +1 -0
  25. package/build/components/NavModal/NavModal.d.ts +1 -1
  26. package/build/components/NavModal/NavModal.js +2 -2
  27. package/build/components/Select/Select.js +3 -1
  28. package/docs/changelog.mdx +86 -0
  29. package/package.json +3 -3
  30. package/src/components/BottomSheet/BottomSheet.context.ts +4 -1
  31. package/src/components/BottomSheet/BottomSheet.docs.mdx +20 -0
  32. package/src/components/BottomSheet/BottomSheet.tsx +8 -1
  33. package/src/components/BottomSheet/BottomSheetFlatList.tsx +16 -2
  34. package/src/components/BottomSheet/BottomSheetModal.tsx +8 -1
  35. package/src/components/BottomSheet/BottomSheetModalProvider.tsx +33 -0
  36. package/src/components/BottomSheet/BottomSheetScrollView.tsx +16 -2
  37. package/src/components/BottomSheet/BottomSheetView.tsx +17 -2
  38. package/src/components/BottomSheet/index.ts +2 -1
  39. package/src/components/Combobox/Combobox.tsx +3 -1
  40. package/src/components/DescriptionList/DescriptionListItem.tsx +1 -2
  41. package/src/components/ExpandableCard/ExpandableCardTriggerRoot.tsx +4 -4
  42. package/src/components/Heading/Heading.docs.mdx +12 -3
  43. package/src/components/Heading/Heading.tsx +1 -0
  44. package/src/components/List/List.docs.mdx +2 -2
  45. package/src/components/List/List.stories.tsx +6 -5
  46. package/src/components/List/List.tsx +14 -2
  47. package/src/components/List/ListAction/ListAction.tsx +1 -0
  48. package/src/components/List/ListItem/ListItemRoot.tsx +3 -3
  49. package/src/components/Modal/Modal.docs.mdx +36 -21
  50. package/src/components/Modal/Modal.shared.types.ts +1 -0
  51. package/src/components/Modal/Modal.tsx +60 -9
  52. package/src/components/NavModal/NavModal.docs.mdx +1 -0
  53. package/src/components/NavModal/NavModal.tsx +10 -1
  54. package/src/components/Select/Select.tsx +3 -1
@@ -4,7 +4,7 @@ import {
4
4
  } from '@utilitywarehouse/hearth-react-native-icons';
5
5
  import { Pressable, ViewStyle } from 'react-native';
6
6
  import { StyleSheet } from 'react-native-unistyles';
7
- import { DetailText } from '../DetailText';
7
+ import { BodyText } from '../BodyText';
8
8
  import ExpandableCardContent from './ExpandableCardContent';
9
9
  import ExpandableCardHelperText from './ExpandableCardHelperText';
10
10
  import ExpandableCardIcon from './ExpandableCardIcon';
@@ -62,10 +62,10 @@ const ExpandableCardTriggerRoot = ({
62
62
  {helperText && <ExpandableCardHelperText>{helperText}</ExpandableCardHelperText>}
63
63
  {badgePosition === 'bottom' ? badge : null}
64
64
  </ExpandableCardContent>
65
- {numericValue && (
66
- <DetailText size="lg" style={styles.numericValue}>
65
+ {!!numericValue && (
66
+ <BodyText weight="semibold" style={styles.numericValue}>
67
67
  {numericValue}
68
- </DetailText>
68
+ </BodyText>
69
69
  )}
70
70
  <ExpandableCardTrailingContent style={styles.chevron}>
71
71
  <ExpandableCardTrailingIcon as={isExpanded ? ChevronUpSmallIcon : ChevronDownSmallIcon} />
@@ -1,7 +1,7 @@
1
- import { Meta, Canvas, Story, Controls, Primary } from '@storybook/addon-docs/blocks';
1
+ import { Canvas, Controls, Meta, Primary, Story } from '@storybook/addon-docs/blocks';
2
+ import { Box, Center, Heading, Pressable } from '../../';
3
+ import { BackToTopButton, UsageWrap, ViewFigmaButton } from '../../../docs/components';
2
4
  import * as Stories from './Heading.stories';
3
- import { Center, Pressable, Heading, Box } from '../../';
4
- import { UsageWrap, BackToTopButton, ViewFigmaButton } from '../../../docs/components';
5
5
 
6
6
  <Meta title="Typography / Heading" />
7
7
 
@@ -17,6 +17,7 @@ The `Heading` component gives you the ability to create headings for your screen
17
17
  - [Usage](#usage)
18
18
  - [Props](#props)
19
19
  - [Sizes](#sizes)
20
+ - [Accessibility](#accessibility)
20
21
 
21
22
  ## Playground
22
23
 
@@ -60,3 +61,11 @@ const MyComponent = () => <Heading>Welcome to Utility Warehouse</Heading>;
60
61
  The `Heading` component has different sizes to style the text.
61
62
 
62
63
  <Canvas of={Stories.KitchenSink} />
64
+
65
+ ## Accessibility
66
+
67
+ `Heading` sets `accessibilityRole="header"` by default, so VoiceOver and TalkBack can announce it as a heading without any extra configuration.
68
+ If you need to override this behavior, pass an explicit `accessibilityRole` prop (for example, `accessibilityRole="text"`) to opt out of heading semantics.
69
+
70
+ - Use `Heading` for actual screen or section titles so assistive technologies can expose the right document structure.
71
+ - If you need styled text without heading semantics, either override the `accessibilityRole` on `Heading` or use one of the other text components instead.
@@ -33,6 +33,7 @@ const Heading = ({
33
33
 
34
34
  return (
35
35
  <Text
36
+ accessibilityRole="header"
36
37
  {...remainingProps}
37
38
  {...(truncated
38
39
  ? {
@@ -383,7 +383,7 @@ const MyComponent = () => (
383
383
  trailingContent={
384
384
  <>
385
385
  <BodyText>-£100.00</BodyText>
386
- <BodyText color="textBrand">-£100.00</BodyText>
386
+ <BodyText color="brand">-£100.00</BodyText>
387
387
  </>
388
388
  }
389
389
  onPress={() => console.log('Transaction pressed')}
@@ -393,7 +393,7 @@ const MyComponent = () => (
393
393
  helperText="Apr 4, 2024"
394
394
  trailingContent={
395
395
  <>
396
- <BodyText color="textAffirmative">+£100.00</BodyText>
396
+ <BodyText color="affirmative">+£100.00</BodyText>
397
397
  </>
398
398
  }
399
399
  onPress={() => console.log('Transaction pressed')}
@@ -1,4 +1,4 @@
1
- import { Meta, StoryObj } from '@storybook/react-vite';
1
+ import { Meta, StoryObj } from '@storybook/react-native';
2
2
  import {
3
3
  BillMediumIcon,
4
4
  ChevronRightSmallIcon,
@@ -60,7 +60,7 @@ export default meta;
60
60
  type Story = StoryObj<typeof meta>;
61
61
 
62
62
  export const Playground: Story = {
63
- render: ({ container, ...args }) => {
63
+ render: ({ container, ...args }: StoryObj<typeof meta.args>) => {
64
64
  return (
65
65
  <List {...args} container={container}>
66
66
  {Array.from({ length: 4 }).map((_, index) => (
@@ -75,7 +75,7 @@ export const WithAction: Story = {
75
75
  args: {
76
76
  container: 'subtleWhite',
77
77
  },
78
- render: ({ container, ...args }) => (
78
+ render: ({ container, ...args }: StoryObj<typeof meta.args>) => (
79
79
  <List {...args} container={container}>
80
80
  {Array.from({ length: 4 }).map((_, index) => (
81
81
  <ListItem key={index} heading="List item text" helperText="Supporting text" />
@@ -292,6 +292,7 @@ export const WithListAction: Story = {
292
292
  heading="Manage payment methods"
293
293
  helperText="Update your credit or debit cards"
294
294
  onPress={() => console.log('Manage pressed')}
295
+ accessibilityRole="link"
295
296
  />
296
297
  <ListAction heading="Contact support" onPress={() => console.log('Contact pressed')} />
297
298
  </List>
@@ -310,7 +311,7 @@ export const WithTransactions: Story = {
310
311
  trailingContent={
311
312
  <>
312
313
  <BodyText>-£100.00</BodyText>
313
- <BodyText color="textBrand">+£1.00 CB</BodyText>
314
+ <BodyText color="brand">+£1.00 CB</BodyText>
314
315
  </>
315
316
  }
316
317
  onPress={() => console.log('Transaction pressed')}
@@ -320,7 +321,7 @@ export const WithTransactions: Story = {
320
321
  helperText="Apr 4, 2024"
321
322
  trailingContent={
322
323
  <>
323
- <BodyText color="textAffirmative">+£100.00</BodyText>
324
+ <BodyText color="affirmative">+£100.00</BodyText>
324
325
  </>
325
326
  }
326
327
  onPress={() => console.log('Transaction pressed')}
@@ -14,7 +14,15 @@ const List = ({
14
14
  invalidText,
15
15
  ...props
16
16
  }: ListProps) => {
17
- const { loading, disabled, container = 'none', testID, style, ...rest } = props;
17
+ const {
18
+ loading,
19
+ disabled,
20
+ container = 'none',
21
+ testID,
22
+ style,
23
+ accessibilityRole,
24
+ ...rest
25
+ } = props;
18
26
 
19
27
  const orderRef = useRef<string[]>([]);
20
28
  const [firstItemId, setFirstItemId] = useState<string | undefined>(undefined);
@@ -52,7 +60,11 @@ const List = ({
52
60
  styles.useVariants({ disabled });
53
61
  return (
54
62
  <ListContext.Provider value={value}>
55
- <View {...rest} style={[styles.container, style]}>
63
+ <View
64
+ {...rest}
65
+ accessibilityRole={accessibilityRole ?? 'list'}
66
+ style={[styles.container, style]}
67
+ >
56
68
  {heading ? (
57
69
  <SectionHeader
58
70
  heading={heading}
@@ -62,6 +62,7 @@ const ListActionRoot = ({
62
62
  return (
63
63
  <Pressable
64
64
  {...props}
65
+ accessibilityRole={props.accessibilityRole ?? 'button'}
65
66
  testID={testID}
66
67
  style={[styles.container, props.style as ViewStyle]}
67
68
  disabled={isDisabled || !onPress}
@@ -2,7 +2,7 @@ import { ChevronRightSmallIcon } from '@utilitywarehouse/hearth-react-native-ico
2
2
  import { useId, useLayoutEffect, useMemo } from 'react';
3
3
  import { Pressable, ViewStyle } from 'react-native';
4
4
  import { StyleSheet } from 'react-native-unistyles';
5
- import { DetailText } from '../../DetailText';
5
+ import { BodyText } from '../../BodyText';
6
6
  import { Skeleton } from '../../Skeleton';
7
7
  import { useListContext } from '../List.context';
8
8
  import { IListItemContext, ListItemContext } from './ListItem.context';
@@ -109,7 +109,7 @@ const ListItemRoot = ({
109
109
  testID={testID}
110
110
  style={[styles.container, props.style as ViewStyle]}
111
111
  disabled={isDisabled}
112
- accessibilityRole={onPress ? 'button' : undefined}
112
+ accessibilityRole={props.accessibilityRole ?? (onPress ? 'button' : undefined)}
113
113
  >
114
114
  {children ? (
115
115
  children
@@ -126,7 +126,7 @@ const ListItemRoot = ({
126
126
  ) : null}
127
127
  {badgePosition === 'bottom' && badge ? badge : null}
128
128
  </ListItemContent>
129
- {!!numericValue && <DetailText size="lg">{numericValue}</DetailText>}
129
+ {!!numericValue && <BodyText weight="semibold">{numericValue}</BodyText>}
130
130
  {trailingContent ? (
131
131
  <ListItemTrailingContent>{trailingContent}</ListItemTrailingContent>
132
132
  ) : onPress ? (
@@ -87,27 +87,28 @@ const MyComponent = () => {
87
87
 
88
88
  The Modal component extends the `BottomSheetModal` component and accepts all of its props, plus the following additional props:
89
89
 
90
- | Property | Type | Description | Default |
91
- | ----------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | -------------- |
92
- | `heading` | `string` | The heading text displayed at the top of the modal | - |
93
- | `description` | `string` | The description text displayed below the heading | - |
94
- | `showCloseButton` | `boolean` | Whether to show the close button in the top-right corner | `true` |
95
- | `primaryButtonText` | `string` | Text for the primary action button | - |
96
- | `secondaryButtonText` | `string` | Text for the secondary action button | - |
97
- | `onPressPrimaryButton` | `() => void` | Callback function called when the primary button is pressed | - |
98
- | `onPressSecondaryButton` | `() => void` | Callback function called when the secondary button is pressed | - |
99
- | `onPressCloseButton` | `() => void` | Callback function called when the close button is pressed | - |
100
- | `closeOnPrimaryButtonPress` | `boolean` | Whether to automatically close the modal when the primary button is pressed | `true` |
101
- | `closeOnSecondaryButtonPress` | `boolean` | Whether to automatically close the modal when the secondary button is pressed | `true` |
102
- | `onChange` | `(index: number, position: number, `<br />` type: number) => void` | Callback function called when the modal's position changes \* | - |
103
- | `loading` | `boolean` | Whether to show a loading state with spinner | `false` |
104
- | `loadingHeading` | `string` | The heading text to be displayed when loading is true. If not provided, the regular heading will be shown. | `'Loading...'` |
105
- | `image` | `ImageProps` | Image to display in the modal (shows as centered content with text below) | - |
106
- | `children` | `ReactNode` | Custom content to display in the modal body | - |
107
- | `primaryButtonProps` | `Omit<ButtonWithoutChildrenProps, 'children'>` | Additional props to pass to the primary button (colorScheme defaults to 'highlight', variant to 'solid') | - |
108
- | `secondaryButtonProps` | `Omit<ButtonWithoutChildrenProps, 'children'>` | Additional props to pass to the secondary button (colorScheme defaults to 'functional', variant to 'outline') | - |
109
- | `closeButtonProps` | `Omit<UnstyledIconButtonProps, 'children'>` | Additional props to pass to the close button | - |
110
- | `fullscreen` | `boolean` | Whether the modal should take up the full screen height | `false` |
90
+ | Property | Type | Description | Default |
91
+ | ----------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | -------------- |
92
+ | `heading` | `string` | The heading text displayed at the top of the modal | - |
93
+ | `description` | `string` | The description text displayed below the heading | - |
94
+ | `showCloseButton` | `boolean` | Whether to show the close button in the top-right corner | `true` |
95
+ | `primaryButtonText` | `string` | Text for the primary action button | - |
96
+ | `secondaryButtonText` | `string` | Text for the secondary action button | - |
97
+ | `onPressPrimaryButton` | `() => void` | Callback function called when the primary button is pressed | - |
98
+ | `onPressSecondaryButton` | `() => void` | Callback function called when the secondary button is pressed | - |
99
+ | `onPressCloseButton` | `() => void` | Callback function called when the close button is pressed | - |
100
+ | `closeOnPrimaryButtonPress` | `boolean` | Whether to automatically close the modal when the primary button is pressed | `true` |
101
+ | `closeOnSecondaryButtonPress` | `boolean` | Whether to automatically close the modal when the secondary button is pressed | `true` |
102
+ | `onChange` | `(index: number, position: number, `<br />` type: number) => void` | Callback function called when the modal's position changes \* | - |
103
+ | `loading` | `boolean` | Whether to show a loading state with spinner | `false` |
104
+ | `loadingHeading` | `string` | The heading text to be displayed when loading is true. If not provided, the regular heading will be shown. | `'Loading...'` |
105
+ | `loadingDescription` | `string` | The description text to be displayed when loading is true. If not provided, the regular description will be shown. | - |
106
+ | `image` | `ImageProps` | Image to display in the modal (shows as centered content with text below) | - |
107
+ | `children` | `ReactNode` | Custom content to display in the modal body | - |
108
+ | `primaryButtonProps` | `Omit<ButtonWithoutChildrenProps, 'children'>` | Additional props to pass to the primary button (colorScheme defaults to 'highlight', variant to 'solid') | - |
109
+ | `secondaryButtonProps` | `Omit<ButtonWithoutChildrenProps, 'children'>` | Additional props to pass to the secondary button (colorScheme defaults to 'functional', variant to 'outline') | - |
110
+ | `closeButtonProps` | `Omit<UnstyledIconButtonProps, 'children'>` | Additional props to pass to the close button | - |
111
+ | `fullscreen` | `boolean` | Whether the modal should take up the full screen height | `false` |
111
112
 
112
113
  \* use this to detect if the modal has been opened or closed, index 0 indicates open state and -1 indicates closed state
113
114
 
@@ -476,6 +477,20 @@ const App = () => {
476
477
  };
477
478
  ```
478
479
 
480
+ If your app already handles safe-area padding around modal content, you can disable Hearth's bottom-sheet safe-area spacing:
481
+
482
+ ```tsx
483
+ import { BottomSheetModalProvider } from '@utilitywarehouse/hearth-react-native';
484
+
485
+ const App = () => {
486
+ return (
487
+ <BottomSheetModalProvider useSafeAreaInsets={false}>
488
+ {/* Your app content */}
489
+ </BottomSheetModalProvider>
490
+ );
491
+ };
492
+ ```
493
+
479
494
  ### Ref Usage
480
495
 
481
496
  The Modal component forwards its ref to the underlying `BottomSheetModal`, giving you access to methods like:
@@ -9,6 +9,7 @@ export interface ModalCommonProps {
9
9
  showCloseButton?: boolean;
10
10
  heading?: string;
11
11
  loadingHeading?: string;
12
+ loadingDescription?: string;
12
13
  description?: string;
13
14
  stickyFooter?: boolean;
14
15
  children?: ViewProps['children'];
@@ -11,6 +11,7 @@ import { AccessibilityInfo, Platform, View, findNodeHandle } from 'react-native'
11
11
  import { StyleSheet } from 'react-native-unistyles';
12
12
  import { BodyText } from '../BodyText';
13
13
  import { BottomSheetModal, BottomSheetScrollView } from '../BottomSheet';
14
+ import { useBottomSheetContext } from '../BottomSheet/BottomSheet.context';
14
15
  import { Button } from '../Button';
15
16
  import { Heading } from '../Heading';
16
17
  import { Spinner } from '../Spinner';
@@ -34,6 +35,7 @@ const Modal = ({
34
35
  closeOnSecondaryButtonPress = true,
35
36
  loading,
36
37
  loadingHeading = 'Loading...',
38
+ loadingDescription,
37
39
  fullscreen = false,
38
40
  image,
39
41
  primaryButtonProps,
@@ -45,6 +47,7 @@ const Modal = ({
45
47
  const bottomSheetModalRef = useRef<BottomSheetModal>(null);
46
48
  const viewRef = useRef<View>(null);
47
49
  const scrollViewRef = useRef<BottomSheetScrollViewMethods>(null);
50
+ const { useSafeAreaInsets } = useBottomSheetContext();
48
51
 
49
52
  useImperativeHandle(ref, () => ({
50
53
  ...(bottomSheetModalRef.current as BottomSheetModal),
@@ -105,6 +108,7 @@ const Modal = ({
105
108
  noButtons,
106
109
  stickyFooter,
107
110
  showHandle: props.showHandle,
111
+ useSafeAreaInsets,
108
112
  });
109
113
 
110
114
  const footer = useMemo(
@@ -148,7 +152,10 @@ const Modal = ({
148
152
  <View
149
153
  style={styles.loadingContainer}
150
154
  accessible={Platform.OS === 'android' ? true : undefined}
151
- accessibilityLabel={Platform.OS === 'android' ? 'Loading' : undefined}
155
+ accessibilityLabel={Platform.OS === 'android' ? (loadingHeading ?? 'Loading') : undefined}
156
+ accessibilityHint={
157
+ Platform.OS === 'android' && loadingDescription ? loadingDescription : undefined
158
+ }
152
159
  screenReaderFocusable
153
160
  ref={viewRef}
154
161
  >
@@ -156,6 +163,7 @@ const Modal = ({
156
163
  <Heading size="lg" textAlign="center">
157
164
  {loadingHeading}
158
165
  </Heading>
166
+ {loadingDescription ? <BodyText textAlign="center">{loadingDescription}</BodyText> : null}
159
167
  </View>
160
168
  ) : (
161
169
  <View
@@ -256,13 +264,44 @@ const styles = StyleSheet.create((theme, rt) => ({
256
264
  variants: {
257
265
  bothButtons: {
258
266
  true: {
267
+ paddingBottom: 166,
268
+ },
269
+ false: {
270
+ paddingBottom: 102,
271
+ },
272
+ },
273
+ noButtons: {
274
+ true: {
275
+ paddingBottom: theme.components.modal.padding,
276
+ },
277
+ },
278
+ stickyFooter: {
279
+ true: {},
280
+ false: {
281
+ paddingBottom: theme.components.modal.padding + theme.components.bottomSheet.padding,
282
+ },
283
+ },
284
+ useSafeAreaInsets: {
285
+ true: {},
286
+ false: {},
287
+ },
288
+ },
289
+ compoundVariants: [
290
+ {
291
+ bothButtons: true,
292
+ useSafeAreaInsets: true,
293
+ styles: {
259
294
  paddingBottom:
260
295
  166 +
261
296
  rt.insets.bottom -
262
297
  theme.components.modal.padding +
263
298
  theme.components.bottomSheet.padding,
264
299
  },
265
- false: {
300
+ },
301
+ {
302
+ bothButtons: false,
303
+ useSafeAreaInsets: true,
304
+ styles: {
266
305
  paddingBottom:
267
306
  102 +
268
307
  rt.insets.bottom -
@@ -270,24 +309,27 @@ const styles = StyleSheet.create((theme, rt) => ({
270
309
  theme.components.bottomSheet.padding,
271
310
  },
272
311
  },
273
- noButtons: {
274
- true: {
312
+ {
313
+ noButtons: true,
314
+ useSafeAreaInsets: true,
315
+ styles: {
275
316
  paddingBottom:
276
317
  rt.insets.bottom +
277
318
  theme.components.modal.padding +
278
319
  theme.components.bottomSheet.padding,
279
320
  },
280
321
  },
281
- stickyFooter: {
282
- true: {},
283
- false: {
322
+ {
323
+ useSafeAreaInsets: true,
324
+ stickyFooter: false,
325
+ styles: {
284
326
  paddingBottom:
285
327
  rt.insets.bottom +
286
328
  theme.components.modal.padding +
287
329
  theme.components.bottomSheet.padding,
288
330
  },
289
331
  },
290
- },
332
+ ],
291
333
  },
292
334
  header: {
293
335
  flexDirection: 'row',
@@ -333,7 +375,16 @@ const styles = StyleSheet.create((theme, rt) => ({
333
375
  footerWrap: {
334
376
  backgroundColor: theme.color.surface.neutral.strong,
335
377
  paddingHorizontal: theme.components.bottomSheet.padding,
336
- paddingBottom: theme.components.bottomSheet.padding + rt.insets.bottom,
378
+ variants: {
379
+ useSafeAreaInsets: {
380
+ true: {
381
+ paddingBottom: theme.components.bottomSheet.padding + rt.insets.bottom,
382
+ },
383
+ false: {
384
+ paddingBottom: theme.components.bottomSheet.padding,
385
+ },
386
+ },
387
+ },
337
388
  },
338
389
  }));
339
390
 
@@ -150,6 +150,7 @@ const styles = StyleSheet.create({
150
150
  | `closeButtonProps` | `Omit<UnstyledIconButtonProps, 'children'>` | Extra props forwarded to the close button. | - |
151
151
  | `loading` | `boolean` | Replaces the content with a loading state and spinner. | `false` |
152
152
  | `loadingHeading` | `string` | Heading text shown while `loading` is true. | `'Loading...'` |
153
+ | `loadingDescription` | `string` | Supporting text shown below the heading while `loading` is true. | - |
153
154
  | `image` | `ReactNode` | Optional image or illustration shown above the text content. | - |
154
155
  | `children` | `ReactNode` | Content rendered inside the modal body. | - |
155
156
  | `stickyFooter` | `boolean` | Keeps action buttons pinned to the bottom instead of flowing with the content. | `true` |
@@ -32,6 +32,7 @@ const NavModal = ({
32
32
  onPressSecondaryButton,
33
33
  loading,
34
34
  loadingHeading = 'Loading...',
35
+ loadingDescription,
35
36
  image,
36
37
  primaryButtonProps,
37
38
  secondaryButtonProps,
@@ -162,13 +163,21 @@ const NavModal = ({
162
163
  <View
163
164
  style={styles.loadingContainer}
164
165
  accessible={Platform.OS === 'android' ? true : undefined}
165
- accessibilityLabel={Platform.OS === 'android' ? 'Loading' : undefined}
166
+ accessibilityLabel={Platform.OS === 'android' ? (loadingHeading ?? 'Loading') : undefined}
167
+ accessibilityHint={
168
+ Platform.OS === 'android' && loadingDescription ? loadingDescription : undefined
169
+ }
166
170
  screenReaderFocusable
167
171
  >
168
172
  <Spinner size="lg" color={isBrandBackground ? theme.color.icon.inverted : undefined} />
169
173
  <Heading size="lg" textAlign="center" inverted={isBrandBackground}>
170
174
  {loadingHeading}
171
175
  </Heading>
176
+ {loadingDescription ? (
177
+ <BodyText size="md" textAlign="center" inverted={isBrandBackground}>
178
+ {loadingDescription}
179
+ </BodyText>
180
+ ) : null}
172
181
  </View>
173
182
  ) : (
174
183
  <View
@@ -10,6 +10,7 @@ import {
10
10
  BottomSheetScrollView,
11
11
  BottomSheetView,
12
12
  } from '../BottomSheet';
13
+ import { useBottomSheetContext } from '../BottomSheet/BottomSheet.context';
13
14
  import { DetailText } from '../DetailText';
14
15
  import { FormField, useFormFieldContext } from '../FormField';
15
16
  import { Icon } from '../Icon';
@@ -49,6 +50,7 @@ const Select = ({
49
50
  const isRequired = formFieldContext?.required ?? required;
50
51
  const isDisabled = formFieldContext?.disabled ?? disabled;
51
52
  const isReadonly = formFieldContext?.readonly ?? readonly;
53
+ const { useSafeAreaInsets } = useBottomSheetContext();
52
54
 
53
55
  const bottomSheetModalRef = useRef<BottomSheetModal>(null);
54
56
  const [search, setSearch] = useState('');
@@ -183,7 +185,7 @@ const Select = ({
183
185
  close: closeBottomSheet,
184
186
  }}
185
187
  >
186
- <SafeAreaView edges={['top']} style={{ flex: 1 }}>
188
+ <SafeAreaView edges={useSafeAreaInsets ? ['top'] : []} style={{ flex: 1 }}>
187
189
  {menuHeading && (
188
190
  <View style={styles.headingContainer}>
189
191
  <DetailText size="lg">{menuHeading}</DetailText>