@utilitywarehouse/hearth-react-native 0.30.4 → 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +15 -18
- package/CHANGELOG.md +165 -0
- package/build/components/Badge/Badge.js +2 -2
- package/build/components/Badge/Badge.props.d.ts +1 -0
- package/build/components/Badge/BadgeText.d.ts +1 -1
- package/build/components/Badge/BadgeText.js +2 -2
- package/build/components/Container/Container.props.d.ts +2 -2
- package/build/components/ExpandableCard/ExpandableCard.d.ts +1 -1
- package/build/components/ExpandableCard/ExpandableCard.js +13 -2
- package/build/components/ExpandableCard/ExpandableCard.props.d.ts +43 -23
- package/build/components/ExpandableCard/ExpandableCardText.js +1 -1
- package/build/components/ExpandableCard/ExpandableCardTrigger.d.ts +3 -3
- package/build/components/ExpandableCard/ExpandableCardTrigger.props.d.ts +31 -6
- package/build/components/ExpandableCard/ExpandableCardTriggerRoot.d.ts +1 -1
- package/build/components/ExpandableCard/ExpandableCardTriggerRoot.js +13 -2
- package/build/components/Flex/Flex.props.d.ts +2 -2
- package/build/components/FormField/FormField.d.ts +5 -5
- package/build/components/FormField/FormField.js +3 -2
- package/build/components/Modal/Modal.d.ts +1 -1
- package/build/components/Modal/Modal.js +33 -39
- package/build/components/Modal/Modal.props.d.ts +8 -3
- package/build/components/Modal/Modal.shared.types.d.ts +19 -4
- package/build/components/Modal/Modal.web.d.ts +1 -1
- package/build/components/Modal/Modal.web.js +6 -3
- package/build/components/NavModal/NavModal.d.ts +1 -1
- package/build/components/NavModal/NavModal.js +10 -7
- package/build/components/NavModal/NavModal.props.d.ts +4 -3
- package/build/components/Table/TableHeaderCell.js +10 -1
- package/build/components/Textarea/Textarea.d.ts +1 -1
- package/build/components/Textarea/Textarea.js +64 -5
- package/build/components/Textarea/Textarea.props.d.ts +10 -0
- package/build/components/Textarea/TextareaRoot.js +4 -1
- package/build/core/themes.d.ts +92 -88
- package/build/tokens/color.d.ts +82 -80
- package/build/tokens/color.js +41 -40
- package/build/tokens/components/dark/alert.d.ts +6 -6
- package/build/tokens/components/dark/alert.js +6 -6
- package/build/tokens/components/dark/bottom-navigation.d.ts +2 -2
- package/build/tokens/components/dark/bottom-navigation.js +2 -2
- package/build/tokens/components/dark/checkbox.d.ts +1 -1
- package/build/tokens/components/dark/checkbox.js +1 -1
- package/build/tokens/components/dark/icon-button.d.ts +3 -3
- package/build/tokens/components/dark/icon-button.js +3 -3
- package/build/tokens/components/dark/inline-link.d.ts +1 -1
- package/build/tokens/components/dark/inline-link.js +1 -1
- package/build/tokens/components/dark/link.d.ts +3 -3
- package/build/tokens/components/dark/link.js +3 -3
- package/build/tokens/components/dark/navigation.d.ts +2 -2
- package/build/tokens/components/dark/navigation.js +2 -2
- package/build/tokens/components/dark/parts.d.ts +2 -2
- package/build/tokens/components/dark/parts.js +2 -2
- package/build/tokens/components/dark/progress-bar.d.ts +3 -3
- package/build/tokens/components/dark/progress-bar.js +3 -3
- package/build/tokens/components/dark/progress-stepper.d.ts +1 -1
- package/build/tokens/components/dark/progress-stepper.js +1 -1
- package/build/tokens/components/dark/spinner.d.ts +1 -1
- package/build/tokens/components/dark/spinner.js +1 -1
- package/build/tokens/components/dark/table.d.ts +2 -0
- package/build/tokens/components/dark/table.js +2 -0
- package/build/tokens/components/dark/time-picker.d.ts +1 -0
- package/build/tokens/components/dark/time-picker.js +1 -0
- package/build/tokens/components/light/parts.d.ts +3 -3
- package/build/tokens/components/light/parts.js +3 -3
- package/build/tokens/components/light/table.d.ts +2 -0
- package/build/tokens/components/light/table.js +2 -0
- package/build/tokens/components/light/time-picker.d.ts +1 -0
- package/build/tokens/components/light/time-picker.js +1 -0
- package/build/tokens/semantic-dark.d.ts +40 -40
- package/build/tokens/semantic-dark.js +40 -40
- package/docs/changelog.mdx +170 -0
- package/package.json +3 -3
- package/src/components/Badge/Badge.props.ts +1 -0
- package/src/components/Badge/Badge.tsx +6 -1
- package/src/components/Badge/BadgeText.tsx +8 -2
- package/src/components/Container/Container.props.ts +10 -1
- package/src/components/ExpandableCard/ExpandableCard.docs.mdx +89 -37
- package/src/components/ExpandableCard/ExpandableCard.props.ts +51 -27
- package/src/components/ExpandableCard/ExpandableCard.stories.tsx +67 -17
- package/src/components/ExpandableCard/ExpandableCard.tsx +15 -7
- package/src/components/ExpandableCard/ExpandableCardText.tsx +1 -1
- package/src/components/ExpandableCard/ExpandableCardTrigger.props.ts +37 -7
- package/src/components/ExpandableCard/ExpandableCardTriggerRoot.tsx +36 -2
- package/src/components/Flex/Flex.props.ts +16 -2
- package/src/components/FormField/FormField.tsx +2 -1
- package/src/components/List/List.stories.tsx +35 -0
- package/src/components/Modal/Modal.docs.mdx +52 -1
- package/src/components/Modal/Modal.props.ts +21 -6
- package/src/components/Modal/Modal.shared.types.ts +23 -4
- package/src/components/Modal/Modal.stories.tsx +165 -1
- package/src/components/Modal/Modal.tsx +101 -81
- package/src/components/Modal/Modal.web.tsx +29 -23
- package/src/components/NavModal/NavModal.docs.mdx +29 -0
- package/src/components/NavModal/NavModal.props.ts +11 -3
- package/src/components/NavModal/NavModal.stories.tsx +29 -0
- package/src/components/NavModal/NavModal.tsx +39 -33
- package/src/components/Table/TableHeaderCell.tsx +10 -1
- package/src/components/Textarea/Textarea.docs.mdx +33 -1
- package/src/components/Textarea/Textarea.props.ts +11 -2
- package/src/components/Textarea/Textarea.stories.tsx +21 -1
- package/src/components/Textarea/Textarea.tsx +107 -3
- package/src/components/Textarea/TextareaRoot.tsx +6 -2
- package/src/tokens/color.ts +41 -40
- package/src/tokens/components/dark/alert.ts +6 -6
- package/src/tokens/components/dark/bottom-navigation.ts +2 -2
- package/src/tokens/components/dark/checkbox.ts +1 -1
- package/src/tokens/components/dark/icon-button.ts +3 -3
- package/src/tokens/components/dark/inline-link.ts +1 -1
- package/src/tokens/components/dark/link.ts +3 -3
- package/src/tokens/components/dark/navigation.ts +2 -2
- package/src/tokens/components/dark/parts.ts +2 -2
- package/src/tokens/components/dark/progress-bar.ts +3 -3
- package/src/tokens/components/dark/progress-stepper.ts +1 -1
- package/src/tokens/components/dark/spinner.ts +1 -1
- package/src/tokens/components/dark/table.ts +2 -0
- package/src/tokens/components/dark/time-picker.ts +1 -0
- package/src/tokens/components/light/parts.ts +3 -3
- package/src/tokens/components/light/table.ts +2 -0
- package/src/tokens/components/light/time-picker.ts +1 -0
- package/src/tokens/semantic-dark.ts +40 -40
- package/vercel.json +0 -21
|
@@ -14,6 +14,7 @@ const ExpandableCard = ({
|
|
|
14
14
|
badge,
|
|
15
15
|
badgePosition = 'bottom',
|
|
16
16
|
numericValue,
|
|
17
|
+
triggerContent,
|
|
17
18
|
expandedContent,
|
|
18
19
|
duration = 200,
|
|
19
20
|
animateOpacity = true,
|
|
@@ -39,18 +40,25 @@ const ExpandableCard = ({
|
|
|
39
40
|
onExpandedChange?.(newExpanded);
|
|
40
41
|
};
|
|
41
42
|
|
|
43
|
+
const triggerProps =
|
|
44
|
+
triggerContent !== undefined
|
|
45
|
+
? { triggerContent }
|
|
46
|
+
: {
|
|
47
|
+
heading,
|
|
48
|
+
helperText,
|
|
49
|
+
leadingIcon,
|
|
50
|
+
leadingContent,
|
|
51
|
+
badge,
|
|
52
|
+
badgePosition,
|
|
53
|
+
numericValue,
|
|
54
|
+
};
|
|
55
|
+
|
|
42
56
|
const renderDefaultContent = () => (
|
|
43
57
|
<>
|
|
44
58
|
<ExpandableCardTrigger
|
|
45
59
|
onPress={handlePress}
|
|
46
60
|
disabled={disabled}
|
|
47
|
-
|
|
48
|
-
helperText={helperText}
|
|
49
|
-
leadingIcon={leadingIcon}
|
|
50
|
-
leadingContent={leadingContent}
|
|
51
|
-
badge={badge}
|
|
52
|
-
badgePosition={badgePosition}
|
|
53
|
-
numericValue={numericValue}
|
|
61
|
+
{...triggerProps}
|
|
54
62
|
isExpanded={isExpanded}
|
|
55
63
|
testID={`${testID}-trigger`}
|
|
56
64
|
/>
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import type { ComponentType, ReactNode } from 'react';
|
|
2
2
|
import type { PressableProps } from 'react-native';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
interface ExpandableCardTriggerSharedProps extends Omit<PressableProps, 'children'> {
|
|
5
|
+
/**
|
|
6
|
+
* Whether the expandable card is expanded
|
|
7
|
+
*/
|
|
8
|
+
isExpanded: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Whether to show the chevron when providing custom children.
|
|
11
|
+
* @default true
|
|
12
|
+
*/
|
|
13
|
+
showChevron?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the trigger is disabled
|
|
16
|
+
*/
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
/* Optional children */
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ExpandableCardTriggerDefaultContentProps {
|
|
5
23
|
/**
|
|
6
24
|
* The main heading text
|
|
7
25
|
*/
|
|
@@ -31,16 +49,28 @@ export interface ExpandableCardTriggerProps extends Omit<PressableProps, 'childr
|
|
|
31
49
|
* Optional numeric value to display
|
|
32
50
|
*/
|
|
33
51
|
numericValue?: string | number;
|
|
52
|
+
|
|
34
53
|
/**
|
|
35
|
-
*
|
|
54
|
+
* Custom trigger content that replaces the default trigger layout.
|
|
36
55
|
*/
|
|
37
|
-
|
|
56
|
+
triggerContent?: never;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface ExpandableCardTriggerCustomContentProps {
|
|
38
60
|
/**
|
|
39
|
-
*
|
|
61
|
+
* Custom trigger content that replaces the default trigger layout.
|
|
40
62
|
*/
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
63
|
+
triggerContent: ReactNode;
|
|
64
|
+
heading?: never;
|
|
65
|
+
helperText?: never;
|
|
66
|
+
leadingIcon?: never;
|
|
67
|
+
leadingContent?: never;
|
|
68
|
+
badge?: never;
|
|
69
|
+
badgePosition?: never;
|
|
70
|
+
numericValue?: never;
|
|
44
71
|
}
|
|
45
72
|
|
|
73
|
+
type ExpandableCardTriggerProps = ExpandableCardTriggerSharedProps &
|
|
74
|
+
(ExpandableCardTriggerDefaultContentProps | ExpandableCardTriggerCustomContentProps);
|
|
75
|
+
|
|
46
76
|
export default ExpandableCardTriggerProps;
|
|
@@ -22,7 +22,9 @@ const ExpandableCardTriggerRoot = ({
|
|
|
22
22
|
badge,
|
|
23
23
|
badgePosition = 'bottom',
|
|
24
24
|
numericValue,
|
|
25
|
+
triggerContent,
|
|
25
26
|
isExpanded,
|
|
27
|
+
showChevron = true,
|
|
26
28
|
disabled,
|
|
27
29
|
states,
|
|
28
30
|
children,
|
|
@@ -53,6 +55,8 @@ const ExpandableCardTriggerRoot = ({
|
|
|
53
55
|
return null;
|
|
54
56
|
};
|
|
55
57
|
|
|
58
|
+
const defaultAccessibilityLabel = [heading, helperText].filter(Boolean).join(', ');
|
|
59
|
+
|
|
56
60
|
const renderDefaultContent = () => (
|
|
57
61
|
<>
|
|
58
62
|
{renderLeadingContent()}
|
|
@@ -73,6 +77,36 @@ const ExpandableCardTriggerRoot = ({
|
|
|
73
77
|
</>
|
|
74
78
|
);
|
|
75
79
|
|
|
80
|
+
const renderChevron = () => (
|
|
81
|
+
<ExpandableCardTrailingContent style={styles.chevron}>
|
|
82
|
+
<ExpandableCardTrailingIcon as={isExpanded ? ChevronUpSmallIcon : ChevronDownSmallIcon} />
|
|
83
|
+
</ExpandableCardTrailingContent>
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const renderCustomTriggerContent = () => (
|
|
87
|
+
<>
|
|
88
|
+
{triggerContent}
|
|
89
|
+
{showChevron ? renderChevron() : null}
|
|
90
|
+
</>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const renderChildrenContent = () => (
|
|
94
|
+
<>
|
|
95
|
+
{children}
|
|
96
|
+
{showChevron ? renderChevron() : null}
|
|
97
|
+
</>
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
let triggerBody = renderDefaultContent();
|
|
101
|
+
|
|
102
|
+
if (triggerContent !== undefined) {
|
|
103
|
+
triggerBody = renderCustomTriggerContent();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (children) {
|
|
107
|
+
triggerBody = renderChildrenContent();
|
|
108
|
+
}
|
|
109
|
+
|
|
76
110
|
return (
|
|
77
111
|
<Pressable
|
|
78
112
|
{...props}
|
|
@@ -81,9 +115,9 @@ const ExpandableCardTriggerRoot = ({
|
|
|
81
115
|
disabled={disabled}
|
|
82
116
|
accessibilityRole="button"
|
|
83
117
|
accessibilityState={{ expanded: isExpanded, disabled }}
|
|
84
|
-
accessibilityLabel={
|
|
118
|
+
accessibilityLabel={props.accessibilityLabel || defaultAccessibilityLabel || undefined}
|
|
85
119
|
>
|
|
86
|
-
{
|
|
120
|
+
{triggerBody}
|
|
87
121
|
</Pressable>
|
|
88
122
|
);
|
|
89
123
|
};
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import type { FlexAlignType, ViewProps, ViewStyle } from 'react-native';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DisplayProps,
|
|
4
|
+
FlexLayoutProps,
|
|
5
|
+
GapProps,
|
|
6
|
+
MarginProps,
|
|
7
|
+
PaddingProps,
|
|
8
|
+
SpacingValues,
|
|
9
|
+
} from '../../types';
|
|
3
10
|
|
|
4
|
-
interface FlexProps
|
|
11
|
+
interface FlexProps
|
|
12
|
+
extends
|
|
13
|
+
ViewProps,
|
|
14
|
+
MarginProps,
|
|
15
|
+
PaddingProps,
|
|
16
|
+
FlexLayoutProps,
|
|
17
|
+
GapProps,
|
|
18
|
+
Omit<DisplayProps, 'direction'> {
|
|
5
19
|
direction?: ViewStyle['flexDirection'];
|
|
6
20
|
align?: FlexAlignType;
|
|
7
21
|
justify?: ViewStyle['justifyContent'];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createFormControl } from '@gluestack-ui/form-control';
|
|
2
2
|
import { useMemo, useState } from 'react';
|
|
3
3
|
import { View } from 'react-native';
|
|
4
|
+
import { BodyText } from '../BodyText';
|
|
4
5
|
import { HelperIcon, HelperText } from '../Helper';
|
|
5
6
|
import { FormFieldContext } from './FormField.context';
|
|
6
7
|
import FormFieldProps from './FormField.props';
|
|
@@ -94,7 +95,7 @@ const FormField = ({
|
|
|
94
95
|
accessibilityElementsHidden={shouldHandleAccessibility}
|
|
95
96
|
>
|
|
96
97
|
{label}
|
|
97
|
-
{!required ?
|
|
98
|
+
{!required ? <BodyText weight="regular"> (Optional)</BodyText> : ''}
|
|
98
99
|
</FormFieldLabelText>
|
|
99
100
|
)}
|
|
100
101
|
{!!helperText && (
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
UserMediumIcon,
|
|
11
11
|
} from '@utilitywarehouse/hearth-react-native-icons';
|
|
12
12
|
import { useState } from 'react';
|
|
13
|
+
import { FlatList } from 'react-native';
|
|
13
14
|
import { List, ListAction, ListItem, ListItemIcon, ListItemTrailingIcon } from '.';
|
|
14
15
|
import { VariantTitle } from '../../../docs/components';
|
|
15
16
|
import { Badge } from '../Badge';
|
|
@@ -398,6 +399,40 @@ export const Loading: Story = {
|
|
|
398
399
|
),
|
|
399
400
|
};
|
|
400
401
|
|
|
402
|
+
export const WithFlatList: Story = {
|
|
403
|
+
parameters: {
|
|
404
|
+
controls: { include: [] },
|
|
405
|
+
},
|
|
406
|
+
render: () => {
|
|
407
|
+
const listData = Array.from({ length: 200 }).map((_, index) => ({
|
|
408
|
+
id: index.toString(),
|
|
409
|
+
heading: `List Item ${index + 1}`,
|
|
410
|
+
helperText: `Supporting Text ${index + 1}`,
|
|
411
|
+
}));
|
|
412
|
+
|
|
413
|
+
return (
|
|
414
|
+
<List
|
|
415
|
+
container="subtleWhite"
|
|
416
|
+
heading="FlatList Example"
|
|
417
|
+
helperText="This list is rendered using FlatList for performance"
|
|
418
|
+
>
|
|
419
|
+
<FlatList
|
|
420
|
+
data={listData}
|
|
421
|
+
keyExtractor={item => item.id}
|
|
422
|
+
renderItem={({ item }) => (
|
|
423
|
+
<ListItem
|
|
424
|
+
heading={item.heading}
|
|
425
|
+
helperText={item.helperText}
|
|
426
|
+
leadingContent={<ListItemIcon as={SettingsMediumIcon} />}
|
|
427
|
+
onPress={() => console.log(`${item.heading} pressed`)}
|
|
428
|
+
/>
|
|
429
|
+
)}
|
|
430
|
+
/>
|
|
431
|
+
</List>
|
|
432
|
+
);
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
|
|
401
436
|
export const KitchenSink: Story = {
|
|
402
437
|
parameters: {
|
|
403
438
|
controls: { include: [] },
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Canvas, Controls, Meta, Story } from '@storybook/addon-docs/blocks';
|
|
2
|
-
import { BodyText, BottomSheetModal, Box, Button, Center, Heading, Modal } from '../../';
|
|
2
|
+
import { BodyText, BottomSheetModal, Box, Button, Center, Flex, Heading, Modal } from '../../';
|
|
3
3
|
import StorybookLink from '../../../../../shared/storybook/StorybookLink';
|
|
4
4
|
import { BackToTopButton, UsageWrap, ViewFigmaButton } from '../../../docs/components';
|
|
5
5
|
import * as Stories from './Modal.stories';
|
|
@@ -28,6 +28,7 @@ If you need a modal layout inside a React Navigation modal screen, use <Storyboo
|
|
|
28
28
|
- [Modal with Image](#modal-with-image)
|
|
29
29
|
- [Fullscreen Modal](#fullscreen-modal)
|
|
30
30
|
- [Modal with Custom Content](#modal-with-custom-content)
|
|
31
|
+
- [Sticky Custom Footer](#sticky-custom-footer)
|
|
31
32
|
- [Loading State](#loading-state)
|
|
32
33
|
- [Without Close Button](#without-close-button)
|
|
33
34
|
- [Single Action Modal](#single-action-modal)
|
|
@@ -107,9 +108,13 @@ The Modal component extends the `BottomSheetModal` component and accepts all of
|
|
|
107
108
|
| `children` | `ReactNode` | Custom content to display in the modal body | - |
|
|
108
109
|
| `primaryButtonProps` | `Omit<ButtonWithoutChildrenProps, 'children'>` | Additional props to pass to the primary button (colorScheme defaults to 'highlight', variant to 'solid') | - |
|
|
109
110
|
| `secondaryButtonProps` | `Omit<ButtonWithoutChildrenProps, 'children'>` | Additional props to pass to the secondary button (colorScheme defaults to 'functional', variant to 'outline') | - |
|
|
111
|
+
| `footer` | `ReactNode` | Custom footer content that replaces the built-in action buttons | - |
|
|
112
|
+
| `footerStyle` | `StyleProp<ViewStyle>` | Styles applied to the footer container, useful for sticky footer shadows or custom spacing | - |
|
|
110
113
|
| `closeButtonProps` | `Omit<UnstyledIconButtonProps, 'children'>` | Additional props to pass to the close button | - |
|
|
111
114
|
| `fullscreen` | `boolean` | Whether the modal should take up the full screen height | `false` |
|
|
112
115
|
|
|
116
|
+
When `footer` is provided, the primary and secondary button props are not available. Build your footer actions directly inside the custom footer content instead.
|
|
117
|
+
|
|
113
118
|
\* use this to detect if the modal has been opened or closed, index 0 indicates open state and -1 indicates closed state
|
|
114
119
|
|
|
115
120
|
### `ModalImage` Props
|
|
@@ -371,6 +376,52 @@ const CustomContentModal = () => {
|
|
|
371
376
|
};
|
|
372
377
|
```
|
|
373
378
|
|
|
379
|
+
### Sticky Custom Footer
|
|
380
|
+
|
|
381
|
+
Replace the built-in buttons with a custom sticky footer when you need custom layouts or button arrangements:
|
|
382
|
+
|
|
383
|
+
<Canvas of={Stories.StickyCustomFooter} />
|
|
384
|
+
|
|
385
|
+
```tsx
|
|
386
|
+
const StickyCustomFooterModal = () => {
|
|
387
|
+
const modalRef = useRef<BottomSheetModal>(null);
|
|
388
|
+
|
|
389
|
+
return (
|
|
390
|
+
<>
|
|
391
|
+
<Button onPress={() => modalRef.current?.present()}>Show Custom Footer Modal</Button>
|
|
392
|
+
|
|
393
|
+
<Modal
|
|
394
|
+
ref={modalRef}
|
|
395
|
+
heading="Update billing details"
|
|
396
|
+
description="Use a custom sticky footer when you need horizontal actions or extra decoration."
|
|
397
|
+
footer={
|
|
398
|
+
<Flex direction="row" spacing="md" pt="250">
|
|
399
|
+
<Button
|
|
400
|
+
variant="outline"
|
|
401
|
+
colorScheme="functional"
|
|
402
|
+
onPress={() => modalRef.current?.dismiss()}
|
|
403
|
+
style={{ flex: 1 }}
|
|
404
|
+
>
|
|
405
|
+
Cancel
|
|
406
|
+
</Button>
|
|
407
|
+
<Button onPress={() => modalRef.current?.dismiss()} style={{ flex: 1 }}>
|
|
408
|
+
Save changes
|
|
409
|
+
</Button>
|
|
410
|
+
</Flex>
|
|
411
|
+
}
|
|
412
|
+
footerStyle={{
|
|
413
|
+
boxShadow: '0px -6px 12px rgba(16, 24, 40, 0.12)',
|
|
414
|
+
}}
|
|
415
|
+
>
|
|
416
|
+
<Box gap="200">
|
|
417
|
+
<BodyText>Review the changes before saving.</BodyText>
|
|
418
|
+
</Box>
|
|
419
|
+
</Modal>
|
|
420
|
+
</>
|
|
421
|
+
);
|
|
422
|
+
};
|
|
423
|
+
```
|
|
424
|
+
|
|
374
425
|
### Loading State
|
|
375
426
|
|
|
376
427
|
Show a loading spinner while processing:
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import { BottomSheetProps } from '../BottomSheet';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ModalButtonFooterProps,
|
|
4
|
+
ModalCommonBaseProps,
|
|
5
|
+
ModalCustomFooterProps,
|
|
6
|
+
} from './Modal.shared.types';
|
|
3
7
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
type ModalBaseProps = Omit<BottomSheetProps, 'children'> &
|
|
9
|
+
ModalCommonBaseProps & {
|
|
10
|
+
fullscreen?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type ModalProps =
|
|
14
|
+
| (ModalBaseProps &
|
|
15
|
+
ModalButtonFooterProps & {
|
|
16
|
+
closeOnPrimaryButtonPress?: boolean;
|
|
17
|
+
closeOnSecondaryButtonPress?: boolean;
|
|
18
|
+
})
|
|
19
|
+
| (ModalBaseProps &
|
|
20
|
+
ModalCustomFooterProps & {
|
|
21
|
+
closeOnPrimaryButtonPress?: never;
|
|
22
|
+
closeOnSecondaryButtonPress?: never;
|
|
23
|
+
});
|
|
9
24
|
|
|
10
25
|
export default ModalProps;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { ViewProps } from 'react-native';
|
|
2
|
+
import { StyleProp, ViewProps, ViewStyle } from 'react-native';
|
|
3
3
|
import { ButtonWithoutChildrenProps } from '../Button/Button.props';
|
|
4
4
|
import { UnstyledIconButtonProps } from '../UnstyledIconButton';
|
|
5
5
|
|
|
6
|
-
export interface
|
|
6
|
+
export interface ModalCommonBaseProps {
|
|
7
7
|
loading?: boolean;
|
|
8
8
|
image?: ReactNode;
|
|
9
9
|
showCloseButton?: boolean;
|
|
@@ -13,12 +13,31 @@ export interface ModalCommonProps {
|
|
|
13
13
|
description?: string;
|
|
14
14
|
stickyFooter?: boolean;
|
|
15
15
|
children?: ViewProps['children'];
|
|
16
|
+
onPressCloseButton?: () => void;
|
|
17
|
+
closeButtonProps?: Omit<UnstyledIconButtonProps, 'children'>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ModalButtonFooterProps {
|
|
16
21
|
onPressPrimaryButton?: () => void;
|
|
17
22
|
primaryButtonText?: string;
|
|
18
23
|
onPressSecondaryButton?: () => void;
|
|
19
24
|
secondaryButtonText?: string;
|
|
20
|
-
onPressCloseButton?: () => void;
|
|
21
25
|
primaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
|
|
22
26
|
secondaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
|
|
23
|
-
|
|
27
|
+
footer?: never;
|
|
28
|
+
footerStyle?: StyleProp<ViewStyle>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ModalCustomFooterProps {
|
|
32
|
+
footer: ReactNode;
|
|
33
|
+
footerStyle?: StyleProp<ViewStyle>;
|
|
34
|
+
onPressPrimaryButton?: never;
|
|
35
|
+
primaryButtonText?: never;
|
|
36
|
+
onPressSecondaryButton?: never;
|
|
37
|
+
secondaryButtonText?: never;
|
|
38
|
+
primaryButtonProps?: never;
|
|
39
|
+
secondaryButtonProps?: never;
|
|
24
40
|
}
|
|
41
|
+
|
|
42
|
+
export type ModalCommonProps = ModalCommonBaseProps &
|
|
43
|
+
(ModalButtonFooterProps | ModalCustomFooterProps);
|
|
@@ -5,9 +5,10 @@ import { Modal, ModalImage } from '.';
|
|
|
5
5
|
import pigs from '../../../docs/assets/pigs.png';
|
|
6
6
|
import { ViewWrap } from '../../../docs/components';
|
|
7
7
|
import { BodyText } from '../BodyText';
|
|
8
|
-
import { BottomSheetModal } from '../BottomSheet';
|
|
8
|
+
import { BottomSheetModal, BottomSheetModalProvider } from '../BottomSheet';
|
|
9
9
|
import { Box } from '../Box';
|
|
10
10
|
import { Button } from '../Button';
|
|
11
|
+
import { Flex } from '../Flex';
|
|
11
12
|
|
|
12
13
|
const meta = {
|
|
13
14
|
title: 'Stories / Modal',
|
|
@@ -164,6 +165,56 @@ export const WithCustomContent = () => {
|
|
|
164
165
|
);
|
|
165
166
|
};
|
|
166
167
|
|
|
168
|
+
export const StickyCustomFooter = () => {
|
|
169
|
+
const modalRef = useRef<BottomSheetModal>(null);
|
|
170
|
+
|
|
171
|
+
const openModal = () => {
|
|
172
|
+
modalRef.current?.present();
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const closeModal = () => {
|
|
176
|
+
modalRef.current?.dismiss();
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<View style={Platform.OS === 'web' ? { width: 400, height: 400 } : {}}>
|
|
181
|
+
<ViewWrap>
|
|
182
|
+
<Button onPress={openModal}>Open Modal</Button>
|
|
183
|
+
|
|
184
|
+
<Modal
|
|
185
|
+
ref={modalRef}
|
|
186
|
+
heading="Update billing details"
|
|
187
|
+
description="Review the changes below, then save or discard them using a custom sticky footer."
|
|
188
|
+
onPressCloseButton={closeModal}
|
|
189
|
+
footer={
|
|
190
|
+
<Flex direction="row" spacing="md" pt="250">
|
|
191
|
+
<Button
|
|
192
|
+
variant="outline"
|
|
193
|
+
colorScheme="functional"
|
|
194
|
+
onPress={closeModal}
|
|
195
|
+
style={{ flex: 1 }}
|
|
196
|
+
>
|
|
197
|
+
Cancel
|
|
198
|
+
</Button>
|
|
199
|
+
<Button onPress={closeModal} style={{ flex: 1 }}>
|
|
200
|
+
Save changes
|
|
201
|
+
</Button>
|
|
202
|
+
</Flex>
|
|
203
|
+
}
|
|
204
|
+
footerStyle={{
|
|
205
|
+
boxShadow: '0px -6px 12px rgba(16, 24, 40, 0.12)',
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
<Box gap="200">
|
|
209
|
+
<BodyText>Billing address: 14 Park Street, Bristol</BodyText>
|
|
210
|
+
<BodyText>Preferred payment day: 15th of each month</BodyText>
|
|
211
|
+
</Box>
|
|
212
|
+
</Modal>
|
|
213
|
+
</ViewWrap>
|
|
214
|
+
</View>
|
|
215
|
+
);
|
|
216
|
+
};
|
|
217
|
+
|
|
167
218
|
export const Loading = () => {
|
|
168
219
|
const modalRef = useRef<BottomSheetModal>(null);
|
|
169
220
|
|
|
@@ -239,3 +290,116 @@ export const FullscreenModal: Story = {
|
|
|
239
290
|
);
|
|
240
291
|
},
|
|
241
292
|
};
|
|
293
|
+
|
|
294
|
+
export const NoStickyFooter: Story = {
|
|
295
|
+
render: () => {
|
|
296
|
+
const modalRef = useRef<BottomSheetModal>(null);
|
|
297
|
+
|
|
298
|
+
const openModal = () => {
|
|
299
|
+
modalRef.current?.present();
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const closeModal = () => {
|
|
303
|
+
modalRef.current?.dismiss();
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
return (
|
|
307
|
+
<View style={Platform.OS === 'web' ? { width: 400, height: 400 } : {}}>
|
|
308
|
+
<ViewWrap>
|
|
309
|
+
<Button onPress={openModal}>Open Modal</Button>
|
|
310
|
+
|
|
311
|
+
<Modal
|
|
312
|
+
ref={modalRef}
|
|
313
|
+
heading="Modal Heading"
|
|
314
|
+
description="This is a modal description without a sticky footer."
|
|
315
|
+
onPressCloseButton={closeModal}
|
|
316
|
+
primaryButtonText="Primary"
|
|
317
|
+
onPressPrimaryButton={closeModal}
|
|
318
|
+
secondaryButtonText="Cancel"
|
|
319
|
+
onPressSecondaryButton={closeModal}
|
|
320
|
+
stickyFooter={false}
|
|
321
|
+
>
|
|
322
|
+
<Box gap="200">
|
|
323
|
+
<BodyText>This is a modal with content.</BodyText>
|
|
324
|
+
<BodyText>You can swipe it up and down to close.</BodyText>
|
|
325
|
+
</Box>
|
|
326
|
+
</Modal>
|
|
327
|
+
</ViewWrap>
|
|
328
|
+
</View>
|
|
329
|
+
);
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export const NoFooter: Story = {
|
|
334
|
+
render: () => {
|
|
335
|
+
const modalRef = useRef<BottomSheetModal>(null);
|
|
336
|
+
|
|
337
|
+
const openModal = () => {
|
|
338
|
+
modalRef.current?.present();
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const closeModal = () => {
|
|
342
|
+
modalRef.current?.dismiss();
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
return (
|
|
346
|
+
<View style={Platform.OS === 'web' ? { width: 400, height: 400 } : {}}>
|
|
347
|
+
<ViewWrap>
|
|
348
|
+
<Button onPress={openModal}>Open Modal</Button>
|
|
349
|
+
|
|
350
|
+
<Modal
|
|
351
|
+
ref={modalRef}
|
|
352
|
+
heading="Modal Heading"
|
|
353
|
+
description="This is a modal description without a footer."
|
|
354
|
+
onPressCloseButton={closeModal}
|
|
355
|
+
>
|
|
356
|
+
<Box gap="200">
|
|
357
|
+
<BodyText>This is a modal with content.</BodyText>
|
|
358
|
+
<BodyText>You can swipe it up and down to close.</BodyText>
|
|
359
|
+
</Box>
|
|
360
|
+
</Modal>
|
|
361
|
+
</ViewWrap>
|
|
362
|
+
</View>
|
|
363
|
+
);
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
export const NoSafeArea: Story = {
|
|
368
|
+
render: () => {
|
|
369
|
+
const modalRef = useRef<BottomSheetModal>(null);
|
|
370
|
+
|
|
371
|
+
const openModal = () => {
|
|
372
|
+
modalRef.current?.present();
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const closeModal = () => {
|
|
376
|
+
modalRef.current?.dismiss();
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
return (
|
|
380
|
+
<BottomSheetModalProvider useSafeAreaInsets={false}>
|
|
381
|
+
<View style={Platform.OS === 'web' ? { width: 400, height: 400 } : {}}>
|
|
382
|
+
<ViewWrap>
|
|
383
|
+
<Button onPress={openModal}>Open Modal</Button>
|
|
384
|
+
|
|
385
|
+
<Modal
|
|
386
|
+
ref={modalRef}
|
|
387
|
+
heading="Modal Heading"
|
|
388
|
+
description="This is a modal description without safe area insets."
|
|
389
|
+
onPressCloseButton={closeModal}
|
|
390
|
+
primaryButtonText="Primary"
|
|
391
|
+
onPressPrimaryButton={closeModal}
|
|
392
|
+
secondaryButtonText="Cancel"
|
|
393
|
+
onPressSecondaryButton={closeModal}
|
|
394
|
+
>
|
|
395
|
+
<Box gap="200">
|
|
396
|
+
<BodyText>This is a modal with content.</BodyText>
|
|
397
|
+
<BodyText>You can swipe it up and down to close.</BodyText>
|
|
398
|
+
</Box>
|
|
399
|
+
</Modal>
|
|
400
|
+
</ViewWrap>
|
|
401
|
+
</View>
|
|
402
|
+
</BottomSheetModalProvider>
|
|
403
|
+
);
|
|
404
|
+
},
|
|
405
|
+
};
|