@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.
- package/.storybook/main.ts +12 -6
- 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/Button/ButtonIcon.js +2 -1
- package/build/components/Button/ButtonRoot.js +2 -6
- package/build/components/Button/ButtonText.js +4 -1
- package/build/components/Card/Card.context.d.ts +7 -0
- package/build/components/Card/CardAction/CardAction.context.d.ts +9 -0
- package/build/components/Card/{CardAction.context.js → CardAction/CardAction.context.js} +7 -1
- package/build/components/Card/CardAction/CardAction.d.ts +18 -0
- package/build/components/Card/CardAction/CardAction.js +7 -0
- package/build/components/Card/CardAction/CardAction.props.d.ts +63 -0
- package/build/components/Card/CardAction/CardAction.props.js +1 -0
- package/build/components/Card/CardAction/CardActionContent.d.ts +6 -0
- package/build/components/Card/CardAction/CardActionContent.js +13 -0
- package/build/components/Card/CardAction/CardActionHelperText.d.ts +6 -0
- package/build/components/Card/CardAction/CardActionHelperText.js +13 -0
- package/build/components/Card/CardAction/CardActionIcon.d.ts +9 -0
- package/build/components/Card/CardAction/CardActionIcon.js +19 -0
- package/build/components/Card/CardAction/CardActionLeadingContent.d.ts +6 -0
- package/build/components/Card/CardAction/CardActionLeadingContent.js +5 -0
- package/build/components/Card/CardAction/CardActionRoot.d.ts +12 -0
- package/build/components/Card/CardAction/CardActionRoot.js +155 -0
- package/build/components/Card/CardAction/CardActionText.d.ts +6 -0
- package/build/components/Card/CardAction/CardActionText.js +9 -0
- package/build/components/Card/CardAction/CardActionTrailingContent.d.ts +6 -0
- package/build/components/Card/CardAction/CardActionTrailingContent.js +5 -0
- package/build/components/Card/CardAction/CardActionTrailingIcon.d.ts +9 -0
- package/build/components/Card/CardAction/CardActionTrailingIcon.js +19 -0
- package/build/components/Card/CardAction/index.d.ts +10 -0
- package/build/components/Card/CardAction/index.js +9 -0
- package/build/components/Card/CardContent.d.ts +6 -0
- package/build/components/Card/CardContent.js +33 -0
- package/build/components/Card/CardPressHandler.context.d.ts +6 -0
- package/build/components/Card/CardPressHandler.context.js +6 -0
- package/build/components/Card/{CardAction.d.ts → CardPressHandler.d.ts} +3 -3
- package/build/components/Card/CardPressHandler.js +13 -0
- package/build/components/Card/CardRoot.js +103 -11
- package/build/components/Card/index.d.ts +3 -2
- package/build/components/Card/index.js +3 -2
- package/build/components/Checkbox/CheckboxIcon.js +2 -1
- package/build/components/Container/Container.js +3 -3
- package/build/components/CurrencyInput/CurrencyInput.js +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/Helper/HelperIcon.js +2 -1
- package/build/components/HighlightBanner/HighlightBanner.d.ts +6 -0
- package/build/components/HighlightBanner/HighlightBanner.js +86 -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/Icon/Icon.d.ts +2 -6
- package/build/components/IconButton/IconButtonIcon.js +2 -1
- package/build/components/IconContainer/IconContainer.d.ts +4 -3
- package/build/components/IconContainer/IconContainer.js +3 -3
- package/build/components/Input/InputField.js +4 -2
- package/build/components/Input/InputIcon.js +2 -1
- package/build/components/Link/LinkIcon.js +3 -2
- package/build/components/List/ListAction/ListActionTrailingIcon.js +2 -1
- package/build/components/List/ListItem/ListItemIcon.js +2 -1
- package/build/components/List/ListItem/ListItemTrailingIcon.js +2 -3
- package/build/components/Radio/RadioIcon.js +7 -2
- package/build/components/RadioCard/RadioCardIcon.js +3 -2
- package/build/components/Spinner/Spinner.web.d.ts +2 -1
- package/build/components/Switch/Switch.js +5 -3
- package/build/components/Textarea/TextareaField.js +1 -1
- package/build/components/ToggleButton/ToggleButtonIcon.js +2 -1
- package/build/components/ToggleButton/ToggleButtonRoot.js +2 -2
- package/build/components/UnstyledIconButton/UnstyledIconButtonIcon.js +2 -1
- package/build/components/index.d.ts +5 -1
- package/build/components/index.js +5 -1
- package/build/core/index.d.ts +3 -3
- package/build/core/index.js +3 -3
- package/build/core/themes.d.ts +24 -12
- package/build/hooks/useColorMode.d.ts +1 -1
- package/build/hooks/useColorMode.js +7 -8
- package/build/tokens/components/dark/banner.d.ts +19 -0
- package/build/tokens/components/dark/banner.js +19 -0
- package/build/tokens/components/dark/card-action.d.ts +11 -0
- package/build/tokens/components/dark/card-action.js +10 -0
- package/build/tokens/components/dark/card-content.d.ts +25 -0
- package/build/tokens/components/dark/card-content.js +24 -0
- package/build/tokens/components/dark/drawer.d.ts +29 -0
- package/build/tokens/components/dark/drawer.js +28 -0
- package/build/tokens/components/dark/illustrations.d.ts +0 -1
- package/build/tokens/components/dark/illustrations.js +0 -1
- package/build/tokens/components/dark/index.d.ts +3 -0
- package/build/tokens/components/dark/index.js +3 -0
- package/build/tokens/components/light/banner.d.ts +19 -0
- package/build/tokens/components/light/banner.js +19 -0
- package/build/tokens/components/light/card-action.d.ts +11 -0
- package/build/tokens/components/light/card-action.js +10 -0
- package/build/tokens/components/light/card-content.d.ts +25 -0
- package/build/tokens/components/light/card-content.js +24 -0
- package/build/tokens/components/light/drawer.d.ts +29 -0
- package/build/tokens/components/light/drawer.js +28 -0
- package/build/tokens/components/light/illustrations.d.ts +0 -1
- package/build/tokens/components/light/illustrations.js +0 -1
- package/build/tokens/components/light/index.d.ts +3 -0
- package/build/tokens/components/light/index.js +3 -0
- package/build/tokens/layout.d.ts +6 -6
- package/build/tokens/layout.js +3 -3
- package/build/tokens/typography.d.ts +6 -0
- package/build/tokens/typography.js +3 -0
- package/docs/components/AllComponents.web.tsx +75 -4
- package/docs/components/NextPrevPage.tsx +5 -5
- package/docs/components/VariantTitle.tsx +17 -7
- package/package.json +16 -14
- 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 +1 -2
- package/src/components/BottomSheet/BottomSheetFlatList.tsx +1 -3
- package/src/components/BottomSheet/BottomSheetHandle.tsx +0 -1
- package/src/components/Button/ButtonIcon.tsx +2 -1
- package/src/components/Button/ButtonRoot.tsx +2 -6
- package/src/components/Button/ButtonText.tsx +4 -1
- package/src/components/Card/Card.context.ts +7 -0
- package/src/components/Card/Card.docs.mdx +221 -15
- package/src/components/Card/Card.stories.tsx +50 -3
- package/src/components/Card/CardAction/CardAction.context.ts +22 -0
- package/src/components/Card/CardAction/CardAction.props.ts +87 -0
- package/src/components/Card/CardAction/CardAction.stories.tsx +265 -0
- package/src/components/Card/CardAction/CardAction.tsx +10 -0
- package/src/components/Card/CardAction/CardActionContent.tsx +20 -0
- package/src/components/Card/CardAction/CardActionHelperText.tsx +21 -0
- package/src/components/Card/CardAction/CardActionIcon.tsx +32 -0
- package/src/components/Card/CardAction/CardActionLeadingContent.tsx +9 -0
- package/src/components/Card/CardAction/CardActionRoot.tsx +258 -0
- package/src/components/Card/CardAction/CardActionText.tsx +17 -0
- package/src/components/Card/CardAction/CardActionTrailingContent.tsx +9 -0
- package/src/components/Card/CardAction/CardActionTrailingIcon.tsx +32 -0
- package/src/components/Card/CardAction/index.ts +10 -0
- package/src/components/Card/CardContent.tsx +40 -0
- package/src/components/Card/CardPressHandler.context.ts +12 -0
- package/src/components/Card/CardPressHandler.tsx +20 -0
- package/src/components/Card/CardRoot.tsx +128 -13
- package/src/components/Card/index.ts +3 -2
- package/src/components/Checkbox/CheckboxIcon.tsx +2 -1
- package/src/components/Container/Container.tsx +3 -3
- package/src/components/CurrencyInput/CurrencyInput.tsx +1 -1
- 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/Helper/HelperIcon.tsx +2 -1
- package/src/components/HighlightBanner/HighlightBanner.docs.mdx +277 -0
- package/src/components/HighlightBanner/HighlightBanner.props.ts +29 -0
- package/src/components/HighlightBanner/HighlightBanner.stories.tsx +259 -0
- package/src/components/HighlightBanner/HighlightBanner.tsx +122 -0
- package/src/components/HighlightBanner/index.ts +2 -0
- package/src/components/Icon/Icon.tsx +4 -3
- package/src/components/IconButton/IconButtonIcon.tsx +2 -1
- package/src/components/IconContainer/IconContainer.tsx +17 -19
- package/src/components/Input/InputField.tsx +2 -1
- package/src/components/Input/InputIcon.tsx +2 -1
- package/src/components/Link/LinkIcon.tsx +3 -2
- package/src/components/List/ListAction/ListActionTrailingIcon.tsx +2 -1
- package/src/components/List/ListItem/ListItemIcon.tsx +2 -1
- package/src/components/List/ListItem/ListItemTrailingIcon.tsx +2 -3
- package/src/components/Radio/RadioIcon.tsx +8 -3
- package/src/components/RadioCard/RadioCardIcon.tsx +4 -3
- package/src/components/Switch/Switch.tsx +10 -5
- package/src/components/Switch/Switch.web.tsx +1 -0
- package/src/components/Textarea/TextareaField.tsx +1 -1
- package/src/components/ToggleButton/ToggleButtonIcon.tsx +2 -1
- package/src/components/ToggleButton/ToggleButtonRoot.tsx +2 -2
- package/src/components/UnstyledIconButton/UnstyledIconButtonIcon.tsx +2 -1
- package/src/components/index.ts +5 -9
- package/src/core/index.ts +14 -11
- package/src/hooks/useColorMode.ts +9 -12
- package/src/tokens/components/dark/banner.ts +19 -0
- package/src/tokens/components/dark/card-action.ts +11 -0
- package/src/tokens/components/dark/card-content.ts +25 -0
- package/src/tokens/components/dark/drawer.ts +29 -0
- package/src/tokens/components/dark/illustrations.ts +0 -1
- package/src/tokens/components/dark/index.ts +3 -0
- package/src/tokens/components/light/banner.ts +19 -0
- package/src/tokens/components/light/card-action.ts +11 -0
- package/src/tokens/components/light/card-content.ts +25 -0
- package/src/tokens/components/light/drawer.ts +29 -0
- package/src/tokens/components/light/illustrations.ts +0 -1
- package/src/tokens/components/light/index.ts +3 -0
- package/src/tokens/layout.ts +3 -3
- package/src/tokens/typography.ts +3 -0
- package/build/components/Card/CardAction.context.d.ts +0 -6
- package/build/components/Card/CardAction.js +0 -13
- package/src/components/Card/CardAction.context.ts +0 -12
- 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;
|