@utilitywarehouse/hearth-react-native 0.7.0 → 0.8.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 +1 -1
- package/CHANGELOG.md +18 -0
- package/build/components/Banner/Banner.d.ts +6 -0
- package/build/components/Banner/Banner.js +161 -0
- package/build/components/Banner/Banner.props.d.ts +82 -0
- package/build/components/Banner/Banner.props.js +1 -0
- package/build/components/Banner/index.d.ts +2 -0
- package/build/components/Banner/index.js +1 -0
- package/build/components/BottomSheet/BottomSheetBackdrop.js +1 -5
- package/build/components/BottomSheet/BottomSheetFlatList.js +1 -5
- package/build/components/BottomSheet/BottomSheetHandle.js +1 -5
- package/build/components/BottomSheet/useBottomSheetLogic.d.ts +1 -1
- package/build/components/Expandable/Expandable.d.ts +6 -0
- package/build/components/Expandable/Expandable.js +44 -0
- package/build/components/Expandable/Expandable.props.d.ts +38 -0
- package/build/components/Expandable/Expandable.props.js +1 -0
- package/build/components/Expandable/index.d.ts +2 -0
- package/build/components/Expandable/index.js +1 -0
- package/build/components/ExpandableCard/ExpandableCard.d.ts +6 -0
- package/build/components/ExpandableCard/ExpandableCard.js +23 -0
- package/build/components/ExpandableCard/ExpandableCard.props.d.ts +69 -0
- package/build/components/ExpandableCard/ExpandableCard.props.js +1 -0
- package/build/components/ExpandableCard/ExpandableCardContent.d.ts +6 -0
- package/build/components/ExpandableCard/ExpandableCardContent.js +14 -0
- package/build/components/ExpandableCard/ExpandableCardExpandedContent.d.ts +11 -0
- package/build/components/ExpandableCard/ExpandableCardExpandedContent.js +18 -0
- package/build/components/ExpandableCard/ExpandableCardGroup.d.ts +6 -0
- package/build/components/ExpandableCard/ExpandableCardGroup.js +17 -0
- package/build/components/ExpandableCard/ExpandableCardGroup.props.d.ts +25 -0
- package/build/components/ExpandableCard/ExpandableCardGroup.props.js +1 -0
- package/build/components/ExpandableCard/ExpandableCardHelperText.d.ts +6 -0
- package/build/components/ExpandableCard/ExpandableCardHelperText.js +13 -0
- package/build/components/ExpandableCard/ExpandableCardIcon.d.ts +9 -0
- package/build/components/ExpandableCard/ExpandableCardIcon.js +19 -0
- package/build/components/ExpandableCard/ExpandableCardLeadingContent.d.ts +6 -0
- package/build/components/ExpandableCard/ExpandableCardLeadingContent.js +5 -0
- package/build/components/ExpandableCard/ExpandableCardText.d.ts +6 -0
- package/build/components/ExpandableCard/ExpandableCardText.js +7 -0
- package/build/components/ExpandableCard/ExpandableCardTrailingContent.d.ts +6 -0
- package/build/components/ExpandableCard/ExpandableCardTrailingContent.js +5 -0
- package/build/components/ExpandableCard/ExpandableCardTrailingIcon.d.ts +9 -0
- package/build/components/ExpandableCard/ExpandableCardTrailingIcon.js +17 -0
- package/build/components/ExpandableCard/ExpandableCardTrigger.d.ts +17 -0
- package/build/components/ExpandableCard/ExpandableCardTrigger.js +7 -0
- package/build/components/ExpandableCard/ExpandableCardTrigger.props.d.ts +44 -0
- package/build/components/ExpandableCard/ExpandableCardTrigger.props.js +1 -0
- package/build/components/ExpandableCard/ExpandableCardTriggerRoot.d.ts +11 -0
- package/build/components/ExpandableCard/ExpandableCardTriggerRoot.js +91 -0
- package/build/components/ExpandableCard/index.d.ts +14 -0
- package/build/components/ExpandableCard/index.js +11 -0
- package/build/components/HighlightBanner/HighlightBanner.d.ts +6 -0
- package/build/components/HighlightBanner/HighlightBanner.js +96 -0
- package/build/components/HighlightBanner/HighlightBanner.props.d.ts +14 -0
- package/build/components/HighlightBanner/HighlightBanner.props.js +1 -0
- package/build/components/HighlightBanner/index.d.ts +2 -0
- package/build/components/HighlightBanner/index.js +1 -0
- package/build/components/Spinner/Spinner.js +0 -2
- package/build/components/Spinner/Spinner.web.d.ts +2 -1
- package/build/components/Spinner/Spinner.web.js +0 -2
- package/build/components/Switch/Switch.web.js +0 -1
- package/build/components/Tabs/TabsList.js +1 -6
- package/build/components/index.d.ts +4 -0
- package/build/components/index.js +4 -0
- package/docs/components/AllComponents.web.tsx +75 -4
- package/docs/components/VariantTitle.tsx +1 -1
- package/package.json +4 -4
- package/src/components/Banner/Banner.docs.mdx +402 -0
- package/src/components/Banner/Banner.props.ts +106 -0
- package/src/components/Banner/Banner.stories.tsx +494 -0
- package/src/components/Banner/Banner.tsx +264 -0
- package/src/components/Banner/index.ts +2 -0
- package/src/components/BottomSheet/BottomSheetBackdrop.tsx +0 -1
- package/src/components/BottomSheet/BottomSheetFlatList.tsx +0 -1
- package/src/components/BottomSheet/BottomSheetHandle.tsx +0 -1
- package/src/components/Card/Card.docs.mdx +10 -2
- package/src/components/Expandable/Expandable.docs.mdx +201 -0
- package/src/components/Expandable/Expandable.props.ts +46 -0
- package/src/components/Expandable/Expandable.stories.tsx +284 -0
- package/src/components/Expandable/Expandable.tsx +92 -0
- package/src/components/Expandable/index.ts +2 -0
- package/src/components/ExpandableCard/ExpandableCard.docs.mdx +312 -0
- package/src/components/ExpandableCard/ExpandableCard.props.ts +85 -0
- package/src/components/ExpandableCard/ExpandableCard.stories.tsx +326 -0
- package/src/components/ExpandableCard/ExpandableCard.tsx +76 -0
- package/src/components/ExpandableCard/ExpandableCardContent.tsx +21 -0
- package/src/components/ExpandableCard/ExpandableCardExpandedContent.tsx +42 -0
- package/src/components/ExpandableCard/ExpandableCardGroup.props.ts +31 -0
- package/src/components/ExpandableCard/ExpandableCardGroup.tsx +40 -0
- package/src/components/ExpandableCard/ExpandableCardHelperText.tsx +21 -0
- package/src/components/ExpandableCard/ExpandableCardIcon.tsx +32 -0
- package/src/components/ExpandableCard/ExpandableCardLeadingContent.tsx +9 -0
- package/src/components/ExpandableCard/ExpandableCardText.tsx +14 -0
- package/src/components/ExpandableCard/ExpandableCardTrailingContent.tsx +9 -0
- package/src/components/ExpandableCard/ExpandableCardTrailingIcon.tsx +30 -0
- package/src/components/ExpandableCard/ExpandableCardTrigger.props.ts +47 -0
- package/src/components/ExpandableCard/ExpandableCardTrigger.tsx +10 -0
- package/src/components/ExpandableCard/ExpandableCardTriggerRoot.tsx +154 -0
- package/src/components/ExpandableCard/index.ts +14 -0
- package/src/components/HighlightBanner/HighlightBanner.docs.mdx +296 -0
- package/src/components/HighlightBanner/HighlightBanner.props.ts +29 -0
- package/src/components/HighlightBanner/HighlightBanner.stories.tsx +275 -0
- package/src/components/HighlightBanner/HighlightBanner.tsx +134 -0
- package/src/components/HighlightBanner/index.ts +2 -0
- package/src/components/Spinner/Spinner.tsx +0 -2
- package/src/components/Spinner/Spinner.web.tsx +0 -2
- package/src/components/Switch/Switch.web.tsx +1 -5
- package/src/components/Tabs/TabsList.tsx +0 -2
- package/src/components/index.ts +4 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
2
|
+
import type BadgeProps from '../Badge/Badge.props';
|
|
3
|
+
import type CardProps from '../Card/Card.props';
|
|
4
|
+
|
|
5
|
+
export interface ExpandableCardProps extends Omit<CardProps, 'children'> {
|
|
6
|
+
/**
|
|
7
|
+
* Whether the card is expanded
|
|
8
|
+
*/
|
|
9
|
+
expanded?: boolean;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Callback when expanded state changes
|
|
13
|
+
*/
|
|
14
|
+
onExpandedChange?: (expanded: boolean) => void;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The heading text displayed in the trigger
|
|
18
|
+
*/
|
|
19
|
+
heading?: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Optional helper text displayed below the heading
|
|
23
|
+
*/
|
|
24
|
+
helperText?: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Leading icon component
|
|
28
|
+
*/
|
|
29
|
+
leadingIcon?: ComponentType;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Leading content (icon or custom element)
|
|
33
|
+
*/
|
|
34
|
+
leadingContent?: ReactNode;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Badge to display
|
|
38
|
+
*/
|
|
39
|
+
badge?: BadgeProps;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Badge position
|
|
43
|
+
* @default 'bottom'
|
|
44
|
+
*/
|
|
45
|
+
badgePosition?: 'top' | 'bottom';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Numeric value to display on the right
|
|
49
|
+
*/
|
|
50
|
+
numericValue?: string | number;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The content to show when expanded
|
|
54
|
+
*/
|
|
55
|
+
expandedContent?: ReactNode;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Duration of the expansion animation in milliseconds
|
|
59
|
+
* @default 200
|
|
60
|
+
*/
|
|
61
|
+
duration?: number;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether to animate opacity during expansion
|
|
65
|
+
* @default true
|
|
66
|
+
*/
|
|
67
|
+
animateOpacity?: boolean;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Whether the card is disabled
|
|
71
|
+
*/
|
|
72
|
+
disabled?: boolean;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Test ID for testing
|
|
76
|
+
*/
|
|
77
|
+
testID?: string;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Custom children for advanced composition
|
|
81
|
+
*/
|
|
82
|
+
children?: ReactNode;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default ExpandableCardProps;
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-native';
|
|
2
|
+
import {
|
|
3
|
+
BillMediumIcon,
|
|
4
|
+
ElectricityMediumIcon,
|
|
5
|
+
GasMediumIcon,
|
|
6
|
+
PaymentMediumIcon,
|
|
7
|
+
SettingsMediumIcon,
|
|
8
|
+
} from '@utilitywarehouse/hearth-react-native-icons';
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { BodyText, IconContainer, Link } from '../../components';
|
|
11
|
+
import ExpandableCard from './ExpandableCard';
|
|
12
|
+
import ExpandableCardExpandedContent from './ExpandableCardExpandedContent';
|
|
13
|
+
import ExpandableCardGroup from './ExpandableCardGroup';
|
|
14
|
+
import ExpandableCardIcon from './ExpandableCardIcon';
|
|
15
|
+
import ExpandableCardTrigger from './ExpandableCardTrigger';
|
|
16
|
+
|
|
17
|
+
const meta = {
|
|
18
|
+
title: 'Stories / ExpandableCard',
|
|
19
|
+
component: ExpandableCard,
|
|
20
|
+
parameters: {
|
|
21
|
+
layout: 'centered',
|
|
22
|
+
},
|
|
23
|
+
argTypes: {
|
|
24
|
+
expanded: {
|
|
25
|
+
control: 'boolean',
|
|
26
|
+
description: 'Whether the card is expanded',
|
|
27
|
+
},
|
|
28
|
+
heading: {
|
|
29
|
+
control: 'text',
|
|
30
|
+
description: 'The heading text',
|
|
31
|
+
},
|
|
32
|
+
helperText: {
|
|
33
|
+
control: 'text',
|
|
34
|
+
description: 'Helper text below heading',
|
|
35
|
+
},
|
|
36
|
+
disabled: {
|
|
37
|
+
control: 'boolean',
|
|
38
|
+
description: 'Whether the card is disabled',
|
|
39
|
+
},
|
|
40
|
+
variant: {
|
|
41
|
+
control: 'radio',
|
|
42
|
+
description: 'The variant style of the card',
|
|
43
|
+
options: ['subtle', 'emphasis'],
|
|
44
|
+
},
|
|
45
|
+
duration: {
|
|
46
|
+
control: { type: 'number', min: 100, max: 1000, step: 50 },
|
|
47
|
+
description: 'Animation duration in milliseconds',
|
|
48
|
+
},
|
|
49
|
+
animateOpacity: {
|
|
50
|
+
control: 'boolean',
|
|
51
|
+
description: 'Whether to animate opacity',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
args: {
|
|
55
|
+
heading: 'Expandable Card',
|
|
56
|
+
helperText: 'Click to expand',
|
|
57
|
+
expanded: false,
|
|
58
|
+
disabled: false,
|
|
59
|
+
duration: 200,
|
|
60
|
+
animateOpacity: true,
|
|
61
|
+
variant: 'subtle',
|
|
62
|
+
},
|
|
63
|
+
} satisfies Meta<typeof ExpandableCard>;
|
|
64
|
+
|
|
65
|
+
export default meta;
|
|
66
|
+
type Story = StoryObj<typeof meta>;
|
|
67
|
+
|
|
68
|
+
export const Playground: Story = {
|
|
69
|
+
args: {
|
|
70
|
+
expandedContent: (
|
|
71
|
+
<BodyText>
|
|
72
|
+
This is the expanded content. It can contain any component or content you need.
|
|
73
|
+
</BodyText>
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const BasicExample: Story = {
|
|
79
|
+
render: () => (
|
|
80
|
+
<ExpandableCard
|
|
81
|
+
heading="Order Details"
|
|
82
|
+
helperText="View your order information"
|
|
83
|
+
expandedContent={
|
|
84
|
+
<>
|
|
85
|
+
<BodyText>Order #12345</BodyText>
|
|
86
|
+
<BodyText>Status: Delivered</BodyText>
|
|
87
|
+
<BodyText>Date: 10 Nov 2025</BodyText>
|
|
88
|
+
</>
|
|
89
|
+
}
|
|
90
|
+
style={{ width: 350 }}
|
|
91
|
+
/>
|
|
92
|
+
),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const WithLeadingIcon: Story = {
|
|
96
|
+
render: () => (
|
|
97
|
+
<ExpandableCard
|
|
98
|
+
heading="Settings"
|
|
99
|
+
helperText="Configure your preferences"
|
|
100
|
+
leadingIcon={SettingsMediumIcon}
|
|
101
|
+
expandedContent={
|
|
102
|
+
<>
|
|
103
|
+
<BodyText>• Notifications</BodyText>
|
|
104
|
+
<BodyText>• Privacy</BodyText>
|
|
105
|
+
<BodyText>• Account</BodyText>
|
|
106
|
+
</>
|
|
107
|
+
}
|
|
108
|
+
style={{ width: 350 }}
|
|
109
|
+
/>
|
|
110
|
+
),
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const WithIconContainer: Story = {
|
|
114
|
+
render: () => (
|
|
115
|
+
<ExpandableCard
|
|
116
|
+
heading="Electricity"
|
|
117
|
+
helperText="Last reading 23/03/24"
|
|
118
|
+
leadingContent={
|
|
119
|
+
<IconContainer icon={ElectricityMediumIcon} size="md" variant="emphasis" color="energy" />
|
|
120
|
+
}
|
|
121
|
+
expandedContent={
|
|
122
|
+
<>
|
|
123
|
+
<BodyText>Current Usage: 245 kWh</BodyText>
|
|
124
|
+
<BodyText>Estimated Cost: £45.50</BodyText>
|
|
125
|
+
<BodyText>Next Reading: 23/04/24</BodyText>
|
|
126
|
+
</>
|
|
127
|
+
}
|
|
128
|
+
style={{ width: 350 }}
|
|
129
|
+
/>
|
|
130
|
+
),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const WithBadge: Story = {
|
|
134
|
+
render: () => (
|
|
135
|
+
<ExpandableCard
|
|
136
|
+
heading="New Feature"
|
|
137
|
+
helperText="Check out what's new"
|
|
138
|
+
badge={{ text: 'New', colorScheme: 'info' }}
|
|
139
|
+
expandedContent={
|
|
140
|
+
<BodyText>We've added new features to improve your experience. Explore them now!</BodyText>
|
|
141
|
+
}
|
|
142
|
+
style={{ width: 350 }}
|
|
143
|
+
/>
|
|
144
|
+
),
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const WithNumericValue: Story = {
|
|
148
|
+
render: () => (
|
|
149
|
+
<ExpandableCard
|
|
150
|
+
heading="Total Balance"
|
|
151
|
+
helperText="Current account balance"
|
|
152
|
+
numericValue="£123.45"
|
|
153
|
+
expandedContent={
|
|
154
|
+
<>
|
|
155
|
+
<BodyText>Available: £100.00</BodyText>
|
|
156
|
+
<BodyText>Pending: £23.45</BodyText>
|
|
157
|
+
</>
|
|
158
|
+
}
|
|
159
|
+
style={{ width: 350 }}
|
|
160
|
+
/>
|
|
161
|
+
),
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const Disabled: Story = {
|
|
165
|
+
render: () => (
|
|
166
|
+
<ExpandableCard
|
|
167
|
+
heading="Disabled Card"
|
|
168
|
+
helperText="This card cannot be expanded"
|
|
169
|
+
disabled
|
|
170
|
+
expandedContent={<BodyText>This content is not accessible</BodyText>}
|
|
171
|
+
style={{ width: 350 }}
|
|
172
|
+
/>
|
|
173
|
+
),
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const Controlled: Story = {
|
|
177
|
+
render: () => {
|
|
178
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<div style={{ width: 350 }}>
|
|
182
|
+
<BodyText style={{ marginBottom: 8 }}>
|
|
183
|
+
Status: {expanded ? 'Expanded' : 'Collapsed'}
|
|
184
|
+
</BodyText>
|
|
185
|
+
<ExpandableCard
|
|
186
|
+
heading="Controlled Card"
|
|
187
|
+
helperText="Externally controlled state"
|
|
188
|
+
expanded={expanded}
|
|
189
|
+
onExpandedChange={setExpanded}
|
|
190
|
+
expandedContent={
|
|
191
|
+
<BodyText>This card's state is controlled by the parent component.</BodyText>
|
|
192
|
+
}
|
|
193
|
+
/>
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export const CardGroup: Story = {
|
|
200
|
+
render: () => (
|
|
201
|
+
<ExpandableCardGroup
|
|
202
|
+
heading="Your Services"
|
|
203
|
+
helperText="View details for each service"
|
|
204
|
+
headerTrailingContent={<Link href="#">View all</Link>}
|
|
205
|
+
>
|
|
206
|
+
<ExpandableCard
|
|
207
|
+
heading="Electricity"
|
|
208
|
+
helperText="Last reading 23/03/24"
|
|
209
|
+
leadingContent={
|
|
210
|
+
<IconContainer icon={ElectricityMediumIcon} size="md" variant="emphasis" color="energy" />
|
|
211
|
+
}
|
|
212
|
+
expandedContent={
|
|
213
|
+
<>
|
|
214
|
+
<BodyText>Current Usage: 245 kWh</BodyText>
|
|
215
|
+
<BodyText>Estimated Cost: £45.50</BodyText>
|
|
216
|
+
</>
|
|
217
|
+
}
|
|
218
|
+
/>
|
|
219
|
+
<ExpandableCard
|
|
220
|
+
heading="Gas"
|
|
221
|
+
helperText="Last reading 23/03/24"
|
|
222
|
+
leadingContent={
|
|
223
|
+
<IconContainer icon={GasMediumIcon} size="md" variant="emphasis" color="energy" />
|
|
224
|
+
}
|
|
225
|
+
expandedContent={
|
|
226
|
+
<>
|
|
227
|
+
<BodyText>Current Usage: 180 kWh</BodyText>
|
|
228
|
+
<BodyText>Estimated Cost: £32.00</BodyText>
|
|
229
|
+
</>
|
|
230
|
+
}
|
|
231
|
+
/>
|
|
232
|
+
</ExpandableCardGroup>
|
|
233
|
+
),
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export const GroupWithMixedContent: Story = {
|
|
237
|
+
render: () => (
|
|
238
|
+
<ExpandableCardGroup heading="Account Overview">
|
|
239
|
+
<ExpandableCard
|
|
240
|
+
heading="Bills"
|
|
241
|
+
helperText="View your recent bills"
|
|
242
|
+
leadingContent={<ExpandableCardIcon as={BillMediumIcon} />}
|
|
243
|
+
expandedContent={
|
|
244
|
+
<>
|
|
245
|
+
<BodyText>• March 2025: £89.50</BodyText>
|
|
246
|
+
<BodyText>• February 2025: £92.00</BodyText>
|
|
247
|
+
<BodyText>• January 2025: £95.30</BodyText>
|
|
248
|
+
</>
|
|
249
|
+
}
|
|
250
|
+
/>
|
|
251
|
+
<ExpandableCard
|
|
252
|
+
heading="Payments"
|
|
253
|
+
helperText="Payment history"
|
|
254
|
+
leadingContent={<ExpandableCardIcon as={PaymentMediumIcon} />}
|
|
255
|
+
numericValue="3"
|
|
256
|
+
expandedContent={
|
|
257
|
+
<>
|
|
258
|
+
<BodyText>• 05/03/25: £89.50 paid</BodyText>
|
|
259
|
+
<BodyText>• 05/02/25: £92.00 paid</BodyText>
|
|
260
|
+
<BodyText>• 05/01/25: £95.30 paid</BodyText>
|
|
261
|
+
</>
|
|
262
|
+
}
|
|
263
|
+
/>
|
|
264
|
+
<ExpandableCard
|
|
265
|
+
heading="Settings"
|
|
266
|
+
leadingContent={<ExpandableCardIcon as={SettingsMediumIcon} />}
|
|
267
|
+
badge={{ text: 'Updated', colorScheme: 'info', size: 'sm' }}
|
|
268
|
+
expandedContent={
|
|
269
|
+
<>
|
|
270
|
+
<BodyText>• Notification preferences</BodyText>
|
|
271
|
+
<BodyText>• Payment methods</BodyText>
|
|
272
|
+
<BodyText>• Account details</BodyText>
|
|
273
|
+
</>
|
|
274
|
+
}
|
|
275
|
+
/>
|
|
276
|
+
</ExpandableCardGroup>
|
|
277
|
+
),
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
export const ColorSchemes: Story = {
|
|
281
|
+
render: () => (
|
|
282
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, width: 350 }}>
|
|
283
|
+
<ExpandableCard
|
|
284
|
+
heading="Default Card"
|
|
285
|
+
expandedContent={<BodyText>Default color scheme</BodyText>}
|
|
286
|
+
/>
|
|
287
|
+
<ExpandableCard
|
|
288
|
+
heading="Subtle White Card"
|
|
289
|
+
colorScheme="neutralSubtle"
|
|
290
|
+
expandedContent={<BodyText>Subtle white color scheme</BodyText>}
|
|
291
|
+
/>
|
|
292
|
+
<ExpandableCard
|
|
293
|
+
heading="Strong Card"
|
|
294
|
+
colorScheme="neutralStrong"
|
|
295
|
+
expandedContent={<BodyText>Strong color scheme</BodyText>}
|
|
296
|
+
/>
|
|
297
|
+
</div>
|
|
298
|
+
),
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
export const AdvancedComposition: Story = {
|
|
302
|
+
render: () => {
|
|
303
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<ExpandableCard expanded={expanded} onExpandedChange={setExpanded}>
|
|
307
|
+
<ExpandableCardTrigger
|
|
308
|
+
onPress={() => setExpanded(!expanded)}
|
|
309
|
+
heading="Custom Account Details"
|
|
310
|
+
helperText="View your detailed account information"
|
|
311
|
+
leadingContent={
|
|
312
|
+
<IconContainer icon={BillMediumIcon} size="md" variant="emphasis" color="pig" />
|
|
313
|
+
}
|
|
314
|
+
numericValue="£123.45"
|
|
315
|
+
isExpanded={expanded}
|
|
316
|
+
/>
|
|
317
|
+
<ExpandableCardExpandedContent isExpanded={expanded}>
|
|
318
|
+
<BodyText>Account Number: 1234567890</BodyText>
|
|
319
|
+
<BodyText>Sort Code: 12-34-56</BodyText>
|
|
320
|
+
<BodyText>Balance: £123.45</BodyText>
|
|
321
|
+
<BodyText>Last Updated: 12/11/25</BodyText>
|
|
322
|
+
</ExpandableCardExpandedContent>
|
|
323
|
+
</ExpandableCard>
|
|
324
|
+
);
|
|
325
|
+
},
|
|
326
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Card } from '../Card';
|
|
3
|
+
import type ExpandableCardProps from './ExpandableCard.props';
|
|
4
|
+
import ExpandableCardExpandedContent from './ExpandableCardExpandedContent';
|
|
5
|
+
import ExpandableCardTrigger from './ExpandableCardTrigger';
|
|
6
|
+
|
|
7
|
+
const ExpandableCard = ({
|
|
8
|
+
expanded: controlledExpanded,
|
|
9
|
+
onExpandedChange,
|
|
10
|
+
heading,
|
|
11
|
+
helperText,
|
|
12
|
+
leadingIcon,
|
|
13
|
+
leadingContent,
|
|
14
|
+
badge,
|
|
15
|
+
badgePosition = 'bottom',
|
|
16
|
+
numericValue,
|
|
17
|
+
expandedContent,
|
|
18
|
+
duration = 200,
|
|
19
|
+
animateOpacity = true,
|
|
20
|
+
disabled = false,
|
|
21
|
+
testID = 'expandable-card',
|
|
22
|
+
children,
|
|
23
|
+
...cardProps
|
|
24
|
+
}: ExpandableCardProps) => {
|
|
25
|
+
const [internalExpanded, setInternalExpanded] = useState(false);
|
|
26
|
+
|
|
27
|
+
// Use controlled or uncontrolled state
|
|
28
|
+
const isExpanded = controlledExpanded !== undefined ? controlledExpanded : internalExpanded;
|
|
29
|
+
|
|
30
|
+
const handlePress = () => {
|
|
31
|
+
if (disabled) return;
|
|
32
|
+
|
|
33
|
+
const newExpanded = !isExpanded;
|
|
34
|
+
|
|
35
|
+
if (controlledExpanded === undefined) {
|
|
36
|
+
setInternalExpanded(newExpanded);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
onExpandedChange?.(newExpanded);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const renderDefaultContent = () => (
|
|
43
|
+
<>
|
|
44
|
+
<ExpandableCardTrigger
|
|
45
|
+
onPress={handlePress}
|
|
46
|
+
disabled={disabled}
|
|
47
|
+
heading={heading}
|
|
48
|
+
helperText={helperText}
|
|
49
|
+
leadingIcon={leadingIcon}
|
|
50
|
+
leadingContent={leadingContent}
|
|
51
|
+
badge={badge}
|
|
52
|
+
badgePosition={badgePosition}
|
|
53
|
+
numericValue={numericValue}
|
|
54
|
+
isExpanded={isExpanded}
|
|
55
|
+
testID={`${testID}-trigger`}
|
|
56
|
+
/>
|
|
57
|
+
<ExpandableCardExpandedContent
|
|
58
|
+
isExpanded={isExpanded}
|
|
59
|
+
duration={duration}
|
|
60
|
+
animateOpacity={animateOpacity}
|
|
61
|
+
>
|
|
62
|
+
{expandedContent}
|
|
63
|
+
</ExpandableCardExpandedContent>
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Card noPadding {...cardProps} testID={testID}>
|
|
69
|
+
{children || renderDefaultContent()}
|
|
70
|
+
</Card>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
ExpandableCard.displayName = 'ExpandableCard';
|
|
75
|
+
|
|
76
|
+
export default ExpandableCard;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { View, type ViewProps } from 'react-native';
|
|
2
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
3
|
+
|
|
4
|
+
const ExpandableCardContent = ({ children, ...props }: ViewProps) => {
|
|
5
|
+
return (
|
|
6
|
+
<View {...props} style={[styles.container, props.style]}>
|
|
7
|
+
{children}
|
|
8
|
+
</View>
|
|
9
|
+
);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
ExpandableCardContent.displayName = 'ExpandableCardContent';
|
|
13
|
+
|
|
14
|
+
const styles = StyleSheet.create(theme => ({
|
|
15
|
+
container: {
|
|
16
|
+
gap: theme.components.expandableCard.gapVertical,
|
|
17
|
+
flex: 1,
|
|
18
|
+
},
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
export default ExpandableCardContent;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { View } from 'react-native';
|
|
2
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
3
|
+
import { Divider } from '../Divider';
|
|
4
|
+
import { Expandable } from '../Expandable';
|
|
5
|
+
|
|
6
|
+
interface ExpandableCardExpandedContentProps {
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
isExpanded: boolean;
|
|
9
|
+
duration?: number;
|
|
10
|
+
animateOpacity?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const ExpandableCardExpandedContent = ({
|
|
14
|
+
children,
|
|
15
|
+
isExpanded,
|
|
16
|
+
duration = 200,
|
|
17
|
+
animateOpacity = true,
|
|
18
|
+
}: ExpandableCardExpandedContentProps) => {
|
|
19
|
+
return (
|
|
20
|
+
<View style={styles.container}>
|
|
21
|
+
<Expandable expanded={isExpanded} duration={duration} animateOpacity={animateOpacity}>
|
|
22
|
+
<View>
|
|
23
|
+
<Divider space="none" />
|
|
24
|
+
<View style={styles.content}>{children}</View>
|
|
25
|
+
</View>
|
|
26
|
+
</Expandable>
|
|
27
|
+
</View>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
ExpandableCardExpandedContent.displayName = 'ExpandableCardExpandedContent';
|
|
32
|
+
|
|
33
|
+
const styles = StyleSheet.create(theme => ({
|
|
34
|
+
container: {
|
|
35
|
+
width: '100%',
|
|
36
|
+
},
|
|
37
|
+
content: {
|
|
38
|
+
padding: theme.components.card.mobile.padding,
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
export default ExpandableCardExpandedContent;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ViewProps } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface ExpandableCardGroupProps extends ViewProps {
|
|
5
|
+
/**
|
|
6
|
+
* Section heading
|
|
7
|
+
*/
|
|
8
|
+
heading?: string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Helper text displayed below the heading
|
|
12
|
+
*/
|
|
13
|
+
helperText?: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Trailing content for the header (e.g., a link)
|
|
17
|
+
*/
|
|
18
|
+
headerTrailingContent?: ReactNode;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The ExpandableCard children
|
|
22
|
+
*/
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Test ID for testing
|
|
27
|
+
*/
|
|
28
|
+
testID?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default ExpandableCardGroupProps;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { View } from 'react-native';
|
|
2
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
3
|
+
import { SectionHeader } from '../SectionHeader';
|
|
4
|
+
import type ExpandableCardGroupProps from './ExpandableCardGroup.props';
|
|
5
|
+
|
|
6
|
+
const ExpandableCardGroup = ({
|
|
7
|
+
heading,
|
|
8
|
+
helperText,
|
|
9
|
+
headerTrailingContent,
|
|
10
|
+
children,
|
|
11
|
+
style,
|
|
12
|
+
testID = 'expandable-card-group',
|
|
13
|
+
...props
|
|
14
|
+
}: ExpandableCardGroupProps) => {
|
|
15
|
+
return (
|
|
16
|
+
<View style={[styles.container, style]} testID={testID} {...props}>
|
|
17
|
+
{heading ? (
|
|
18
|
+
<SectionHeader
|
|
19
|
+
heading={heading}
|
|
20
|
+
helperText={helperText}
|
|
21
|
+
trailingContent={headerTrailingContent}
|
|
22
|
+
/>
|
|
23
|
+
) : null}
|
|
24
|
+
<View style={styles.cardsContainer}>{children}</View>
|
|
25
|
+
</View>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
ExpandableCardGroup.displayName = 'ExpandableCardGroup';
|
|
30
|
+
|
|
31
|
+
const styles = StyleSheet.create(theme => ({
|
|
32
|
+
container: {
|
|
33
|
+
gap: theme.components.expandableCard.group.gap,
|
|
34
|
+
},
|
|
35
|
+
cardsContainer: {
|
|
36
|
+
gap: theme.components.expandableCard.group.gap,
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
export default ExpandableCardGroup;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { TextProps } from 'react-native';
|
|
2
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
3
|
+
import { BodyText } from '../BodyText';
|
|
4
|
+
|
|
5
|
+
const ExpandableCardHelperText = ({ children, ...props }: TextProps) => {
|
|
6
|
+
return (
|
|
7
|
+
<BodyText size="md" {...props} style={[styles.text, props.style]}>
|
|
8
|
+
{children}
|
|
9
|
+
</BodyText>
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
ExpandableCardHelperText.displayName = 'ExpandableCardHelperText';
|
|
14
|
+
|
|
15
|
+
const styles = StyleSheet.create(theme => ({
|
|
16
|
+
text: {
|
|
17
|
+
color: theme.color.text.secondary,
|
|
18
|
+
},
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
export default ExpandableCardHelperText;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
import { Platform, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
4
|
+
import { Icon, IconProps } from '../Icon';
|
|
5
|
+
|
|
6
|
+
const ExpandableCardIcon = ({ children, ...props }: IconProps & { as?: ComponentType }) => {
|
|
7
|
+
return (
|
|
8
|
+
<Icon
|
|
9
|
+
{...props}
|
|
10
|
+
style={
|
|
11
|
+
Platform.OS === 'web'
|
|
12
|
+
? // @ts-expect-error - style prop type issue
|
|
13
|
+
{ ...(styles.icon as StyleProp<ViewStyle>), ...props.style }
|
|
14
|
+
: ([styles.icon as StyleProp<ViewStyle>, props.style] as any)
|
|
15
|
+
}
|
|
16
|
+
>
|
|
17
|
+
{children}
|
|
18
|
+
</Icon>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
ExpandableCardIcon.displayName = 'ExpandableCardIcon';
|
|
23
|
+
|
|
24
|
+
const styles = StyleSheet.create(theme => ({
|
|
25
|
+
icon: {
|
|
26
|
+
color: theme.color.icon.primary,
|
|
27
|
+
width: 24,
|
|
28
|
+
height: 24,
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
export default ExpandableCardIcon;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { View, type ViewProps } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const ExpandableCardLeadingContent = ({ children, ...props }: ViewProps) => (
|
|
4
|
+
<View {...props}>{children}</View>
|
|
5
|
+
);
|
|
6
|
+
|
|
7
|
+
ExpandableCardLeadingContent.displayName = 'ExpandableCardLeadingContent';
|
|
8
|
+
|
|
9
|
+
export default ExpandableCardLeadingContent;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TextProps } from 'react-native';
|
|
2
|
+
import { BodyText } from '../BodyText';
|
|
3
|
+
|
|
4
|
+
const ExpandableCardText = ({ children, ...props }: TextProps) => {
|
|
5
|
+
return (
|
|
6
|
+
<BodyText size="lg" {...props}>
|
|
7
|
+
{children}
|
|
8
|
+
</BodyText>
|
|
9
|
+
);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
ExpandableCardText.displayName = 'ExpandableCardText';
|
|
13
|
+
|
|
14
|
+
export default ExpandableCardText;
|