@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.
Files changed (109) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/CHANGELOG.md +18 -0
  4. package/build/components/Banner/Banner.d.ts +6 -0
  5. package/build/components/Banner/Banner.js +161 -0
  6. package/build/components/Banner/Banner.props.d.ts +82 -0
  7. package/build/components/Banner/Banner.props.js +1 -0
  8. package/build/components/Banner/index.d.ts +2 -0
  9. package/build/components/Banner/index.js +1 -0
  10. package/build/components/BottomSheet/BottomSheetBackdrop.js +1 -5
  11. package/build/components/BottomSheet/BottomSheetFlatList.js +1 -5
  12. package/build/components/BottomSheet/BottomSheetHandle.js +1 -5
  13. package/build/components/BottomSheet/useBottomSheetLogic.d.ts +1 -1
  14. package/build/components/Expandable/Expandable.d.ts +6 -0
  15. package/build/components/Expandable/Expandable.js +44 -0
  16. package/build/components/Expandable/Expandable.props.d.ts +38 -0
  17. package/build/components/Expandable/Expandable.props.js +1 -0
  18. package/build/components/Expandable/index.d.ts +2 -0
  19. package/build/components/Expandable/index.js +1 -0
  20. package/build/components/ExpandableCard/ExpandableCard.d.ts +6 -0
  21. package/build/components/ExpandableCard/ExpandableCard.js +23 -0
  22. package/build/components/ExpandableCard/ExpandableCard.props.d.ts +69 -0
  23. package/build/components/ExpandableCard/ExpandableCard.props.js +1 -0
  24. package/build/components/ExpandableCard/ExpandableCardContent.d.ts +6 -0
  25. package/build/components/ExpandableCard/ExpandableCardContent.js +14 -0
  26. package/build/components/ExpandableCard/ExpandableCardExpandedContent.d.ts +11 -0
  27. package/build/components/ExpandableCard/ExpandableCardExpandedContent.js +18 -0
  28. package/build/components/ExpandableCard/ExpandableCardGroup.d.ts +6 -0
  29. package/build/components/ExpandableCard/ExpandableCardGroup.js +17 -0
  30. package/build/components/ExpandableCard/ExpandableCardGroup.props.d.ts +25 -0
  31. package/build/components/ExpandableCard/ExpandableCardGroup.props.js +1 -0
  32. package/build/components/ExpandableCard/ExpandableCardHelperText.d.ts +6 -0
  33. package/build/components/ExpandableCard/ExpandableCardHelperText.js +13 -0
  34. package/build/components/ExpandableCard/ExpandableCardIcon.d.ts +9 -0
  35. package/build/components/ExpandableCard/ExpandableCardIcon.js +19 -0
  36. package/build/components/ExpandableCard/ExpandableCardLeadingContent.d.ts +6 -0
  37. package/build/components/ExpandableCard/ExpandableCardLeadingContent.js +5 -0
  38. package/build/components/ExpandableCard/ExpandableCardText.d.ts +6 -0
  39. package/build/components/ExpandableCard/ExpandableCardText.js +7 -0
  40. package/build/components/ExpandableCard/ExpandableCardTrailingContent.d.ts +6 -0
  41. package/build/components/ExpandableCard/ExpandableCardTrailingContent.js +5 -0
  42. package/build/components/ExpandableCard/ExpandableCardTrailingIcon.d.ts +9 -0
  43. package/build/components/ExpandableCard/ExpandableCardTrailingIcon.js +17 -0
  44. package/build/components/ExpandableCard/ExpandableCardTrigger.d.ts +17 -0
  45. package/build/components/ExpandableCard/ExpandableCardTrigger.js +7 -0
  46. package/build/components/ExpandableCard/ExpandableCardTrigger.props.d.ts +44 -0
  47. package/build/components/ExpandableCard/ExpandableCardTrigger.props.js +1 -0
  48. package/build/components/ExpandableCard/ExpandableCardTriggerRoot.d.ts +11 -0
  49. package/build/components/ExpandableCard/ExpandableCardTriggerRoot.js +91 -0
  50. package/build/components/ExpandableCard/index.d.ts +14 -0
  51. package/build/components/ExpandableCard/index.js +11 -0
  52. package/build/components/HighlightBanner/HighlightBanner.d.ts +6 -0
  53. package/build/components/HighlightBanner/HighlightBanner.js +96 -0
  54. package/build/components/HighlightBanner/HighlightBanner.props.d.ts +14 -0
  55. package/build/components/HighlightBanner/HighlightBanner.props.js +1 -0
  56. package/build/components/HighlightBanner/index.d.ts +2 -0
  57. package/build/components/HighlightBanner/index.js +1 -0
  58. package/build/components/Spinner/Spinner.js +0 -2
  59. package/build/components/Spinner/Spinner.web.d.ts +2 -1
  60. package/build/components/Spinner/Spinner.web.js +0 -2
  61. package/build/components/Switch/Switch.web.js +0 -1
  62. package/build/components/Tabs/TabsList.js +1 -6
  63. package/build/components/index.d.ts +4 -0
  64. package/build/components/index.js +4 -0
  65. package/docs/components/AllComponents.web.tsx +75 -4
  66. package/docs/components/VariantTitle.tsx +1 -1
  67. package/package.json +4 -4
  68. package/src/components/Banner/Banner.docs.mdx +402 -0
  69. package/src/components/Banner/Banner.props.ts +106 -0
  70. package/src/components/Banner/Banner.stories.tsx +494 -0
  71. package/src/components/Banner/Banner.tsx +264 -0
  72. package/src/components/Banner/index.ts +2 -0
  73. package/src/components/BottomSheet/BottomSheetBackdrop.tsx +0 -1
  74. package/src/components/BottomSheet/BottomSheetFlatList.tsx +0 -1
  75. package/src/components/BottomSheet/BottomSheetHandle.tsx +0 -1
  76. package/src/components/Card/Card.docs.mdx +10 -2
  77. package/src/components/Expandable/Expandable.docs.mdx +201 -0
  78. package/src/components/Expandable/Expandable.props.ts +46 -0
  79. package/src/components/Expandable/Expandable.stories.tsx +284 -0
  80. package/src/components/Expandable/Expandable.tsx +92 -0
  81. package/src/components/Expandable/index.ts +2 -0
  82. package/src/components/ExpandableCard/ExpandableCard.docs.mdx +312 -0
  83. package/src/components/ExpandableCard/ExpandableCard.props.ts +85 -0
  84. package/src/components/ExpandableCard/ExpandableCard.stories.tsx +326 -0
  85. package/src/components/ExpandableCard/ExpandableCard.tsx +76 -0
  86. package/src/components/ExpandableCard/ExpandableCardContent.tsx +21 -0
  87. package/src/components/ExpandableCard/ExpandableCardExpandedContent.tsx +42 -0
  88. package/src/components/ExpandableCard/ExpandableCardGroup.props.ts +31 -0
  89. package/src/components/ExpandableCard/ExpandableCardGroup.tsx +40 -0
  90. package/src/components/ExpandableCard/ExpandableCardHelperText.tsx +21 -0
  91. package/src/components/ExpandableCard/ExpandableCardIcon.tsx +32 -0
  92. package/src/components/ExpandableCard/ExpandableCardLeadingContent.tsx +9 -0
  93. package/src/components/ExpandableCard/ExpandableCardText.tsx +14 -0
  94. package/src/components/ExpandableCard/ExpandableCardTrailingContent.tsx +9 -0
  95. package/src/components/ExpandableCard/ExpandableCardTrailingIcon.tsx +30 -0
  96. package/src/components/ExpandableCard/ExpandableCardTrigger.props.ts +47 -0
  97. package/src/components/ExpandableCard/ExpandableCardTrigger.tsx +10 -0
  98. package/src/components/ExpandableCard/ExpandableCardTriggerRoot.tsx +154 -0
  99. package/src/components/ExpandableCard/index.ts +14 -0
  100. package/src/components/HighlightBanner/HighlightBanner.docs.mdx +296 -0
  101. package/src/components/HighlightBanner/HighlightBanner.props.ts +29 -0
  102. package/src/components/HighlightBanner/HighlightBanner.stories.tsx +275 -0
  103. package/src/components/HighlightBanner/HighlightBanner.tsx +134 -0
  104. package/src/components/HighlightBanner/index.ts +2 -0
  105. package/src/components/Spinner/Spinner.tsx +0 -2
  106. package/src/components/Spinner/Spinner.web.tsx +0 -2
  107. package/src/components/Switch/Switch.web.tsx +1 -5
  108. package/src/components/Tabs/TabsList.tsx +0 -2
  109. package/src/components/index.ts +4 -0
@@ -0,0 +1,284 @@
1
+ import { Meta, StoryObj } from '@storybook/react-native';
2
+ import {
3
+ ChevronDownMediumIcon,
4
+ ChevronUpMediumIcon,
5
+ } from '@utilitywarehouse/hearth-react-native-icons';
6
+ import { useState } from 'react';
7
+ import { View } from 'react-native';
8
+ import { BodyText, Button, Card } from '../../components';
9
+ import Expandable from './Expandable';
10
+
11
+ const meta = {
12
+ title: 'Stories / Expandable',
13
+ component: Expandable,
14
+ parameters: {
15
+ // layout: 'centered',
16
+ },
17
+ argTypes: {
18
+ expanded: {
19
+ control: 'boolean',
20
+ description: 'Whether the content is expanded',
21
+ },
22
+ duration: {
23
+ control: { type: 'number', min: 100, max: 1000, step: 50 },
24
+ description: 'Animation duration in milliseconds',
25
+ },
26
+ accessibilityLabel: {
27
+ control: 'text',
28
+ description: 'Accessibility label for screen readers',
29
+ },
30
+ },
31
+ args: {
32
+ expanded: false,
33
+ duration: 300,
34
+ },
35
+ } satisfies Meta<typeof Expandable>;
36
+
37
+ export default meta;
38
+ type Story = StoryObj<typeof meta>;
39
+
40
+ export const Playground: Story = {
41
+ render: (args: typeof meta.args) => {
42
+ const [expanded, setExpanded] = useState(args.expanded ?? false);
43
+
44
+ return (
45
+ <>
46
+ <Button onPress={() => setExpanded(!expanded)} style={{ marginBottom: 16 }}>
47
+ {expanded ? 'Collapse' : 'Expand'}
48
+ </Button>
49
+ <Expandable {...args} expanded={expanded}>
50
+ <Card>
51
+ <BodyText>
52
+ This is expandable content. It animates smoothly when toggled. You can put any content
53
+ here, and it will expand and collapse with animation.
54
+ </BodyText>
55
+ </Card>
56
+ </Expandable>
57
+ </>
58
+ );
59
+ },
60
+ };
61
+
62
+ export const BasicExample: Story = {
63
+ render: () => {
64
+ const [expanded, setExpanded] = useState(false);
65
+
66
+ return (
67
+ <>
68
+ <Button onPress={() => setExpanded(!expanded)} style={{ marginBottom: 16 }}>
69
+ Toggle Content
70
+ </Button>
71
+ <Expandable expanded={expanded}>
72
+ <Card>
73
+ <BodyText>This content expands and collapses with a smooth animation.</BodyText>
74
+ </Card>
75
+ </Expandable>
76
+ </>
77
+ );
78
+ },
79
+ };
80
+
81
+ export const FastAnimation: Story = {
82
+ render: () => {
83
+ const [expanded, setExpanded] = useState(false);
84
+
85
+ return (
86
+ <>
87
+ <Button onPress={() => setExpanded(!expanded)} style={{ marginBottom: 16 }}>
88
+ Fast Toggle
89
+ </Button>
90
+ <Expandable expanded={expanded} duration={150}>
91
+ <Card>
92
+ <BodyText>This expands and collapses quickly with a 150ms duration.</BodyText>
93
+ </Card>
94
+ </Expandable>
95
+ </>
96
+ );
97
+ },
98
+ };
99
+
100
+ export const SlowAnimation: Story = {
101
+ render: () => {
102
+ const [expanded, setExpanded] = useState(false);
103
+
104
+ return (
105
+ <>
106
+ <Button onPress={() => setExpanded(!expanded)} style={{ marginBottom: 16 }}>
107
+ Slow Toggle
108
+ </Button>
109
+ <Expandable expanded={expanded} duration={600}>
110
+ <Card>
111
+ <BodyText>This expands and collapses slowly with a 600ms duration.</BodyText>
112
+ </Card>
113
+ </Expandable>
114
+ </>
115
+ );
116
+ },
117
+ };
118
+
119
+ export const LongContent: Story = {
120
+ render: () => {
121
+ const [expanded, setExpanded] = useState(false);
122
+
123
+ return (
124
+ <>
125
+ <Button onPress={() => setExpanded(!expanded)} style={{ marginBottom: 16 }}>
126
+ {expanded ? 'Hide Details' : 'Show Details'}
127
+ </Button>
128
+ <Expandable expanded={expanded}>
129
+ <Card>
130
+ <BodyText>
131
+ This is a longer piece of content that demonstrates how the Expandable component
132
+ handles larger amounts of text. The animation smoothly transitions regardless of
133
+ content length.
134
+ </BodyText>
135
+ <BodyText style={{ marginTop: 12 }}>
136
+ You can include multiple paragraphs, and the component will measure the full height
137
+ and animate accordingly.
138
+ </BodyText>
139
+ <BodyText style={{ marginTop: 12 }}>
140
+ The animation uses Reanimated for optimal performance and smooth transitions.
141
+ </BodyText>
142
+ </Card>
143
+ </Expandable>
144
+ </>
145
+ );
146
+ },
147
+ };
148
+
149
+ export const MultipleExpandables: Story = {
150
+ render: () => {
151
+ const [expanded1, setExpanded1] = useState(false);
152
+ const [expanded2, setExpanded2] = useState(false);
153
+ const [expanded3, setExpanded3] = useState(false);
154
+
155
+ return (
156
+ <View style={{ width: 300, gap: 16 }}>
157
+ <View>
158
+ <Button
159
+ onPress={() => setExpanded1(!expanded1)}
160
+ style={{ marginBottom: 8 }}
161
+ iconPosition="right"
162
+ icon={expanded1 ? ChevronUpMediumIcon : ChevronDownMediumIcon}
163
+ >
164
+ Section 1
165
+ </Button>
166
+ <Expandable expanded={expanded1}>
167
+ <Card>
168
+ <BodyText>Content for section 1</BodyText>
169
+ </Card>
170
+ </Expandable>
171
+ </View>
172
+
173
+ <View>
174
+ <Button
175
+ onPress={() => setExpanded2(!expanded2)}
176
+ style={{ marginBottom: 8 }}
177
+ iconPosition="right"
178
+ icon={expanded2 ? ChevronUpMediumIcon : ChevronDownMediumIcon}
179
+ >
180
+ Section 2
181
+ </Button>
182
+ <Expandable expanded={expanded2}>
183
+ <Card>
184
+ <BodyText>Content for section 2 with more detailed information.</BodyText>
185
+ </Card>
186
+ </Expandable>
187
+ </View>
188
+
189
+ <View>
190
+ <Button
191
+ onPress={() => setExpanded3(!expanded3)}
192
+ style={{ marginBottom: 8 }}
193
+ iconPosition="right"
194
+ icon={expanded3 ? ChevronUpMediumIcon : ChevronDownMediumIcon}
195
+ >
196
+ Section 3
197
+ </Button>
198
+ <Expandable expanded={expanded3}>
199
+ <Card>
200
+ <BodyText>
201
+ Content for section 3 with even more information that spans multiple lines.
202
+ </BodyText>
203
+ </Card>
204
+ </Expandable>
205
+ </View>
206
+ </View>
207
+ );
208
+ },
209
+ };
210
+
211
+ export const ControlledExample: Story = {
212
+ render: () => {
213
+ const [expanded, setExpanded] = useState(false);
214
+
215
+ return (
216
+ <View style={{ width: 300 }}>
217
+ <View style={{ flexDirection: 'row', gap: 8, marginBottom: 16 }}>
218
+ <Button onPress={() => setExpanded(true)} style={{ flex: 1 }}>
219
+ Expand
220
+ </Button>
221
+ <Button onPress={() => setExpanded(false)} style={{ flex: 1 }}>
222
+ Collapse
223
+ </Button>
224
+ </View>
225
+ <Expandable
226
+ expanded={expanded}
227
+ onExpandedChange={setExpanded}
228
+ accessibilityLabel="Expandable content section"
229
+ >
230
+ <Card>
231
+ <BodyText>
232
+ This is a controlled expandable with separate expand and collapse buttons.
233
+ </BodyText>
234
+ </Card>
235
+ </Expandable>
236
+ </View>
237
+ );
238
+ },
239
+ };
240
+
241
+ export const DefaultExpanded: Story = {
242
+ render: () => {
243
+ const [expanded, setExpanded] = useState(true);
244
+
245
+ return (
246
+ <View style={{ width: 300 }}>
247
+ <Button onPress={() => setExpanded(!expanded)} style={{ marginBottom: 16 }}>
248
+ Toggle
249
+ </Button>
250
+ <Expandable expanded={expanded}>
251
+ <Card>
252
+ <BodyText>This content starts expanded by default.</BodyText>
253
+ </Card>
254
+ </Expandable>
255
+ </View>
256
+ );
257
+ },
258
+ };
259
+
260
+ export const WithCallback: Story = {
261
+ render: () => {
262
+ const [expanded, setExpanded] = useState(false);
263
+ const [message, setMessage] = useState('Content is collapsed');
264
+
265
+ const handleExpandedChange = (newExpanded: boolean) => {
266
+ console.log(newExpanded);
267
+ setMessage(newExpanded ? 'Content is expanded' : 'Content is collapsed');
268
+ };
269
+
270
+ return (
271
+ <View style={{ width: 300 }}>
272
+ <BodyText style={{ marginBottom: 8 }}>{message}</BodyText>
273
+ <Button onPress={() => setExpanded(!expanded)} style={{ marginBottom: 16 }}>
274
+ Toggle
275
+ </Button>
276
+ <Expandable expanded={expanded} onExpandedChange={handleExpandedChange}>
277
+ <Card>
278
+ <BodyText>This example demonstrates the onExpandedChange callback.</BodyText>
279
+ </Card>
280
+ </Expandable>
281
+ </View>
282
+ );
283
+ },
284
+ };
@@ -0,0 +1,92 @@
1
+ import { useEffect } from 'react';
2
+ import { View } from 'react-native';
3
+ import Animated, {
4
+ useAnimatedStyle,
5
+ useDerivedValue,
6
+ useSharedValue,
7
+ withTiming,
8
+ } from 'react-native-reanimated';
9
+ import { StyleSheet } from 'react-native-unistyles';
10
+ import { ExpandableProps } from './Expandable.props';
11
+
12
+ const Expandable = ({
13
+ expanded = false,
14
+ onExpandedChange,
15
+ children,
16
+ duration = 200,
17
+ style,
18
+ accessibilityLabel,
19
+ testID,
20
+ animateOpacity = true,
21
+ ...props
22
+ }: ExpandableProps) => {
23
+ const height = useSharedValue(0);
24
+ const open = useSharedValue(expanded);
25
+
26
+ // Update open value when expanded prop changes and call callback
27
+ useEffect(() => {
28
+ if (open.value !== expanded) {
29
+ open.value = expanded;
30
+ onExpandedChange?.(expanded);
31
+ }
32
+ }, [expanded, onExpandedChange, open]);
33
+
34
+ const derivedHeight = useDerivedValue(() =>
35
+ withTiming(height.value * Number(open.value), {
36
+ duration,
37
+ })
38
+ );
39
+
40
+ const derivedOpacity = useDerivedValue(() =>
41
+ animateOpacity
42
+ ? withTiming(Number(open.value), {
43
+ duration,
44
+ })
45
+ : 1
46
+ );
47
+
48
+ const heightStyle = useAnimatedStyle(() => ({
49
+ height: derivedHeight.value,
50
+ }));
51
+
52
+ const opacityStyle = useAnimatedStyle(() => ({
53
+ opacity: derivedOpacity.value,
54
+ }));
55
+
56
+ return (
57
+ <Animated.View
58
+ style={[styles.container, heightStyle, style]}
59
+ accessible={true}
60
+ accessibilityLabel={accessibilityLabel}
61
+ accessibilityRole="none"
62
+ accessibilityState={{ expanded }}
63
+ testID={testID}
64
+ {...props}
65
+ >
66
+ <Animated.View style={opacityStyle}>
67
+ <View
68
+ onLayout={e => {
69
+ height.value = e.nativeEvent.layout.height;
70
+ }}
71
+ style={styles.content}
72
+ >
73
+ {children}
74
+ </View>
75
+ </Animated.View>
76
+ </Animated.View>
77
+ );
78
+ };
79
+
80
+ Expandable.displayName = 'Expandable';
81
+
82
+ const styles = StyleSheet.create(() => ({
83
+ container: {
84
+ overflow: 'hidden',
85
+ },
86
+ content: {
87
+ position: 'absolute',
88
+ width: '100%',
89
+ },
90
+ }));
91
+
92
+ export default Expandable;
@@ -0,0 +1,2 @@
1
+ export { default as Expandable } from './Expandable';
2
+ export type { ExpandableProps } from './Expandable.props';
@@ -0,0 +1,312 @@
1
+ import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks';
2
+ import { BackToTopButton, ViewFigmaButton } from '../../../docs/components';
3
+ import * as Stories from './ExpandableCard.stories';
4
+
5
+ <Meta title="Components / Expandable Card" />
6
+
7
+ <BackToTopButton />
8
+
9
+ <ViewFigmaButton link="https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=7222-5934" />
10
+
11
+ # Expandable Card
12
+
13
+ The `ExpandableCard` component is an interactive card that expands to reveal additional content when clicked. It's a Card component with smooth expansion animations, making it perfect for displaying collapsible information sections.
14
+
15
+ - [Playground](#playground)
16
+ - [Usage](#usage)
17
+ - [Props](#props)
18
+ - [`ExpandableCard`](#expandablecard)
19
+ - [`ExpandableCardGroup`](#expandablecardgroup)
20
+ - [Examples](#examples)
21
+ - [Basic Example](#basic-example)
22
+ - [With Leading Icon](#with-leading-icon)
23
+ - [With `Badge`](#with-badge)
24
+ - [With Numeric Value](#with-numeric-value)
25
+ - [`CardGroup`](#cardgroup)
26
+ - [Advanced Usage](#advanced-usage)
27
+ - [Accessibility](#accessibility)
28
+
29
+ ## Playground
30
+
31
+ <Canvas of={Stories.Playground} />
32
+
33
+ <Controls of={Stories.Playground} />
34
+
35
+ ## Usage
36
+
37
+ <Canvas of={Stories.BasicExample} />
38
+
39
+ ```tsx
40
+ import { ExpandableCard, BodyText } from '@utilitywarehouse/hearth-react-native';
41
+
42
+ const MyComponent = () => (
43
+ <ExpandableCard
44
+ heading="Order Details"
45
+ helperText="View your order information"
46
+ expandedContent={
47
+ <>
48
+ <BodyText>Order #12345</BodyText>
49
+ <BodyText>Status: Delivered</BodyText>
50
+ <BodyText>Date: 10 Nov 2025</BodyText>
51
+ </>
52
+ }
53
+ />
54
+ );
55
+ ```
56
+
57
+ ## Props
58
+
59
+ ### `ExpandableCard`
60
+
61
+ | Prop | Type | Default | Description |
62
+ | ------------------ | ----------------------------- | ------------------- | ------------------------------------------------ |
63
+ | `heading` | `string` | - | The heading text displayed in the trigger |
64
+ | `helperText` | `string` | - | Optional helper text displayed below the heading |
65
+ | `leadingIcon` | `ComponentType` | - | Leading icon component (automatically wrapped) |
66
+ | `leadingContent` | `ReactNode` | - | Leading content (icon or custom element) |
67
+ | `badge` | `BadgeProps` | - | Badge to display |
68
+ | `badgePosition` | `'top' \| 'bottom'` | `'bottom'` | Badge position |
69
+ | `numericValue` | `string \| number` | - | Numeric value to display on the right |
70
+ | `expandedContent` | `ReactNode` | - | The content to show when expanded |
71
+ | `expanded` | `boolean` | - | Whether the card is expanded (controlled) |
72
+ | `onExpandedChange` | `(expanded: boolean) => void` | - | Callback when expanded state changes |
73
+ | `duration` | `number` | `200` | Duration of expansion animation in milliseconds |
74
+ | `animateOpacity` | `boolean` | `true` | Whether to animate opacity during expansion |
75
+ | `disabled` | `boolean` | `false` | Whether the card is disabled |
76
+ | `colorScheme` | `CardColorScheme` | - | Color scheme (inherits from Card component) |
77
+ | `variant` | `CardVariant` | - | Variant (inherits from Card component) |
78
+ | `testID` | `string` | `'expandable-card'` | Test ID for testing |
79
+
80
+ ### `ExpandableCardGroup`
81
+
82
+ | Prop | Type | Default | Description |
83
+ | ----------------------- | ----------- | ------------------------- | ---------------------------------------------- |
84
+ | `heading` | `string` | - | Section heading |
85
+ | `helperText` | `string` | - | Helper text displayed below the heading |
86
+ | `headerTrailingContent` | `ReactNode` | - | Trailing content for the header (e.g., a link) |
87
+ | `children` | `ReactNode` | - | The ExpandableCard children |
88
+ | `testID` | `string` | `'expandable-card-group'` | Test ID for testing |
89
+
90
+ ## Examples
91
+
92
+ ### Basic Example
93
+
94
+ <Canvas of={Stories.BasicExample} />
95
+
96
+ ### With Leading Icon
97
+
98
+ You can add a leading icon using the `leadingIcon` prop for simple icons, or use `leadingContent` with an `IconContainer` for more customization.
99
+
100
+ <Canvas of={Stories.WithLeadingIcon} />
101
+
102
+ ```tsx
103
+ import { ExpandableCard, BodyText } from '@utilitywarehouse/hearth-react-native';
104
+ import { SettingsMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
105
+
106
+ const MyComponent = () => (
107
+ <ExpandableCard
108
+ heading="Settings"
109
+ helperText="Configure your preferences"
110
+ leadingIcon={SettingsMediumIcon}
111
+ expandedContent={
112
+ <>
113
+ <BodyText>• Notifications</BodyText>
114
+ <BodyText>• Privacy</BodyText>
115
+ <BodyText>• Account</BodyText>
116
+ </>
117
+ }
118
+ />
119
+ );
120
+ ```
121
+
122
+ Or use `leadingContent` with `IconContainer` for emphasis variants:
123
+
124
+ <Canvas of={Stories.WithIconContainer} />
125
+
126
+ ```tsx
127
+ import { ExpandableCard, IconContainer, BodyText } from '@utilitywarehouse/hearth-react-native';
128
+ import { ElectricityMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
129
+
130
+ const MyComponent = () => (
131
+ <ExpandableCard
132
+ heading="Electricity"
133
+ helperText="Last reading 23/03/24"
134
+ leadingContent={
135
+ <IconContainer icon={ElectricityMediumIcon} size="md" variant="emphasis" color="energy" />
136
+ }
137
+ expandedContent={
138
+ <>
139
+ <BodyText>Current Usage: 245 kWh</BodyText>
140
+ <BodyText>Estimated Cost: £45.50</BodyText>
141
+ </>
142
+ }
143
+ />
144
+ );
145
+ ```
146
+
147
+ ### With `Badge`
148
+
149
+ <Canvas of={Stories.WithBadge} />
150
+
151
+ ```tsx
152
+ <ExpandableCard
153
+ heading="New Feature"
154
+ helperText="Check out what's new"
155
+ badge={{ text: 'New', colorScheme: 'cyan' }}
156
+ expandedContent={<BodyText>We've added new features to improve your experience.</BodyText>}
157
+ />
158
+ ```
159
+
160
+ ### With Numeric Value
161
+
162
+ <Canvas of={Stories.WithNumericValue} />
163
+
164
+ ```tsx
165
+ <ExpandableCard
166
+ heading="Total Balance"
167
+ helperText="Current account balance"
168
+ numericValue="£123.45"
169
+ expandedContent={
170
+ <>
171
+ <BodyText>Available: £100.00</BodyText>
172
+ <BodyText>Pending: £23.45</BodyText>
173
+ </>
174
+ }
175
+ />
176
+ ```
177
+
178
+ ### `CardGroup`
179
+
180
+ Use `ExpandableCardGroup` to group multiple expandable cards with an optional header.
181
+
182
+ <Canvas of={Stories.CardGroup} />
183
+
184
+ ```tsx
185
+ import {
186
+ ExpandableCard,
187
+ ExpandableCardGroup,
188
+ IconContainer,
189
+ Link,
190
+ BodyText,
191
+ } from '@utilitywarehouse/hearth-react-native';
192
+ import { ElectricityMediumIcon, GasMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
193
+
194
+ const MyComponent = () => (
195
+ <ExpandableCardGroup
196
+ heading="Your Services"
197
+ helperText="View details for each service"
198
+ headerTrailingContent={<Link href="#">View all</Link>}
199
+ >
200
+ <ExpandableCard
201
+ heading="Electricity"
202
+ helperText="Last reading 23/03/24"
203
+ leadingContent={
204
+ <IconContainer icon={ElectricityMediumIcon} size="md" variant="emphasis" color="energy" />
205
+ }
206
+ expandedContent={
207
+ <>
208
+ <BodyText>Current Usage: 245 kWh</BodyText>
209
+ <BodyText>Estimated Cost: £45.50</BodyText>
210
+ </>
211
+ }
212
+ />
213
+ <ExpandableCard
214
+ heading="Gas"
215
+ helperText="Last reading 23/03/24"
216
+ leadingContent={
217
+ <IconContainer icon={GasMediumIcon} size="md" variant="emphasis" color="energy" />
218
+ }
219
+ expandedContent={
220
+ <>
221
+ <BodyText>Current Usage: 180 kWh</BodyText>
222
+ <BodyText>Estimated Cost: £32.00</BodyText>
223
+ </>
224
+ }
225
+ />
226
+ </ExpandableCardGroup>
227
+ );
228
+ ```
229
+
230
+ ## Advanced Usage
231
+
232
+ If you need to use the `ExpandableCard` component in a more advanced way, you can use the child components directly for full composition control.
233
+
234
+ <Canvas of={Stories.AdvancedComposition} />
235
+
236
+ ```tsx
237
+ import {
238
+ ExpandableCard,
239
+ ExpandableCardTrigger,
240
+ ExpandableCardExpandedContent,
241
+ ExpandableCardContent,
242
+ IconContainer,
243
+ BodyText,
244
+ } from '@utilitywarehouse/hearth-react-native';
245
+ import { BillMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
246
+ import { useState } from 'react';
247
+
248
+ const MyComponent = () => {
249
+ const [expanded, setExpanded] = useState(false);
250
+
251
+ return (
252
+ <ExpandableCard expanded={expanded} onExpandedChange={setExpanded}>
253
+ <ExpandableCardTrigger
254
+ onPress={() => setExpanded(!expanded)}
255
+ heading="Custom Account Details"
256
+ helperText="View your detailed account information"
257
+ leadingContent={
258
+ <IconContainer icon={BillMediumIcon} size="md" variant="emphasis" color="pig" />
259
+ }
260
+ numericValue="£123.45"
261
+ isExpanded={expanded}
262
+ />
263
+ <ExpandableCardExpandedContent isExpanded={expanded}>
264
+ <BodyText>Account Number: 1234567890</BodyText>
265
+ <BodyText>Sort Code: 12-34-56</BodyText>
266
+ <BodyText>Balance: £123.45</BodyText>
267
+ <BodyText>Last Updated: 12/11/25</BodyText>
268
+ </ExpandableCardExpandedContent>
269
+ </ExpandableCard>
270
+ );
271
+ };
272
+ ```
273
+
274
+ ### Child Components
275
+
276
+ When using advanced composition, you have access to these components:
277
+
278
+ - **`ExpandableCardTrigger`**: The clickable header section with heading, helper text, leading/trailing content
279
+ - **`ExpandableCardExpandedContent`**: Wrapper for the expanded content area with animation controls
280
+ - **`ExpandableCardContent`**: Content container with proper spacing and gap
281
+ - **`ExpandableCardIcon`**: Styled icon component for leading icons
282
+ - **`ExpandableCardText`**: Styled text for main heading
283
+ - **`ExpandableCardHelperText`**: Styled text for helper/secondary text
284
+ - **`ExpandableCardLeadingContent`**: Container for leading content (icons, images, etc.)
285
+ - **`ExpandableCardTrailingContent`**: Container for trailing content
286
+ - **`ExpandableCardTrailingIcon`**: Styled icon for trailing position
287
+
288
+ ## Accessibility
289
+
290
+ The ExpandableCard component includes built-in accessibility support:
291
+
292
+ - Uses `accessibilityRole="button"` to indicate it's interactive
293
+ - Sets `accessibilityState` to communicate expanded/collapsed and disabled states
294
+ - Provides meaningful `accessibilityLabel` combining heading and helper text
295
+ - Chevron icon changes direction to indicate current state
296
+ - Keyboard accessible when used on web
297
+
298
+ ## Best Practices
299
+
300
+ 1. **Use descriptive headings**: Make sure your heading clearly describes what content will be revealed.
301
+
302
+ 2. **Keep expanded content concise**: While you can put any content inside, try to keep it focused and scannable.
303
+
304
+ 3. **Group related cards**: Use `ExpandableCardGroup` to organize multiple related expandable cards with a common header.
305
+
306
+ 4. **Consider initial state**: Most cards should start collapsed, but you can default to expanded for critical information.
307
+
308
+ 5. **Provide visual context**: Use leading icons or icon containers to help users quickly identify the card's purpose.
309
+
310
+ 6. **Use numeric values appropriately**: Display counts, amounts, or other quantitative data that adds quick context.
311
+
312
+ 7. **Controlled vs Uncontrolled**: Use controlled state (via `expanded` and `onExpandedChange`) when you need to manage expansion state externally, otherwise let the component handle it internally.