@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,264 @@
1
+ import { ChevronRightSmallIcon, CloseSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
2
+ import { Pressable, View } from 'react-native';
3
+ import { StyleSheet } from 'react-native-unistyles';
4
+ import { BodyText } from '../BodyText';
5
+ import { Card } from '../Card';
6
+ import { Heading } from '../Heading';
7
+ import { IconContainer } from '../IconContainer';
8
+ import { ThemedImage } from '../ThemedImage';
9
+ import { UnstyledIconButton } from '../UnstyledIconButton';
10
+ import type BannerProps from './Banner.props';
11
+
12
+ const Banner = ({
13
+ icon,
14
+ iconContainerVariant = 'subtle',
15
+ iconContainerSize = 'md',
16
+ iconContainerColor = 'pig',
17
+ illustration,
18
+ image,
19
+ heading,
20
+ description,
21
+ direction = 'horizontal',
22
+ link,
23
+ button,
24
+ onPress,
25
+ onClose,
26
+ variant = 'subtle',
27
+ colorScheme = 'pig',
28
+ style,
29
+ ...props
30
+ }: BannerProps) => {
31
+ const hasIllustration = Boolean(illustration);
32
+ styles.useVariants({ direction, hasIllustration });
33
+
34
+ const renderIconOrImage = () => {
35
+ if (icon) {
36
+ return (
37
+ <IconContainer
38
+ icon={icon}
39
+ variant={iconContainerVariant}
40
+ size={iconContainerSize}
41
+ color={iconContainerColor}
42
+ style={styles.media}
43
+ />
44
+ );
45
+ }
46
+ if (illustration) {
47
+ return (
48
+ <ThemedImage
49
+ light={illustration.light}
50
+ dark={illustration.dark}
51
+ style={styles.image}
52
+ accessible
53
+ accessibilityLabel={heading}
54
+ />
55
+ );
56
+ }
57
+ if (image) {
58
+ return (
59
+ <View style={[styles.media, styles.imageWrapper]}>
60
+ <ThemedImage
61
+ light={image.light}
62
+ dark={image.dark}
63
+ style={styles.image}
64
+ accessible
65
+ accessibilityLabel={heading}
66
+ />
67
+ </View>
68
+ );
69
+ }
70
+ return null;
71
+ };
72
+
73
+ const renderAction = () => {
74
+ if (link) {
75
+ return <View style={styles.action}>{link}</View>;
76
+ }
77
+ if (button) {
78
+ return <View style={styles.action}>{button}</View>;
79
+ }
80
+ return null;
81
+ };
82
+
83
+ const content = (
84
+ <View style={styles.container}>
85
+ {renderIconOrImage()}
86
+ <View style={styles.contentContainer}>
87
+ <View style={styles.textContainer}>
88
+ <Heading
89
+ size="sm"
90
+ style={styles.heading}
91
+ textAlign={hasIllustration && direction === 'vertical' ? 'center' : 'left'}
92
+ >
93
+ {heading}
94
+ </Heading>
95
+ <BodyText
96
+ size="md"
97
+ style={styles.description}
98
+ textAlign={hasIllustration && direction === 'vertical' ? 'center' : 'left'}
99
+ >
100
+ {description}
101
+ </BodyText>
102
+ {renderAction()}
103
+ </View>
104
+ {onPress && (
105
+ <UnstyledIconButton
106
+ icon={ChevronRightSmallIcon}
107
+ size="sm"
108
+ onPress={onPress}
109
+ style={styles.chevron}
110
+ />
111
+ )}
112
+ {onClose && (
113
+ <UnstyledIconButton
114
+ icon={CloseSmallIcon}
115
+ size="sm"
116
+ onPress={onClose}
117
+ style={styles.closeButton}
118
+ accessibilityLabel="Close banner"
119
+ />
120
+ )}
121
+ </View>
122
+ </View>
123
+ );
124
+
125
+ if (onPress) {
126
+ return (
127
+ <Card variant={variant} style={[styles.card, style]} {...props}>
128
+ <Pressable onPress={onPress} accessibilityRole="button" style={styles.pressable}>
129
+ {content}
130
+ </Pressable>
131
+ </Card>
132
+ );
133
+ }
134
+
135
+ return (
136
+ <Card variant={variant} style={[styles.card, style]} {...props}>
137
+ {content}
138
+ </Card>
139
+ );
140
+ };
141
+
142
+ Banner.displayName = 'Banner';
143
+
144
+ const styles = StyleSheet.create(theme => ({
145
+ card: {},
146
+ pressable: {
147
+ width: '100%',
148
+ },
149
+ container: {
150
+ flexDirection: 'row',
151
+ gap: theme.space.lg,
152
+ variants: {
153
+ direction: {
154
+ horizontal: {
155
+ flexDirection: 'row',
156
+ alignItems: 'flex-start',
157
+ },
158
+ vertical: {
159
+ flexDirection: 'column',
160
+ alignItems: 'stretch',
161
+ },
162
+ },
163
+ hasIllustration: {
164
+ true: {},
165
+ false: {},
166
+ },
167
+ },
168
+ compoundVariants: [
169
+ {
170
+ direction: 'vertical',
171
+ hasIllustration: false,
172
+ styles: {
173
+ alignItems: 'flex-start',
174
+ },
175
+ },
176
+ {
177
+ direction: 'vertical',
178
+ hasIllustration: true,
179
+ styles: {
180
+ alignItems: 'center',
181
+ },
182
+ },
183
+ ],
184
+ },
185
+ media: {
186
+ flexShrink: 0,
187
+ variants: {
188
+ direction: {
189
+ horizontal: {},
190
+ vertical: {
191
+ alignSelf: 'flex-start',
192
+ },
193
+ },
194
+ },
195
+ },
196
+ imageWrapper: {
197
+ variants: {
198
+ direction: {
199
+ horizontal: {},
200
+ vertical: {
201
+ width: '100%',
202
+ },
203
+ },
204
+ },
205
+ },
206
+ image: {
207
+ borderRadius: theme.borderRadius.md,
208
+ borderWidth: theme.borderWidth[1],
209
+ borderColor: theme.color.border.strong,
210
+ variants: {
211
+ direction: {
212
+ horizontal: { width: 160, height: 95 },
213
+ vertical: {
214
+ width: '100%',
215
+ height: 160,
216
+ },
217
+ },
218
+ },
219
+ },
220
+ contentContainer: {
221
+ flex: 1,
222
+ flexDirection: 'row',
223
+ alignItems: 'flex-start',
224
+ justifyContent: 'space-between',
225
+ gap: theme.space.lg,
226
+ },
227
+ textContainer: {
228
+ flex: 1,
229
+ gap: theme.space.lg,
230
+ },
231
+ heading: {
232
+ compoundVariants: [
233
+ {
234
+ direction: 'vertical',
235
+ hasIllustration: true,
236
+ styles: {
237
+ textAlign: 'center',
238
+ },
239
+ },
240
+ ],
241
+ },
242
+ description: {
243
+ compoundVariants: [
244
+ {
245
+ direction: 'vertical',
246
+ hasIllustration: true,
247
+ styles: {
248
+ textAlign: 'center',
249
+ },
250
+ },
251
+ ],
252
+ },
253
+ action: {
254
+ alignSelf: 'flex-start',
255
+ },
256
+ chevron: {
257
+ alignSelf: 'center',
258
+ },
259
+ closeButton: {
260
+ alignSelf: 'flex-start',
261
+ },
262
+ }));
263
+
264
+ export default Banner;
@@ -0,0 +1,2 @@
1
+ export { default as Banner } from './Banner';
2
+ export type { default as BannerProps } from './Banner.props';
@@ -9,7 +9,6 @@ const BottomSheetBackdrop = ({ style, ...props }: BottomSheetDefaultBackdropProp
9
9
  const theme = useTheme();
10
10
  return (
11
11
  <StyledBottomSheetBackdrop
12
- // @ts-expect-error - style prop type issue
13
12
  style={[styles.backdrop, style]}
14
13
  opacity={theme.components.overlay.opacity / 100}
15
14
  appearsOnIndex={0}
@@ -19,7 +19,6 @@ const BottomSheetFlatList = ({
19
19
 
20
20
  return (
21
21
  <StyledBottomSheetFlatList
22
- // @ts-expect-error - style prop type issue
23
22
  style={[styles.container, style]}
24
23
  contentContainerStyle={[styles.contentContainer, contentContainerStyle]}
25
24
  {...props}
@@ -7,7 +7,6 @@ const StyledBottomSheetHandle = withUnistyles(Handle);
7
7
  const BottomSheetHandle = ({ style, indicatorStyle, ...props }: BottomSheetDefaultHandleProps) => {
8
8
  return (
9
9
  <StyledBottomSheetHandle
10
- // @ts-expect-error - style prop type issue
11
10
  style={[styles.handle, style]}
12
11
  indicatorStyle={[styles.indicator, indicatorStyle]}
13
12
  {...props}
@@ -17,10 +17,18 @@ A Card component serves as a visual container that groups related content and ac
17
17
  - [Playground](#playground)
18
18
  - [Usage](#usage)
19
19
  - [Props](#props)
20
+ - [`CardPressHandler` Props](#cardpresshandler-props)
21
+ - [`CardAction` Props](#cardaction-props)
20
22
  - [Variants](#variants)
21
23
  - [Examples](#examples)
22
24
  - [Interactive](#interactive)
23
25
  - [With `CardAction`](#with-cardaction)
26
+ - [`CardAction` Playground](#cardaction-playground)
27
+ - [`CardAction` With Badge](#cardaction-with-badge)
28
+ - [`CardAction` Size Variants](#cardaction-size-variants)
29
+ - [`CardAction` Advanced Usage with Children](#cardaction-advanced-usage-with-children)
30
+ - [`CardAction` Component Parts](#cardaction-component-parts)
31
+ - [`CardAction` Accessibility](#cardaction-accessibility)
24
32
 
25
33
  ## Playground
26
34
 
@@ -69,13 +77,13 @@ const MyComponent = () => (
69
77
  | rowGap | `number` | The row gap between the content. | `0` |
70
78
  | columnGap | `number` | The column gap between the content. | `0` |
71
79
 
72
- ### `CardPressHandler`
80
+ ### `CardPressHandler` Props
73
81
 
74
82
  | Property | Type | Description | Default |
75
83
  | ---------------- | -------- | --------------------------------------------------------------------------- | --------- |
76
84
  | handlerToInherit | `string` | The handler to inherit from the child component when the `Card` is pressed. | `onPress` |
77
85
 
78
- ### `CardAction`
86
+ ### `CardAction` Props
79
87
 
80
88
  | Property | Type | Description | Default |
81
89
  | -------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------- |
@@ -0,0 +1,201 @@
1
+ import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks';
2
+ import { BodyText, Button, Card, Center } from '../../';
3
+ import { BackToTopButton, UsageWrap } from '../../../docs/components';
4
+ import * as Stories from './Expandable.stories';
5
+
6
+ <Meta title="Utility Components / Expandable" />
7
+
8
+ <BackToTopButton />
9
+
10
+ # Expandable
11
+
12
+ The Expandable component is a primitive for creating expandable content with smooth animations. It's commonly used as a building block for components like accordions, collapsible sections, and other interactive content that needs to expand and collapse.
13
+
14
+ - [Playground](#playground)
15
+ - [Usage](#usage)
16
+ - [Props](#props)
17
+ - [Animation Duration](#animation-duration)
18
+ - [Examples](#examples)
19
+ - [Basic Example](#basic-example)
20
+ - [With Different Animation Speeds](#with-different-animation-speeds)
21
+ - [Multiple Expandables](#multiple-expandables)
22
+ - [Controlled Example](#controlled-example)
23
+ - [Accessibility](#accessibility)
24
+
25
+ ## Playground
26
+
27
+ <Canvas of={Stories.Playground} />
28
+
29
+ <Controls of={Stories.Playground} />
30
+
31
+ ## Usage
32
+
33
+ <Canvas of={Stories.BasicExample} />
34
+
35
+ ```tsx
36
+ import { useState } from 'react';
37
+ import { View } from 'react-native';
38
+ import { Expandable, Button, Card, BodyText } from '@utilitywarehouse/hearth-react-native';
39
+
40
+ const MyComponent = () => {
41
+ const [expanded, setExpanded] = useState(false);
42
+
43
+ return (
44
+ <View>
45
+ <Button onPress={() => setExpanded(!expanded)}>Toggle Content</Button>
46
+ <Expandable expanded={expanded}>
47
+ <Card>
48
+ <BodyText>This content expands and collapses with a smooth animation.</BodyText>
49
+ </Card>
50
+ </Expandable>
51
+ </View>
52
+ );
53
+ };
54
+ ```
55
+
56
+ ## Props
57
+
58
+ | Prop | Type | Default | Description |
59
+ | -------------------- | ----------------------------- | ------- | ---------------------------------------------------- |
60
+ | `expanded` | `boolean` | `false` | Whether the content is expanded |
61
+ | `onExpandedChange` | `(expanded: boolean) => void` | - | Callback when expanded state changes |
62
+ | `children` | `ReactNode` | - | The content to expand/collapse |
63
+ | `duration` | `number` | `200` | Duration of the animation in milliseconds |
64
+ | `animateOpacity` | `boolean` | `true` | Whether to animate opacity during expansion/collapse |
65
+ | `style` | `ViewStyle` | - | Additional style for the container |
66
+ | `accessibilityLabel` | `string` | - | Accessibility label for screen readers |
67
+ | `testID` | `string` | - | Test ID for testing purposes |
68
+
69
+ ## Animation Duration
70
+
71
+ The `duration` prop controls how long the expand/collapse animation takes. The default is 200ms, which provides a smooth, natural feel. You can adjust this for faster or slower animations.
72
+
73
+ <Canvas of={Stories.FastAnimation} />
74
+
75
+ ```tsx
76
+ // Fast animation (150ms)
77
+ <Expandable expanded={expanded} duration={150}>
78
+ <Card>
79
+ <BodyText>Quick animation</BodyText>
80
+ </Card>
81
+ </Expandable>
82
+
83
+ // Slow animation (600ms)
84
+ <Expandable expanded={expanded} duration={600}>
85
+ <Card>
86
+ <BodyText>Slow animation</BodyText>
87
+ </Card>
88
+ </Expandable>
89
+ ```
90
+
91
+ ## Examples
92
+
93
+ ### Basic Example
94
+
95
+ <Canvas of={Stories.BasicExample} />
96
+
97
+ ### With Different Animation Speeds
98
+
99
+ <Canvas of={Stories.FastAnimation} />
100
+ <Canvas of={Stories.SlowAnimation} />
101
+
102
+ ### Multiple Expandables
103
+
104
+ <Canvas of={Stories.MultipleExpandables} />
105
+
106
+ ```tsx
107
+ import { useState } from 'react';
108
+ import { View } from 'react-native';
109
+ import { Expandable, Button, Card, BodyText } from '@utilitywarehouse/hearth-react-native';
110
+
111
+ const MyComponent = () => {
112
+ const [expanded1, setExpanded1] = useState(false);
113
+ const [expanded2, setExpanded2] = useState(false);
114
+ const [expanded3, setExpanded3] = useState(false);
115
+
116
+ return (
117
+ <View style={{ gap: 16 }}>
118
+ <View>
119
+ <Button onPress={() => setExpanded1(!expanded1)}>Section 1 {expanded1 ? '▲' : '▼'}</Button>
120
+ <Expandable expanded={expanded1}>
121
+ <Card>
122
+ <BodyText>Content for section 1</BodyText>
123
+ </Card>
124
+ </Expandable>
125
+ </View>
126
+
127
+ <View>
128
+ <Button onPress={() => setExpanded2(!expanded2)}>Section 2 {expanded2 ? '▲' : '▼'}</Button>
129
+ <Expandable expanded={expanded2}>
130
+ <Card>
131
+ <BodyText>Content for section 2</BodyText>
132
+ </Card>
133
+ </Expandable>
134
+ </View>
135
+
136
+ <View>
137
+ <Button onPress={() => setExpanded3(!expanded3)}>Section 3 {expanded3 ? '▲' : '▼'}</Button>
138
+ <Expandable expanded={expanded3}>
139
+ <Card>
140
+ <BodyText>Content for section 3</BodyText>
141
+ </Card>
142
+ </Expandable>
143
+ </View>
144
+ </View>
145
+ );
146
+ };
147
+ ```
148
+
149
+ ### Controlled Example
150
+
151
+ <Canvas of={Stories.ControlledExample} />
152
+
153
+ ```tsx
154
+ import { useState } from 'react';
155
+ import { View } from 'react-native';
156
+ import { Expandable, Button, Card, BodyText } from '@utilitywarehouse/hearth-react-native';
157
+
158
+ const MyComponent = () => {
159
+ const [expanded, setExpanded] = useState(false);
160
+
161
+ return (
162
+ <View>
163
+ <View style={{ flexDirection: 'row', gap: 8 }}>
164
+ <Button onPress={() => setExpanded(true)}>Expand</Button>
165
+ <Button onPress={() => setExpanded(false)}>Collapse</Button>
166
+ </View>
167
+ <Expandable
168
+ expanded={expanded}
169
+ onExpandedChange={setExpanded}
170
+ accessibilityLabel="Expandable content section"
171
+ >
172
+ <Card>
173
+ <BodyText>Controlled expandable content</BodyText>
174
+ </Card>
175
+ </Expandable>
176
+ </View>
177
+ );
178
+ };
179
+ ```
180
+
181
+ ## Accessibility
182
+
183
+ The Expandable component includes built-in accessibility support:
184
+
185
+ - Uses `accessibilityState` to communicate expanded/collapsed state to screen readers
186
+ - Set `accessibilityLabel` to provide context about the expandable content
187
+ - Automatically announces state changes to assistive technologies
188
+
189
+ ```tsx
190
+ <Expandable expanded={expanded} accessibilityLabel="Additional information section">
191
+ {/* content */}
192
+ </Expandable>
193
+ ```
194
+
195
+ ### Screen Reader Announcements
196
+
197
+ When using the Expandable component:
198
+
199
+ - The `accessibilityState` prop automatically includes the `expanded` state
200
+ - Screen readers will announce "expanded" or "collapsed" based on the current state
201
+ - Provide descriptive `accessibilityLabel` values to give users context
@@ -0,0 +1,46 @@
1
+ import { ReactNode } from 'react';
2
+ import { ViewProps, ViewStyle } from 'react-native';
3
+
4
+ export interface ExpandableProps extends ViewProps {
5
+ /**
6
+ * Whether the content is expanded
7
+ */
8
+ expanded?: boolean;
9
+
10
+ /**
11
+ * Callback when expanded state changes
12
+ */
13
+ onExpandedChange?: (expanded: boolean) => void;
14
+
15
+ /**
16
+ * The content to expand/collapse
17
+ */
18
+ children: ReactNode;
19
+
20
+ /**
21
+ * Duration of the animation in milliseconds
22
+ * @default 200
23
+ */
24
+ duration?: number;
25
+
26
+ /**
27
+ * Additional style for the container
28
+ */
29
+ style?: ViewStyle;
30
+
31
+ /**
32
+ * Accessibility label for screen readers
33
+ */
34
+ accessibilityLabel?: string;
35
+
36
+ /**
37
+ * Test ID for testing
38
+ */
39
+ testID?: string;
40
+
41
+ /**
42
+ * Whether to animate opacity during expansion/collapse
43
+ * @default true
44
+ */
45
+ animateOpacity?: boolean;
46
+ }