@utilitywarehouse/hearth-react-native 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/.storybook/main.ts +12 -6
  2. package/.turbo/turbo-build.log +1 -1
  3. package/.turbo/turbo-lint.log +1 -1
  4. package/CHANGELOG.md +18 -0
  5. package/build/components/Banner/Banner.d.ts +6 -0
  6. package/build/components/Banner/Banner.js +161 -0
  7. package/build/components/Banner/Banner.props.d.ts +82 -0
  8. package/build/components/Banner/Banner.props.js +1 -0
  9. package/build/components/Banner/index.d.ts +2 -0
  10. package/build/components/Banner/index.js +1 -0
  11. package/build/components/BottomSheet/BottomSheetBackdrop.js +1 -5
  12. package/build/components/BottomSheet/BottomSheetFlatList.js +1 -5
  13. package/build/components/BottomSheet/BottomSheetHandle.js +1 -5
  14. package/build/components/BottomSheet/useBottomSheetLogic.d.ts +1 -1
  15. package/build/components/Button/ButtonIcon.js +2 -1
  16. package/build/components/Button/ButtonRoot.js +2 -6
  17. package/build/components/Button/ButtonText.js +4 -1
  18. package/build/components/Card/Card.context.d.ts +7 -0
  19. package/build/components/Card/CardAction/CardAction.context.d.ts +9 -0
  20. package/build/components/Card/{CardAction.context.js → CardAction/CardAction.context.js} +7 -1
  21. package/build/components/Card/CardAction/CardAction.d.ts +18 -0
  22. package/build/components/Card/CardAction/CardAction.js +7 -0
  23. package/build/components/Card/CardAction/CardAction.props.d.ts +63 -0
  24. package/build/components/Card/CardAction/CardAction.props.js +1 -0
  25. package/build/components/Card/CardAction/CardActionContent.d.ts +6 -0
  26. package/build/components/Card/CardAction/CardActionContent.js +13 -0
  27. package/build/components/Card/CardAction/CardActionHelperText.d.ts +6 -0
  28. package/build/components/Card/CardAction/CardActionHelperText.js +13 -0
  29. package/build/components/Card/CardAction/CardActionIcon.d.ts +9 -0
  30. package/build/components/Card/CardAction/CardActionIcon.js +19 -0
  31. package/build/components/Card/CardAction/CardActionLeadingContent.d.ts +6 -0
  32. package/build/components/Card/CardAction/CardActionLeadingContent.js +5 -0
  33. package/build/components/Card/CardAction/CardActionRoot.d.ts +12 -0
  34. package/build/components/Card/CardAction/CardActionRoot.js +155 -0
  35. package/build/components/Card/CardAction/CardActionText.d.ts +6 -0
  36. package/build/components/Card/CardAction/CardActionText.js +9 -0
  37. package/build/components/Card/CardAction/CardActionTrailingContent.d.ts +6 -0
  38. package/build/components/Card/CardAction/CardActionTrailingContent.js +5 -0
  39. package/build/components/Card/CardAction/CardActionTrailingIcon.d.ts +9 -0
  40. package/build/components/Card/CardAction/CardActionTrailingIcon.js +19 -0
  41. package/build/components/Card/CardAction/index.d.ts +10 -0
  42. package/build/components/Card/CardAction/index.js +9 -0
  43. package/build/components/Card/CardContent.d.ts +6 -0
  44. package/build/components/Card/CardContent.js +33 -0
  45. package/build/components/Card/CardPressHandler.context.d.ts +6 -0
  46. package/build/components/Card/CardPressHandler.context.js +6 -0
  47. package/build/components/Card/{CardAction.d.ts → CardPressHandler.d.ts} +3 -3
  48. package/build/components/Card/CardPressHandler.js +13 -0
  49. package/build/components/Card/CardRoot.js +103 -11
  50. package/build/components/Card/index.d.ts +3 -2
  51. package/build/components/Card/index.js +3 -2
  52. package/build/components/Checkbox/CheckboxIcon.js +2 -1
  53. package/build/components/Container/Container.js +3 -3
  54. package/build/components/CurrencyInput/CurrencyInput.js +1 -1
  55. package/build/components/Expandable/Expandable.d.ts +6 -0
  56. package/build/components/Expandable/Expandable.js +44 -0
  57. package/build/components/Expandable/Expandable.props.d.ts +38 -0
  58. package/build/components/Expandable/Expandable.props.js +1 -0
  59. package/build/components/Expandable/index.d.ts +2 -0
  60. package/build/components/Expandable/index.js +1 -0
  61. package/build/components/ExpandableCard/ExpandableCard.d.ts +6 -0
  62. package/build/components/ExpandableCard/ExpandableCard.js +23 -0
  63. package/build/components/ExpandableCard/ExpandableCard.props.d.ts +69 -0
  64. package/build/components/ExpandableCard/ExpandableCard.props.js +1 -0
  65. package/build/components/ExpandableCard/ExpandableCardContent.d.ts +6 -0
  66. package/build/components/ExpandableCard/ExpandableCardContent.js +14 -0
  67. package/build/components/ExpandableCard/ExpandableCardExpandedContent.d.ts +11 -0
  68. package/build/components/ExpandableCard/ExpandableCardExpandedContent.js +18 -0
  69. package/build/components/ExpandableCard/ExpandableCardGroup.d.ts +6 -0
  70. package/build/components/ExpandableCard/ExpandableCardGroup.js +17 -0
  71. package/build/components/ExpandableCard/ExpandableCardGroup.props.d.ts +25 -0
  72. package/build/components/ExpandableCard/ExpandableCardGroup.props.js +1 -0
  73. package/build/components/ExpandableCard/ExpandableCardHelperText.d.ts +6 -0
  74. package/build/components/ExpandableCard/ExpandableCardHelperText.js +13 -0
  75. package/build/components/ExpandableCard/ExpandableCardIcon.d.ts +9 -0
  76. package/build/components/ExpandableCard/ExpandableCardIcon.js +19 -0
  77. package/build/components/ExpandableCard/ExpandableCardLeadingContent.d.ts +6 -0
  78. package/build/components/ExpandableCard/ExpandableCardLeadingContent.js +5 -0
  79. package/build/components/ExpandableCard/ExpandableCardText.d.ts +6 -0
  80. package/build/components/ExpandableCard/ExpandableCardText.js +7 -0
  81. package/build/components/ExpandableCard/ExpandableCardTrailingContent.d.ts +6 -0
  82. package/build/components/ExpandableCard/ExpandableCardTrailingContent.js +5 -0
  83. package/build/components/ExpandableCard/ExpandableCardTrailingIcon.d.ts +9 -0
  84. package/build/components/ExpandableCard/ExpandableCardTrailingIcon.js +17 -0
  85. package/build/components/ExpandableCard/ExpandableCardTrigger.d.ts +17 -0
  86. package/build/components/ExpandableCard/ExpandableCardTrigger.js +7 -0
  87. package/build/components/ExpandableCard/ExpandableCardTrigger.props.d.ts +44 -0
  88. package/build/components/ExpandableCard/ExpandableCardTrigger.props.js +1 -0
  89. package/build/components/ExpandableCard/ExpandableCardTriggerRoot.d.ts +11 -0
  90. package/build/components/ExpandableCard/ExpandableCardTriggerRoot.js +91 -0
  91. package/build/components/ExpandableCard/index.d.ts +14 -0
  92. package/build/components/ExpandableCard/index.js +11 -0
  93. package/build/components/Helper/HelperIcon.js +2 -1
  94. package/build/components/HighlightBanner/HighlightBanner.d.ts +6 -0
  95. package/build/components/HighlightBanner/HighlightBanner.js +86 -0
  96. package/build/components/HighlightBanner/HighlightBanner.props.d.ts +14 -0
  97. package/build/components/HighlightBanner/HighlightBanner.props.js +1 -0
  98. package/build/components/HighlightBanner/index.d.ts +2 -0
  99. package/build/components/HighlightBanner/index.js +1 -0
  100. package/build/components/Icon/Icon.d.ts +2 -6
  101. package/build/components/IconButton/IconButtonIcon.js +2 -1
  102. package/build/components/IconContainer/IconContainer.d.ts +4 -3
  103. package/build/components/IconContainer/IconContainer.js +3 -3
  104. package/build/components/Input/InputField.js +4 -2
  105. package/build/components/Input/InputIcon.js +2 -1
  106. package/build/components/Link/LinkIcon.js +3 -2
  107. package/build/components/List/ListAction/ListActionTrailingIcon.js +2 -1
  108. package/build/components/List/ListItem/ListItemIcon.js +2 -1
  109. package/build/components/List/ListItem/ListItemTrailingIcon.js +2 -3
  110. package/build/components/Radio/RadioIcon.js +7 -2
  111. package/build/components/RadioCard/RadioCardIcon.js +3 -2
  112. package/build/components/Spinner/Spinner.web.d.ts +2 -1
  113. package/build/components/Switch/Switch.js +5 -3
  114. package/build/components/Textarea/TextareaField.js +1 -1
  115. package/build/components/ToggleButton/ToggleButtonIcon.js +2 -1
  116. package/build/components/ToggleButton/ToggleButtonRoot.js +2 -2
  117. package/build/components/UnstyledIconButton/UnstyledIconButtonIcon.js +2 -1
  118. package/build/components/index.d.ts +5 -1
  119. package/build/components/index.js +5 -1
  120. package/build/core/index.d.ts +3 -3
  121. package/build/core/index.js +3 -3
  122. package/build/core/themes.d.ts +24 -12
  123. package/build/hooks/useColorMode.d.ts +1 -1
  124. package/build/hooks/useColorMode.js +7 -8
  125. package/build/tokens/components/dark/banner.d.ts +19 -0
  126. package/build/tokens/components/dark/banner.js +19 -0
  127. package/build/tokens/components/dark/card-action.d.ts +11 -0
  128. package/build/tokens/components/dark/card-action.js +10 -0
  129. package/build/tokens/components/dark/card-content.d.ts +25 -0
  130. package/build/tokens/components/dark/card-content.js +24 -0
  131. package/build/tokens/components/dark/drawer.d.ts +29 -0
  132. package/build/tokens/components/dark/drawer.js +28 -0
  133. package/build/tokens/components/dark/illustrations.d.ts +0 -1
  134. package/build/tokens/components/dark/illustrations.js +0 -1
  135. package/build/tokens/components/dark/index.d.ts +3 -0
  136. package/build/tokens/components/dark/index.js +3 -0
  137. package/build/tokens/components/light/banner.d.ts +19 -0
  138. package/build/tokens/components/light/banner.js +19 -0
  139. package/build/tokens/components/light/card-action.d.ts +11 -0
  140. package/build/tokens/components/light/card-action.js +10 -0
  141. package/build/tokens/components/light/card-content.d.ts +25 -0
  142. package/build/tokens/components/light/card-content.js +24 -0
  143. package/build/tokens/components/light/drawer.d.ts +29 -0
  144. package/build/tokens/components/light/drawer.js +28 -0
  145. package/build/tokens/components/light/illustrations.d.ts +0 -1
  146. package/build/tokens/components/light/illustrations.js +0 -1
  147. package/build/tokens/components/light/index.d.ts +3 -0
  148. package/build/tokens/components/light/index.js +3 -0
  149. package/build/tokens/layout.d.ts +6 -6
  150. package/build/tokens/layout.js +3 -3
  151. package/build/tokens/typography.d.ts +6 -0
  152. package/build/tokens/typography.js +3 -0
  153. package/docs/components/AllComponents.web.tsx +75 -4
  154. package/docs/components/NextPrevPage.tsx +5 -5
  155. package/docs/components/VariantTitle.tsx +17 -7
  156. package/package.json +16 -14
  157. package/src/components/Banner/Banner.docs.mdx +402 -0
  158. package/src/components/Banner/Banner.props.ts +106 -0
  159. package/src/components/Banner/Banner.stories.tsx +494 -0
  160. package/src/components/Banner/Banner.tsx +264 -0
  161. package/src/components/Banner/index.ts +2 -0
  162. package/src/components/BottomSheet/BottomSheetBackdrop.tsx +1 -2
  163. package/src/components/BottomSheet/BottomSheetFlatList.tsx +1 -3
  164. package/src/components/BottomSheet/BottomSheetHandle.tsx +0 -1
  165. package/src/components/Button/ButtonIcon.tsx +2 -1
  166. package/src/components/Button/ButtonRoot.tsx +2 -6
  167. package/src/components/Button/ButtonText.tsx +4 -1
  168. package/src/components/Card/Card.context.ts +7 -0
  169. package/src/components/Card/Card.docs.mdx +221 -15
  170. package/src/components/Card/Card.stories.tsx +50 -3
  171. package/src/components/Card/CardAction/CardAction.context.ts +22 -0
  172. package/src/components/Card/CardAction/CardAction.props.ts +87 -0
  173. package/src/components/Card/CardAction/CardAction.stories.tsx +265 -0
  174. package/src/components/Card/CardAction/CardAction.tsx +10 -0
  175. package/src/components/Card/CardAction/CardActionContent.tsx +20 -0
  176. package/src/components/Card/CardAction/CardActionHelperText.tsx +21 -0
  177. package/src/components/Card/CardAction/CardActionIcon.tsx +32 -0
  178. package/src/components/Card/CardAction/CardActionLeadingContent.tsx +9 -0
  179. package/src/components/Card/CardAction/CardActionRoot.tsx +258 -0
  180. package/src/components/Card/CardAction/CardActionText.tsx +17 -0
  181. package/src/components/Card/CardAction/CardActionTrailingContent.tsx +9 -0
  182. package/src/components/Card/CardAction/CardActionTrailingIcon.tsx +32 -0
  183. package/src/components/Card/CardAction/index.ts +10 -0
  184. package/src/components/Card/CardContent.tsx +40 -0
  185. package/src/components/Card/CardPressHandler.context.ts +12 -0
  186. package/src/components/Card/CardPressHandler.tsx +20 -0
  187. package/src/components/Card/CardRoot.tsx +128 -13
  188. package/src/components/Card/index.ts +3 -2
  189. package/src/components/Checkbox/CheckboxIcon.tsx +2 -1
  190. package/src/components/Container/Container.tsx +3 -3
  191. package/src/components/CurrencyInput/CurrencyInput.tsx +1 -1
  192. package/src/components/Expandable/Expandable.docs.mdx +201 -0
  193. package/src/components/Expandable/Expandable.props.ts +46 -0
  194. package/src/components/Expandable/Expandable.stories.tsx +284 -0
  195. package/src/components/Expandable/Expandable.tsx +92 -0
  196. package/src/components/Expandable/index.ts +2 -0
  197. package/src/components/ExpandableCard/ExpandableCard.docs.mdx +312 -0
  198. package/src/components/ExpandableCard/ExpandableCard.props.ts +85 -0
  199. package/src/components/ExpandableCard/ExpandableCard.stories.tsx +326 -0
  200. package/src/components/ExpandableCard/ExpandableCard.tsx +76 -0
  201. package/src/components/ExpandableCard/ExpandableCardContent.tsx +21 -0
  202. package/src/components/ExpandableCard/ExpandableCardExpandedContent.tsx +42 -0
  203. package/src/components/ExpandableCard/ExpandableCardGroup.props.ts +31 -0
  204. package/src/components/ExpandableCard/ExpandableCardGroup.tsx +40 -0
  205. package/src/components/ExpandableCard/ExpandableCardHelperText.tsx +21 -0
  206. package/src/components/ExpandableCard/ExpandableCardIcon.tsx +32 -0
  207. package/src/components/ExpandableCard/ExpandableCardLeadingContent.tsx +9 -0
  208. package/src/components/ExpandableCard/ExpandableCardText.tsx +14 -0
  209. package/src/components/ExpandableCard/ExpandableCardTrailingContent.tsx +9 -0
  210. package/src/components/ExpandableCard/ExpandableCardTrailingIcon.tsx +30 -0
  211. package/src/components/ExpandableCard/ExpandableCardTrigger.props.ts +47 -0
  212. package/src/components/ExpandableCard/ExpandableCardTrigger.tsx +10 -0
  213. package/src/components/ExpandableCard/ExpandableCardTriggerRoot.tsx +154 -0
  214. package/src/components/ExpandableCard/index.ts +14 -0
  215. package/src/components/Helper/HelperIcon.tsx +2 -1
  216. package/src/components/HighlightBanner/HighlightBanner.docs.mdx +277 -0
  217. package/src/components/HighlightBanner/HighlightBanner.props.ts +29 -0
  218. package/src/components/HighlightBanner/HighlightBanner.stories.tsx +259 -0
  219. package/src/components/HighlightBanner/HighlightBanner.tsx +122 -0
  220. package/src/components/HighlightBanner/index.ts +2 -0
  221. package/src/components/Icon/Icon.tsx +4 -3
  222. package/src/components/IconButton/IconButtonIcon.tsx +2 -1
  223. package/src/components/IconContainer/IconContainer.tsx +17 -19
  224. package/src/components/Input/InputField.tsx +2 -1
  225. package/src/components/Input/InputIcon.tsx +2 -1
  226. package/src/components/Link/LinkIcon.tsx +3 -2
  227. package/src/components/List/ListAction/ListActionTrailingIcon.tsx +2 -1
  228. package/src/components/List/ListItem/ListItemIcon.tsx +2 -1
  229. package/src/components/List/ListItem/ListItemTrailingIcon.tsx +2 -3
  230. package/src/components/Radio/RadioIcon.tsx +8 -3
  231. package/src/components/RadioCard/RadioCardIcon.tsx +4 -3
  232. package/src/components/Switch/Switch.tsx +10 -5
  233. package/src/components/Switch/Switch.web.tsx +1 -0
  234. package/src/components/Textarea/TextareaField.tsx +1 -1
  235. package/src/components/ToggleButton/ToggleButtonIcon.tsx +2 -1
  236. package/src/components/ToggleButton/ToggleButtonRoot.tsx +2 -2
  237. package/src/components/UnstyledIconButton/UnstyledIconButtonIcon.tsx +2 -1
  238. package/src/components/index.ts +5 -9
  239. package/src/core/index.ts +14 -11
  240. package/src/hooks/useColorMode.ts +9 -12
  241. package/src/tokens/components/dark/banner.ts +19 -0
  242. package/src/tokens/components/dark/card-action.ts +11 -0
  243. package/src/tokens/components/dark/card-content.ts +25 -0
  244. package/src/tokens/components/dark/drawer.ts +29 -0
  245. package/src/tokens/components/dark/illustrations.ts +0 -1
  246. package/src/tokens/components/dark/index.ts +3 -0
  247. package/src/tokens/components/light/banner.ts +19 -0
  248. package/src/tokens/components/light/card-action.ts +11 -0
  249. package/src/tokens/components/light/card-content.ts +25 -0
  250. package/src/tokens/components/light/drawer.ts +29 -0
  251. package/src/tokens/components/light/illustrations.ts +0 -1
  252. package/src/tokens/components/light/index.ts +3 -0
  253. package/src/tokens/layout.ts +3 -3
  254. package/src/tokens/typography.ts +3 -0
  255. package/build/components/Card/CardAction.context.d.ts +0 -6
  256. package/build/components/Card/CardAction.js +0 -13
  257. package/src/components/Card/CardAction.context.ts +0 -12
  258. package/src/components/Card/CardAction.tsx +0 -18
@@ -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
+ }
@@ -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';