@utilitywarehouse/hearth-react-native 0.29.2 → 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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +13 -13
- package/CHANGELOG.md +55 -0
- package/build/components/DescriptionList/DescriptionListItem.js +1 -2
- package/build/components/ExpandableCard/ExpandableCardTriggerRoot.js +2 -2
- package/build/components/Heading/Heading.js +1 -1
- package/build/components/List/List.js +2 -2
- package/build/components/List/ListAction/ListAction.js +1 -1
- package/build/components/List/ListItem/ListItemRoot.js +2 -2
- package/build/components/Modal/Modal.d.ts +1 -1
- package/build/components/Modal/Modal.js +2 -2
- package/build/components/Modal/Modal.shared.types.d.ts +1 -0
- package/build/components/NavModal/NavModal.d.ts +1 -1
- package/build/components/NavModal/NavModal.js +2 -2
- package/docs/changelog.mdx +86 -0
- package/package.json +1 -1
- package/src/components/DescriptionList/DescriptionListItem.tsx +1 -2
- package/src/components/ExpandableCard/ExpandableCardTriggerRoot.tsx +4 -4
- package/src/components/Heading/Heading.docs.mdx +12 -3
- package/src/components/Heading/Heading.tsx +1 -0
- package/src/components/List/List.docs.mdx +2 -2
- package/src/components/List/List.stories.tsx +6 -5
- package/src/components/List/List.tsx +14 -2
- package/src/components/List/ListAction/ListAction.tsx +1 -0
- package/src/components/List/ListItem/ListItemRoot.tsx +3 -3
- package/src/components/Modal/Modal.docs.mdx +22 -21
- package/src/components/Modal/Modal.shared.types.ts +1 -0
- package/src/components/Modal/Modal.tsx +6 -1
- package/src/components/NavModal/NavModal.docs.mdx +1 -0
- package/src/components/NavModal/NavModal.tsx +10 -1
package/.turbo/turbo-build.log
CHANGED
package/.turbo/turbo-lint.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @utilitywarehouse/hearth-react-native@0.
|
|
2
|
+
> @utilitywarehouse/hearth-react-native@0.30.0 lint /home/runner/work/hearth/hearth/packages/react-native
|
|
3
3
|
> TIMING=1 eslint .
|
|
4
4
|
|
|
5
5
|
|
|
@@ -51,15 +51,15 @@
|
|
|
51
51
|
|
|
52
52
|
✖ 22 problems (0 errors, 22 warnings)
|
|
53
53
|
|
|
54
|
-
Rule
|
|
55
|
-
|
|
56
|
-
@typescript-eslint/no-unused-vars
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
no-misleading-character-class
|
|
63
|
-
|
|
64
|
-
no-
|
|
65
|
-
|
|
54
|
+
Rule | Time (ms) | Relative
|
|
55
|
+
:---------------------------------|----------:|--------:
|
|
56
|
+
@typescript-eslint/no-unused-vars | 1641.469 | 60.9%
|
|
57
|
+
no-global-assign | 98.820 | 3.7%
|
|
58
|
+
react-hooks/exhaustive-deps | 93.179 | 3.5%
|
|
59
|
+
no-unexpected-multiline | 70.646 | 2.6%
|
|
60
|
+
react-hooks/rules-of-hooks | 65.259 | 2.4%
|
|
61
|
+
@typescript-eslint/ban-ts-comment | 45.783 | 1.7%
|
|
62
|
+
no-misleading-character-class | 45.630 | 1.7%
|
|
63
|
+
no-loss-of-precision | 43.137 | 1.6%
|
|
64
|
+
@typescript-eslint/no-this-alias | 38.127 | 1.4%
|
|
65
|
+
no-control-regex | 32.544 | 1.2%
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# @utilitywarehouse/hearth-react-native
|
|
2
2
|
|
|
3
|
+
## 0.30.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1072](https://github.com/utilitywarehouse/hearth/pull/1072) [`55f0095`](https://github.com/utilitywarehouse/hearth/commit/55f009576ba55081de358bccc21691861ddd7c33) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add `loadingDescription` support to `Modal` and `NavModal`
|
|
8
|
+
|
|
9
|
+
`Modal` and `NavModal` now accept a `loadingDescription` prop to render supporting text beneath the spinner while `loading` is true. This makes it easier to give users more context during loading states without building custom loading content.
|
|
10
|
+
|
|
11
|
+
**Components affected**:
|
|
12
|
+
- `Modal`
|
|
13
|
+
- `NavModal`
|
|
14
|
+
|
|
15
|
+
**Developer changes**:
|
|
16
|
+
|
|
17
|
+
No changes are required for existing usage. To show supporting text in a loading state, pass `loadingDescription` alongside `loading` and, if needed, `loadingHeading`.
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- [#1070](https://github.com/utilitywarehouse/hearth/pull/1070) [`93c042c`](https://github.com/utilitywarehouse/hearth/commit/93c042c7772ab298e2ea4888a9777e8176453098) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: Standardise numeric value typography across list-based components.
|
|
22
|
+
|
|
23
|
+
Numeric values in `DescriptionListItem`, `ExpandableCard`, and `ListItem` now render with semibold `BodyText` instead of `DetailText`, aligning them with the updated content hierarchy used elsewhere in the library.
|
|
24
|
+
|
|
25
|
+
**Components affected**:
|
|
26
|
+
- `DescriptionListItem`
|
|
27
|
+
- `ExpandableCard`
|
|
28
|
+
- `ListItem`
|
|
29
|
+
|
|
30
|
+
**Developer changes**:
|
|
31
|
+
|
|
32
|
+
No changes are required.
|
|
33
|
+
|
|
34
|
+
- [#1073](https://github.com/utilitywarehouse/hearth/pull/1073) [`9759622`](https://github.com/utilitywarehouse/hearth/commit/975962229137dd196e5f72a04037a8f181907818) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: Announce `Heading` as a header for assistive technologies.
|
|
35
|
+
|
|
36
|
+
`Heading` now sets `accessibilityRole="header"` automatically so VoiceOver and TalkBack can identify headings as part of the screen structure.
|
|
37
|
+
|
|
38
|
+
**Components affected**:
|
|
39
|
+
- `Heading`
|
|
40
|
+
|
|
41
|
+
**Developer changes**:
|
|
42
|
+
|
|
43
|
+
No changes are required.
|
|
44
|
+
|
|
45
|
+
- [#1074](https://github.com/utilitywarehouse/hearth/pull/1074) [`95fe19e`](https://github.com/utilitywarehouse/hearth/commit/95fe19e6328bf652ff3ac1b2c723e1389fc59936) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: Improve accessibility roles for `List`, `ListItem`, and `ListAction`
|
|
46
|
+
|
|
47
|
+
`List` now defaults to `accessibilityRole="list"`, `ListAction` now defaults to `accessibilityRole="button"`, and `ListItem` respects an explicitly provided `accessibilityRole` instead of always forcing button semantics when `onPress` is set.
|
|
48
|
+
|
|
49
|
+
**Components affected**:
|
|
50
|
+
- `List`
|
|
51
|
+
- `ListItem`
|
|
52
|
+
- `ListAction`
|
|
53
|
+
|
|
54
|
+
**Developer changes**:
|
|
55
|
+
|
|
56
|
+
No changes are required unless you want a tappable `ListItem` to be announced as something other than a button. In that case, pass `accessibilityRole` explicitly.
|
|
57
|
+
|
|
3
58
|
## 0.29.2
|
|
4
59
|
|
|
5
60
|
### Patch Changes
|
|
@@ -4,7 +4,6 @@ import { View } from 'react-native';
|
|
|
4
4
|
import { StyleSheet } from 'react-native-unistyles';
|
|
5
5
|
import { useTheme } from '../../hooks';
|
|
6
6
|
import { BodyText } from '../BodyText';
|
|
7
|
-
import { DetailText } from '../DetailText';
|
|
8
7
|
import Helper from '../Helper/Helper';
|
|
9
8
|
import { useDescriptionListContext } from './DescriptionList.context';
|
|
10
9
|
const DescriptionListItem = ({ heading, description, headingWidth, trailingContent, invalidText, numericValue, style, ...props }) => {
|
|
@@ -15,7 +14,7 @@ const DescriptionListItem = ({ heading, description, headingWidth, trailingConte
|
|
|
15
14
|
const descIsText = typeof description === 'string' || typeof description === 'number';
|
|
16
15
|
const combinedLabel = headingIsText && descIsText ? `${heading}: ${description}` : undefined;
|
|
17
16
|
const hideDescendants = !!combinedLabel;
|
|
18
|
-
return (_jsxs(View, { accessibilityRole: "text", accessible: !!combinedLabel, accessibilityLabel: combinedLabel, ...props, style: [styles.item, style], children: [_jsxs(View, { style: styles.textWrap, importantForAccessibility: hideDescendants ? 'no-hide-descendants' : undefined, accessibilityElementsHidden: hideDescendants || undefined, children: [_jsx(View, { style: [direction === 'row' && { width: headingWidth || itemHeadingWidth }], children: headingIsText ? _jsx(BodyText, { style: styles.headingText, children: heading }) : heading }), _jsxs(View, { style: styles.descriptionWrapper, children: [descIsText ? _jsx(BodyText, { children: description }) : description, !!invalidText && (_jsx(Helper, { validationStatus: "invalid", showIcon: true, icon: ErrorCircleSmallIcon, text: invalidText || '' }))] }), numericValue ? (_jsx(View, { style: styles.descriptionWrapper, children: _jsx(
|
|
17
|
+
return (_jsxs(View, { accessibilityRole: "text", accessible: !!combinedLabel, accessibilityLabel: combinedLabel, ...props, style: [styles.item, style], children: [_jsxs(View, { style: styles.textWrap, importantForAccessibility: hideDescendants ? 'no-hide-descendants' : undefined, accessibilityElementsHidden: hideDescendants || undefined, children: [_jsx(View, { style: [direction === 'row' && { width: headingWidth || itemHeadingWidth }], children: headingIsText ? _jsx(BodyText, { style: styles.headingText, children: heading }) : heading }), _jsxs(View, { style: styles.descriptionWrapper, children: [descIsText ? _jsx(BodyText, { children: description }) : description, !!invalidText && (_jsx(Helper, { validationStatus: "invalid", showIcon: true, icon: ErrorCircleSmallIcon, text: invalidText || '' }))] }), numericValue ? (_jsx(View, { style: styles.descriptionWrapper, children: _jsx(BodyText, { weight: "semibold", children: numericValue }) })) : null] }), trailingContent ? trailingContent : null] }));
|
|
19
18
|
};
|
|
20
19
|
DescriptionListItem.displayName = 'DescriptionListItem';
|
|
21
20
|
const styles = StyleSheet.create(theme => ({
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { ChevronDownSmallIcon, ChevronUpSmallIcon, } from '@utilitywarehouse/hearth-react-native-icons';
|
|
3
3
|
import { Pressable } from 'react-native';
|
|
4
4
|
import { StyleSheet } from 'react-native-unistyles';
|
|
5
|
-
import {
|
|
5
|
+
import { BodyText } from '../BodyText';
|
|
6
6
|
import ExpandableCardContent from './ExpandableCardContent';
|
|
7
7
|
import ExpandableCardHelperText from './ExpandableCardHelperText';
|
|
8
8
|
import ExpandableCardIcon from './ExpandableCardIcon';
|
|
@@ -26,7 +26,7 @@ const ExpandableCardTriggerRoot = ({ heading, helperText, leadingIcon, leadingCo
|
|
|
26
26
|
}
|
|
27
27
|
return null;
|
|
28
28
|
};
|
|
29
|
-
const renderDefaultContent = () => (_jsxs(_Fragment, { children: [renderLeadingContent(), _jsxs(ExpandableCardContent, { children: [badgePosition === 'top' ? badge : null, _jsx(ExpandableCardText, { children: heading }), helperText && _jsx(ExpandableCardHelperText, { children: helperText }), badgePosition === 'bottom' ? badge : null] }), numericValue && (_jsx(
|
|
29
|
+
const renderDefaultContent = () => (_jsxs(_Fragment, { children: [renderLeadingContent(), _jsxs(ExpandableCardContent, { children: [badgePosition === 'top' ? badge : null, _jsx(ExpandableCardText, { children: heading }), helperText && _jsx(ExpandableCardHelperText, { children: helperText }), badgePosition === 'bottom' ? badge : null] }), !!numericValue && (_jsx(BodyText, { weight: "semibold", style: styles.numericValue, children: numericValue })), _jsx(ExpandableCardTrailingContent, { style: styles.chevron, children: _jsx(ExpandableCardTrailingIcon, { as: isExpanded ? ChevronUpSmallIcon : ChevronDownSmallIcon }) })] }));
|
|
30
30
|
return (_jsx(Pressable, { ...props, testID: testID, style: [styles.container, props.style], disabled: disabled, accessibilityRole: "button", accessibilityState: { expanded: isExpanded, disabled }, accessibilityLabel: `${heading}${helperText ? `, ${helperText}` : ''}`, children: children || renderDefaultContent() }));
|
|
31
31
|
};
|
|
32
32
|
ExpandableCardTriggerRoot.displayName = 'ExpandableCardTriggerRoot';
|
|
@@ -12,7 +12,7 @@ const Heading = ({ children, color, size = 'lg', truncated, underline, strikeThr
|
|
|
12
12
|
strikeThrough,
|
|
13
13
|
inverted,
|
|
14
14
|
});
|
|
15
|
-
return (_jsx(Text, { ...remainingProps, ...(truncated
|
|
15
|
+
return (_jsx(Text, { accessibilityRole: "header", ...remainingProps, ...(truncated
|
|
16
16
|
? {
|
|
17
17
|
numberOfLines: 1,
|
|
18
18
|
ellipsizeMode: 'tail',
|
|
@@ -6,7 +6,7 @@ import { Card } from '../Card';
|
|
|
6
6
|
import { SectionHeader } from '../SectionHeader';
|
|
7
7
|
import { ListContext } from './List.context';
|
|
8
8
|
const List = ({ children, heading, helperText, headerTrailingContent, invalidText, ...props }) => {
|
|
9
|
-
const { loading, disabled, container = 'none', testID, style, ...rest } = props;
|
|
9
|
+
const { loading, disabled, container = 'none', testID, style, accessibilityRole, ...rest } = props;
|
|
10
10
|
const orderRef = useRef([]);
|
|
11
11
|
const [firstItemId, setFirstItemId] = useState(undefined);
|
|
12
12
|
const containerToCard = {
|
|
@@ -35,7 +35,7 @@ const List = ({ children, heading, helperText, headerTrailingContent, invalidTex
|
|
|
35
35
|
registerItem,
|
|
36
36
|
};
|
|
37
37
|
styles.useVariants({ disabled });
|
|
38
|
-
return (_jsx(ListContext.Provider, { value: value, children: _jsxs(View, { ...rest, style: [styles.container, style], children: [heading ? (_jsx(SectionHeader, { heading: heading, helperText: helperText, trailingContent: headerTrailingContent, invalidText: invalidText })) : null, container === 'none' ? (_jsx(View, { testID: testID, children: children })) : (React.Children.count(children) > 0 && (_jsx(Card, { ...containerToCard, noPadding: true, style: styles.card, testID: testID, children: _jsx(_Fragment, { children: children }) })))] }) }));
|
|
38
|
+
return (_jsx(ListContext.Provider, { value: value, children: _jsxs(View, { ...rest, accessibilityRole: accessibilityRole ?? 'list', style: [styles.container, style], children: [heading ? (_jsx(SectionHeader, { heading: heading, helperText: helperText, trailingContent: headerTrailingContent, invalidText: invalidText })) : null, container === 'none' ? (_jsx(View, { testID: testID, children: children })) : (React.Children.count(children) > 0 && (_jsx(Card, { ...containerToCard, noPadding: true, style: styles.card, testID: testID, children: _jsx(_Fragment, { children: children }) })))] }) }));
|
|
39
39
|
};
|
|
40
40
|
List.displayName = 'List';
|
|
41
41
|
const styles = StyleSheet.create(theme => ({
|
|
@@ -43,7 +43,7 @@ const ListActionRoot = ({ heading, disabled, variant = 'subtle', loading, ...pro
|
|
|
43
43
|
isFirstChild,
|
|
44
44
|
container: listContext?.container,
|
|
45
45
|
});
|
|
46
|
-
return (_jsx(Pressable, { ...props, testID: testID, style: [styles.container, props.style], disabled: isDisabled || !onPress, children: loading ? (_jsxs(View, { style: styles.loadingWrap, children: [_jsx(Skeleton, { style: { flex: 1, maxWidth: 166 }, width: "auto", height: 24, borderRadius: "sm" }), _jsx(Skeleton, { width: 24, height: 24, borderRadius: "sm" })] })) : (_jsxs(_Fragment, { children: [_jsx(ListActionContent, { children: _jsx(ListActionText, { children: heading }) }), _jsx(ListActionTrailingContent, { style: styles.centeredTrailingIcon, children: _jsx(ListActionTrailingIcon, { as: ChevronRightSmallIcon }) })] })) }));
|
|
46
|
+
return (_jsx(Pressable, { ...props, accessibilityRole: props.accessibilityRole ?? 'button', testID: testID, style: [styles.container, props.style], disabled: isDisabled || !onPress, children: loading ? (_jsxs(View, { style: styles.loadingWrap, children: [_jsx(Skeleton, { style: { flex: 1, maxWidth: 166 }, width: "auto", height: 24, borderRadius: "sm" }), _jsx(Skeleton, { width: 24, height: 24, borderRadius: "sm" })] })) : (_jsxs(_Fragment, { children: [_jsx(ListActionContent, { children: _jsx(ListActionText, { children: heading }) }), _jsx(ListActionTrailingContent, { style: styles.centeredTrailingIcon, children: _jsx(ListActionTrailingIcon, { as: ChevronRightSmallIcon }) })] })) }));
|
|
47
47
|
};
|
|
48
48
|
const ListAction = createPressable({
|
|
49
49
|
Root: ListActionRoot,
|
|
@@ -3,7 +3,7 @@ import { ChevronRightSmallIcon } from '@utilitywarehouse/hearth-react-native-ico
|
|
|
3
3
|
import { useId, useLayoutEffect, useMemo } from 'react';
|
|
4
4
|
import { Pressable } from 'react-native';
|
|
5
5
|
import { StyleSheet } from 'react-native-unistyles';
|
|
6
|
-
import {
|
|
6
|
+
import { BodyText } from '../../BodyText';
|
|
7
7
|
import { Skeleton } from '../../Skeleton';
|
|
8
8
|
import { useListContext } from '../List.context';
|
|
9
9
|
import { ListItemContext } from './ListItem.context';
|
|
@@ -61,7 +61,7 @@ const ListItemRoot = ({ heading, helperText, leadingContent, trailingContent, di
|
|
|
61
61
|
if (loading || listContext?.loading) {
|
|
62
62
|
return (_jsxs(Pressable, { ...props, testID: loadingTestID, style: [styles.container, props.style], disabled: isDisabled, children: [leadingContent ? _jsx(Skeleton, { width: 24, height: 24 }) : null, _jsxs(ListItemContent, { children: [_jsx(Skeleton, { width: "80%", height: 20 }), _jsx(Skeleton, { width: "100%", height: 16 })] }), onPress || trailingContent ? _jsx(Skeleton, { width: 24, height: 24 }) : null] }));
|
|
63
63
|
}
|
|
64
|
-
return (_jsx(ListItemContext.Provider, { value: value, children: _jsx(Pressable, { ...props, testID: testID, style: [styles.container, props.style], disabled: isDisabled, accessibilityRole: onPress ? 'button' : undefined, children: children ? (children) : (_jsxs(_Fragment, { children: [leadingContent ? (_jsx(ListItemLeadingContent, { children: leadingContent })) : null, _jsxs(ListItemContent, { children: [badgePosition === 'top' && badge ? badge : null, _jsx(ListItemHeading, { truncated: truncateHeading, children: heading }), helperText ? (_jsx(ListItemHelperText, { truncated: truncateHelperText, children: helperText })) : null, badgePosition === 'bottom' && badge ? badge : null] }), !!numericValue && _jsx(
|
|
64
|
+
return (_jsx(ListItemContext.Provider, { value: value, children: _jsx(Pressable, { ...props, testID: testID, style: [styles.container, props.style], disabled: isDisabled, accessibilityRole: props.accessibilityRole ?? (onPress ? 'button' : undefined), children: children ? (children) : (_jsxs(_Fragment, { children: [leadingContent ? (_jsx(ListItemLeadingContent, { children: leadingContent })) : null, _jsxs(ListItemContent, { children: [badgePosition === 'top' && badge ? badge : null, _jsx(ListItemHeading, { truncated: truncateHeading, children: heading }), helperText ? (_jsx(ListItemHelperText, { truncated: truncateHelperText, children: helperText })) : null, badgePosition === 'bottom' && badge ? badge : null] }), !!numericValue && _jsx(BodyText, { weight: "semibold", children: numericValue }), trailingContent ? (_jsx(ListItemTrailingContent, { children: trailingContent })) : onPress ? (_jsx(ListItemTrailingContent, { style: styles.centeredTrailingIcon, children: _jsx(ListItemTrailingIcon, { as: ChevronRightSmallIcon }) })) : null] })) }) }));
|
|
65
65
|
};
|
|
66
66
|
ListItemRoot.displayName = 'ListItemRoot';
|
|
67
67
|
const styles = StyleSheet.create(theme => ({
|
|
@@ -3,5 +3,5 @@ import ModalProps from './Modal.props';
|
|
|
3
3
|
type Modal<T = any> = BottomSheetModalMethods<T> & {
|
|
4
4
|
triggerCloseAnimation?: () => void;
|
|
5
5
|
};
|
|
6
|
-
declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, loadingDescription, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
export default Modal;
|
|
@@ -11,7 +11,7 @@ import { Button } from '../Button';
|
|
|
11
11
|
import { Heading } from '../Heading';
|
|
12
12
|
import { Spinner } from '../Spinner';
|
|
13
13
|
import { UnstyledIconButton } from '../UnstyledIconButton';
|
|
14
|
-
const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter = true, ...props }) => {
|
|
14
|
+
const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', loadingDescription, fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter = true, ...props }) => {
|
|
15
15
|
const bottomSheetModalRef = useRef(null);
|
|
16
16
|
const viewRef = useRef(null);
|
|
17
17
|
const scrollViewRef = useRef(null);
|
|
@@ -78,7 +78,7 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
|
|
|
78
78
|
secondaryButtonProps,
|
|
79
79
|
secondaryButtonText,
|
|
80
80
|
]);
|
|
81
|
-
const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, description && !image ? _jsx(BodyText, { accessible: true, children: description }) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : null] })] })) : null, children, !stickyFooter && !noButtons ? footer : null] })) }));
|
|
81
|
+
const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? (loadingHeading ?? 'Loading') : undefined, accessibilityHint: Platform.OS === 'android' && loadingDescription ? loadingDescription : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading }), loadingDescription ? _jsx(BodyText, { textAlign: "center", children: loadingDescription }) : null] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, description && !image ? _jsx(BodyText, { accessible: true, children: description }) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : null] })] })) : null, children, !stickyFooter && !noButtons ? footer : null] })) }));
|
|
82
82
|
const renderFooter = useCallback((props) => (_jsx(BottomSheetFooter, { ...props, children: _jsx(View, { style: styles.footerWrap, children: footer }) })), [footer]);
|
|
83
83
|
return (_jsxs(BottomSheetModal, { ref: bottomSheetModalRef, enableDynamicSizing: true, snapPoints: image || fullscreen ? ['90%'] : props.snapPoints, showHandle: typeof loading !== 'undefined' && loading ? false : props.showHandle, accessible: false, style: styles.modal, footerComponent: stickyFooter && !noButtons ? renderFooter : undefined, ...props, onChange: handleChange, children: [loading ? _jsx(View, { style: styles.loadingTop }) : null, _jsx(BottomSheetScrollView, { contentContainerStyle: styles.scrollView, ref: scrollViewRef, children: content })] }));
|
|
84
84
|
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import NavModalProps from './NavModal.props';
|
|
2
|
-
declare const NavModal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, loading, loadingHeading, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter, background, scrollable, presentation, scrollViewProps, safeAreaViewProps, ...props }: NavModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
declare const NavModal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, loading, loadingHeading, loadingDescription, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter, background, scrollable, presentation, scrollViewProps, safeAreaViewProps, ...props }: NavModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
3
3
|
export default NavModal;
|
|
@@ -12,7 +12,7 @@ import { Button } from '../Button';
|
|
|
12
12
|
import { Heading } from '../Heading';
|
|
13
13
|
import { Spinner } from '../Spinner';
|
|
14
14
|
import { UnstyledIconButton } from '../UnstyledIconButton';
|
|
15
|
-
const NavModal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, loading, loadingHeading = 'Loading...', image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter = true, background = 'default', scrollable = true, presentation = 'modal', scrollViewProps, safeAreaViewProps, ...props }) => {
|
|
15
|
+
const NavModal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, loading, loadingHeading = 'Loading...', loadingDescription, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter = true, background = 'default', scrollable = true, presentation = 'modal', scrollViewProps, safeAreaViewProps, ...props }) => {
|
|
16
16
|
const theme = useTheme();
|
|
17
17
|
const backgroundOpacity = useSharedValue(0);
|
|
18
18
|
const pretendContentTranslateY = useSharedValue(20);
|
|
@@ -78,7 +78,7 @@ const NavModal = ({ ref, children, heading, description, showCloseButton = true,
|
|
|
78
78
|
secondaryButtonProps,
|
|
79
79
|
secondaryButtonText,
|
|
80
80
|
]);
|
|
81
|
-
const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, children: [_jsx(Spinner, { size: "lg", color: isBrandBackground ? theme.color.icon.inverted : undefined }), _jsx(Heading, { size: "lg", textAlign: "center", inverted: isBrandBackground, children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, inverted: isBrandBackground, children: heading })) : null, description && !image ? (_jsx(BodyText, { accessible: true, inverted: isBrandBackground, children: description })) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", inverted: isBrandBackground, ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, inverted: isBrandBackground, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, inverted: isBrandBackground, children: description })) : null] })] })) : null, scrollable ? (_jsxs(ScrollView, { style: {
|
|
81
|
+
const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? (loadingHeading ?? 'Loading') : undefined, accessibilityHint: Platform.OS === 'android' && loadingDescription ? loadingDescription : undefined, screenReaderFocusable: true, children: [_jsx(Spinner, { size: "lg", color: isBrandBackground ? theme.color.icon.inverted : undefined }), _jsx(Heading, { size: "lg", textAlign: "center", inverted: isBrandBackground, children: loadingHeading }), loadingDescription ? (_jsx(BodyText, { size: "md", textAlign: "center", inverted: isBrandBackground, children: loadingDescription })) : null] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, inverted: isBrandBackground, children: heading })) : null, description && !image ? (_jsx(BodyText, { accessible: true, inverted: isBrandBackground, children: description })) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", inverted: isBrandBackground, ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, inverted: isBrandBackground, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, inverted: isBrandBackground, children: description })) : null] })] })) : null, scrollable ? (_jsxs(ScrollView, { style: {
|
|
82
82
|
flex: stickyFooter ? 1 : 0,
|
|
83
83
|
marginHorizontal: -4,
|
|
84
84
|
}, contentContainerStyle: { paddingHorizontal: 4 }, ...scrollViewProps, children: [children, !stickyFooter && !noButtons ? (_jsx(View, { style: styles.inNavModalFooterContainer, children: footer })) : null] })) : (_jsxs(View, { style: {
|
package/docs/changelog.mdx
CHANGED
|
@@ -9,6 +9,92 @@ import { BackToTopButton, NextPrevPage } from './components';
|
|
|
9
9
|
The changelog for the Hearth React Native library. Here you can find all the changes, improvements, and bug fixes for each version.
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
## 0.29.2
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- [#1067](https://github.com/utilitywarehouse/hearth/pull/1067) [`893cbfd`](https://github.com/utilitywarehouse/hearth/commit/893cbfd1bf090b8b75df6f58f2babaf8ba1e0033) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add a `useSafeAreaInsets` prop to `BottomSheetModalProvider` to control Hearth's bottom-sheet safe-area spacing.
|
|
17
|
+
|
|
18
|
+
Bottom-sheet wrappers such as `BottomSheetView`, `BottomSheetScrollView`, `BottomSheetFlatList`, and components that render `SafeAreaView` inside a bottom sheet now respect `BottomSheetModalProvider` configuration.
|
|
19
|
+
|
|
20
|
+
**Components affected**:
|
|
21
|
+
- `BottomSheetModalProvider`
|
|
22
|
+
- `BottomSheetView`
|
|
23
|
+
- `BottomSheetScrollView`
|
|
24
|
+
- `BottomSheetFlatList`
|
|
25
|
+
- `Modal`
|
|
26
|
+
- `Select`
|
|
27
|
+
- `Combobox`
|
|
28
|
+
|
|
29
|
+
**Developer changes**:
|
|
30
|
+
|
|
31
|
+
No changes are required if you want the current behaviour. If your app already applies its own safe-area padding around bottom-sheet content, opt out like this:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<BottomSheetModalProvider useSafeAreaInsets={false}>
|
|
35
|
+
{/* Your app content */}
|
|
36
|
+
</BottomSheetModalProvider>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 0.29.1
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- [#1062](https://github.com/utilitywarehouse/hearth/pull/1062) [`0da3ffe`](https://github.com/utilitywarehouse/hearth/commit/0da3ffe12691a4287694ae9fcb2290d459e3c041) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: Respect the selected `NavModal` background style
|
|
44
|
+
|
|
45
|
+
Fixed an issue where `NavModal` always rendered its inner content with the default surface background, which prevented the `background="brand"` treatment from being applied correctly.
|
|
46
|
+
|
|
47
|
+
**Components affected**:
|
|
48
|
+
- `NavModal`
|
|
49
|
+
|
|
50
|
+
**Developer changes**:
|
|
51
|
+
|
|
52
|
+
No changes are required.
|
|
53
|
+
|
|
54
|
+
## 0.29.0
|
|
55
|
+
|
|
56
|
+
### Minor Changes
|
|
57
|
+
|
|
58
|
+
- [#1060](https://github.com/utilitywarehouse/hearth/pull/1060) [`05d38f9`](https://github.com/utilitywarehouse/hearth/commit/05d38f9414fec6d6b4a0733829b4d290d96fccae) Thanks [@jordmccord](https://github.com/jordmccord)! - 💔 [BREAKING CHANGE]: Extract navigation-presented modal behaviour into `NavModal`
|
|
59
|
+
|
|
60
|
+
`Modal` now only supports the bottom-sheet modal behaviour. The React Navigation screen-based API that was previously exposed through `inNavModal`, `background`, and `scrollable` has moved to the new `NavModal` component.
|
|
61
|
+
|
|
62
|
+
`NavModal` also adds a `presentation` prop so the component can match the React Navigation screen presentation style for sheet-style and full-screen modal routes.
|
|
63
|
+
|
|
64
|
+
The package-owned `SafeAreaView` component has also been removed in favour of the `react-native-safe-area-context` `SafeAreaView`. Hearth now re-exports that implementation from the package root.
|
|
65
|
+
|
|
66
|
+
**Components affected**:
|
|
67
|
+
- `Modal`
|
|
68
|
+
- `NavModal`
|
|
69
|
+
- `SafeAreaView`
|
|
70
|
+
|
|
71
|
+
**Developer changes**:
|
|
72
|
+
|
|
73
|
+
Update navigation modal screens to use `NavModal` instead of `Modal`:
|
|
74
|
+
|
|
75
|
+
```diff
|
|
76
|
+
- import { Modal } from '@utilitywarehouse/hearth-react-native';
|
|
77
|
+
+ import { NavModal, type NavModalRef } from '@utilitywarehouse/hearth-react-native';
|
|
78
|
+
|
|
79
|
+
- const modalRef = useRef<Modal>(null);
|
|
80
|
+
+ const modalRef = useRef<NavModalRef>(null);
|
|
81
|
+
|
|
82
|
+
- <Modal inNavModal background="brand" scrollable={false}>
|
|
83
|
+
+ <NavModal background="brand" scrollable={false} presentation="modal">
|
|
84
|
+
{/* content */}
|
|
85
|
+
- </Modal>
|
|
86
|
+
+ </NavModal>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
If you are using the bottom-sheet modal API, no changes are required.
|
|
90
|
+
|
|
91
|
+
If you were importing the old component from the component path, update it to use the package root re-export or import directly from `react-native-safe-area-context`:
|
|
92
|
+
|
|
93
|
+
```diff
|
|
94
|
+
- import { SafeAreaView } from '@utilitywarehouse/hearth-react-native';
|
|
95
|
+
+ import { SafeAreaView } from 'react-native-safe-area-context';
|
|
96
|
+
```
|
|
97
|
+
|
|
12
98
|
## 0.28.6
|
|
13
99
|
|
|
14
100
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -3,7 +3,6 @@ import { View } from 'react-native';
|
|
|
3
3
|
import { StyleSheet } from 'react-native-unistyles';
|
|
4
4
|
import { useTheme } from '../../hooks';
|
|
5
5
|
import { BodyText } from '../BodyText';
|
|
6
|
-
import { DetailText } from '../DetailText';
|
|
7
6
|
import Helper from '../Helper/Helper';
|
|
8
7
|
import { useDescriptionListContext } from './DescriptionList.context';
|
|
9
8
|
import type DescriptionListItemProps from './DescriptionListItem.props';
|
|
@@ -55,7 +54,7 @@ const DescriptionListItem = ({
|
|
|
55
54
|
</View>
|
|
56
55
|
{numericValue ? (
|
|
57
56
|
<View style={styles.descriptionWrapper}>
|
|
58
|
-
<
|
|
57
|
+
<BodyText weight="semibold">{numericValue}</BodyText>
|
|
59
58
|
</View>
|
|
60
59
|
) : null}
|
|
61
60
|
</View>
|
|
@@ -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 {
|
|
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
|
-
<
|
|
65
|
+
{!!numericValue && (
|
|
66
|
+
<BodyText weight="semibold" style={styles.numericValue}>
|
|
67
67
|
{numericValue}
|
|
68
|
-
</
|
|
68
|
+
</BodyText>
|
|
69
69
|
)}
|
|
70
70
|
<ExpandableCardTrailingContent style={styles.chevron}>
|
|
71
71
|
<ExpandableCardTrailingIcon as={isExpanded ? ChevronUpSmallIcon : ChevronDownSmallIcon} />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
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.
|
|
@@ -383,7 +383,7 @@ const MyComponent = () => (
|
|
|
383
383
|
trailingContent={
|
|
384
384
|
<>
|
|
385
385
|
<BodyText>-£100.00</BodyText>
|
|
386
|
-
<BodyText color="
|
|
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="
|
|
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-
|
|
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="
|
|
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="
|
|
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 {
|
|
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
|
|
63
|
+
<View
|
|
64
|
+
{...rest}
|
|
65
|
+
accessibilityRole={accessibilityRole ?? 'list'}
|
|
66
|
+
style={[styles.container, style]}
|
|
67
|
+
>
|
|
56
68
|
{heading ? (
|
|
57
69
|
<SectionHeader
|
|
58
70
|
heading={heading}
|
|
@@ -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 {
|
|
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 && <
|
|
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
|
|
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
|
|
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
|
|
101
|
-
| `closeOnSecondaryButtonPress` | `boolean` | Whether to automatically close the modal when the secondary button is pressed
|
|
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
|
|
104
|
-
| `loadingHeading` | `string` | The heading text to be displayed when loading is true. If not provided, the regular heading will be shown.
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
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
|
|
|
@@ -35,6 +35,7 @@ const Modal = ({
|
|
|
35
35
|
closeOnSecondaryButtonPress = true,
|
|
36
36
|
loading,
|
|
37
37
|
loadingHeading = 'Loading...',
|
|
38
|
+
loadingDescription,
|
|
38
39
|
fullscreen = false,
|
|
39
40
|
image,
|
|
40
41
|
primaryButtonProps,
|
|
@@ -151,7 +152,10 @@ const Modal = ({
|
|
|
151
152
|
<View
|
|
152
153
|
style={styles.loadingContainer}
|
|
153
154
|
accessible={Platform.OS === 'android' ? true : undefined}
|
|
154
|
-
accessibilityLabel={Platform.OS === 'android' ? 'Loading' : undefined}
|
|
155
|
+
accessibilityLabel={Platform.OS === 'android' ? (loadingHeading ?? 'Loading') : undefined}
|
|
156
|
+
accessibilityHint={
|
|
157
|
+
Platform.OS === 'android' && loadingDescription ? loadingDescription : undefined
|
|
158
|
+
}
|
|
155
159
|
screenReaderFocusable
|
|
156
160
|
ref={viewRef}
|
|
157
161
|
>
|
|
@@ -159,6 +163,7 @@ const Modal = ({
|
|
|
159
163
|
<Heading size="lg" textAlign="center">
|
|
160
164
|
{loadingHeading}
|
|
161
165
|
</Heading>
|
|
166
|
+
{loadingDescription ? <BodyText textAlign="center">{loadingDescription}</BodyText> : null}
|
|
162
167
|
</View>
|
|
163
168
|
) : (
|
|
164
169
|
<View
|
|
@@ -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
|