@utilitywarehouse/hearth-react-native 0.2.0 → 0.3.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +22 -0
- package/build/components/Badge/Badge.js +101 -14
- package/build/components/Badge/Badge.props.d.ts +2 -2
- package/build/components/Badge/BadgeIcon.js +5 -79
- package/build/components/Badge/BadgeText.js +7 -81
- package/build/components/Button/Button.d.ts +2 -2
- package/build/components/Button/ButtonGroupRoot.d.ts +3 -2
- package/build/components/Button/ButtonGroupRoot.js +9 -0
- package/build/components/Card/Card.props.d.ts +2 -2
- package/build/components/CurrencyInput/CurrencyInput.d.ts +6 -0
- package/build/components/CurrencyInput/CurrencyInput.js +47 -0
- package/build/components/CurrencyInput/CurrencyInput.props.d.ts +14 -0
- package/build/components/CurrencyInput/CurrencyInput.props.js +1 -0
- package/build/components/CurrencyInput/index.d.ts +1 -0
- package/build/components/CurrencyInput/index.js +1 -0
- package/build/components/DescriptionList/DescriptionList.context.d.ts +6 -0
- package/build/components/DescriptionList/DescriptionList.context.js +9 -0
- package/build/components/DescriptionList/DescriptionList.d.ts +6 -0
- package/build/components/DescriptionList/DescriptionList.js +25 -0
- package/build/components/DescriptionList/DescriptionList.props.d.ts +18 -0
- package/build/components/DescriptionList/DescriptionList.props.js +1 -0
- package/build/components/DescriptionList/DescriptionListItem.d.ts +6 -0
- package/build/components/DescriptionList/DescriptionListItem.js +49 -0
- package/build/components/DescriptionList/DescriptionListItem.props.d.ts +17 -0
- package/build/components/DescriptionList/DescriptionListItem.props.js +1 -0
- package/build/components/DescriptionList/index.d.ts +4 -0
- package/build/components/DescriptionList/index.js +2 -0
- package/build/components/Divider/Divider.js +46 -0
- package/build/components/Divider/Divider.props.d.ts +2 -2
- package/build/components/Flex/Flex.props.d.ts +3 -2
- package/build/components/Grid/Grid.props.d.ts +2 -2
- package/build/components/IconContainer/IconContainer.d.ts +5 -0
- package/build/components/IconContainer/IconContainer.js +161 -0
- package/build/components/IconContainer/IconContainer.props.d.ts +15 -0
- package/build/components/IconContainer/IconContainer.props.js +1 -0
- package/build/components/IconContainer/index.d.ts +2 -0
- package/build/components/IconContainer/index.js +1 -0
- package/build/components/Icons/CircleIcon.js +3 -3
- package/build/components/Input/Input.js +2 -34
- package/build/components/Input/Input.props.d.ts +1 -17
- package/build/components/Input/InputField.js +0 -7
- package/build/components/Modal/Modal.js +17 -1
- package/build/components/SectionHeader/SectionHeader.js +1 -0
- package/build/components/Tabs/Tab.d.ts +18 -0
- package/build/components/Tabs/Tab.js +74 -0
- package/build/components/Tabs/Tab.props.d.ts +14 -0
- package/build/components/Tabs/Tab.props.js +1 -0
- package/build/components/Tabs/TabPanel.d.ts +3 -0
- package/build/components/Tabs/TabPanel.js +34 -0
- package/build/components/Tabs/TabPanel.props.d.ts +8 -0
- package/build/components/Tabs/TabPanel.props.js +1 -0
- package/build/components/Tabs/Tabs.context.d.ts +23 -0
- package/build/components/Tabs/Tabs.context.js +8 -0
- package/build/components/Tabs/Tabs.d.ts +6 -0
- package/build/components/Tabs/Tabs.js +114 -0
- package/build/components/Tabs/Tabs.props.d.ts +19 -0
- package/build/components/Tabs/Tabs.props.js +1 -0
- package/build/components/Tabs/TabsList.d.ts +6 -0
- package/build/components/Tabs/TabsList.js +112 -0
- package/build/components/Tabs/TabsList.props.d.ts +6 -0
- package/build/components/Tabs/TabsList.props.js +1 -0
- package/build/components/Tabs/index.d.ts +8 -0
- package/build/components/Tabs/index.js +4 -0
- package/build/components/index.d.ts +4 -0
- package/build/components/index.js +4 -0
- package/build/core/themes.d.ts +416 -148
- package/build/core/themes.js +57 -1
- package/build/tokens/color.d.ts +76 -68
- package/build/tokens/color.js +38 -34
- package/build/tokens/components/dark/button.d.ts +1 -0
- package/build/tokens/components/dark/button.js +1 -0
- package/build/tokens/components/dark/checkbox.d.ts +1 -1
- package/build/tokens/components/dark/checkbox.js +1 -1
- package/build/tokens/components/dark/icon-button.d.ts +3 -3
- package/build/tokens/components/dark/icon-button.js +3 -3
- package/build/tokens/components/dark/radio.d.ts +1 -1
- package/build/tokens/components/dark/radio.js +1 -1
- package/build/tokens/components/dark/tabs.d.ts +2 -0
- package/build/tokens/components/dark/tabs.js +2 -0
- package/build/tokens/components/light/badge.d.ts +1 -1
- package/build/tokens/components/light/badge.js +1 -1
- package/build/tokens/components/light/button.d.ts +1 -0
- package/build/tokens/components/light/button.js +1 -0
- package/build/tokens/components/light/checkbox.d.ts +3 -3
- package/build/tokens/components/light/checkbox.js +3 -3
- package/build/tokens/components/light/icon-button.d.ts +1 -1
- package/build/tokens/components/light/icon-button.js +1 -1
- package/build/tokens/components/light/radio.d.ts +3 -3
- package/build/tokens/components/light/radio.js +3 -3
- package/build/tokens/components/light/tabs.d.ts +2 -0
- package/build/tokens/components/light/tabs.js +2 -0
- package/build/tokens/layout.d.ts +48 -30
- package/build/tokens/layout.js +24 -15
- package/build/tokens/semantic-dark.d.ts +21 -19
- package/build/tokens/semantic-dark.js +21 -19
- package/build/tokens/semantic-light.d.ts +17 -15
- package/build/tokens/semantic-light.js +17 -15
- package/build/types/values.d.ts +2 -1
- package/build/utils/formatThousands.d.ts +2 -0
- package/build/utils/formatThousands.js +16 -0
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.js +1 -0
- package/docs/components/AllComponents.web.tsx +97 -8
- package/docs/components/NextPrevPage.tsx +11 -3
- package/docs/components/UsageWrap.tsx +2 -2
- package/docs/heplers/addReactNativePrefix.ts +8 -0
- package/docs/heplers/index.ts +1 -0
- package/docs/theme-tokens.mdx +42 -0
- package/package.json +2 -3
- package/src/components/Badge/Badge.docs.mdx +7 -7
- package/src/components/Badge/Badge.props.ts +3 -2
- package/src/components/Badge/Badge.stories.tsx +81 -92
- package/src/components/Badge/Badge.tsx +101 -14
- package/src/components/Badge/BadgeIcon.tsx +5 -79
- package/src/components/Badge/BadgeText.tsx +7 -81
- package/src/components/Button/ButtonGroupRoot.tsx +12 -2
- package/src/components/Card/Card.docs.mdx +1 -1
- package/src/components/Card/Card.props.ts +2 -2
- package/src/components/CurrencyInput/CurrencyInput.docs.mdx +120 -0
- package/src/components/CurrencyInput/CurrencyInput.props.ts +19 -0
- package/src/components/CurrencyInput/CurrencyInput.stories.tsx +116 -0
- package/src/components/CurrencyInput/CurrencyInput.tsx +91 -0
- package/src/components/CurrencyInput/index.ts +1 -0
- package/src/components/DescriptionList/DescriptionList.context.ts +18 -0
- package/src/components/DescriptionList/DescriptionList.docs.mdx +98 -0
- package/src/components/DescriptionList/DescriptionList.props.ts +20 -0
- package/src/components/DescriptionList/DescriptionList.stories.tsx +154 -0
- package/src/components/DescriptionList/DescriptionList.tsx +64 -0
- package/src/components/DescriptionList/DescriptionListItem.props.ts +19 -0
- package/src/components/DescriptionList/DescriptionListItem.tsx +101 -0
- package/src/components/DescriptionList/index.ts +4 -0
- package/src/components/Divider/Divider.props.ts +2 -2
- package/src/components/Divider/Divider.stories.tsx +3 -3
- package/src/components/Divider/Divider.tsx +46 -0
- package/src/components/Flex/Flex.docs.mdx +4 -4
- package/src/components/Flex/Flex.props.ts +3 -2
- package/src/components/Flex/Flex.stories.tsx +1 -1
- package/src/components/Grid/Grid.docs.mdx +12 -12
- package/src/components/Grid/Grid.props.ts +2 -2
- package/src/components/Grid/Grid.stories.tsx +2 -2
- package/src/components/IconContainer/IconContainer.docs.mdx +90 -0
- package/src/components/IconContainer/IconContainer.props.ts +17 -0
- package/src/components/IconContainer/IconContainer.stories.tsx +130 -0
- package/src/components/IconContainer/IconContainer.tsx +180 -0
- package/src/components/IconContainer/index.tsx +2 -0
- package/src/components/Icons/CircleIcon.tsx +9 -11
- package/src/components/Input/Input.docs.mdx +3 -3
- package/src/components/Input/Input.props.ts +0 -20
- package/src/components/Input/Input.stories.tsx +0 -6
- package/src/components/Input/Input.tsx +2 -49
- package/src/components/Input/InputField.tsx +0 -7
- package/src/components/Modal/Modal.tsx +18 -0
- package/src/components/SectionHeader/SectionHeader.tsx +1 -0
- package/src/components/Tabs/Tab.props.ts +16 -0
- package/src/components/Tabs/Tab.tsx +113 -0
- package/src/components/Tabs/TabPanel.props.ts +10 -0
- package/src/components/Tabs/TabPanel.tsx +46 -0
- package/src/components/Tabs/Tabs.context.ts +26 -0
- package/src/components/Tabs/Tabs.docs.mdx +214 -0
- package/src/components/Tabs/Tabs.props.ts +21 -0
- package/src/components/Tabs/Tabs.stories.tsx +270 -0
- package/src/components/Tabs/Tabs.tsx +139 -0
- package/src/components/Tabs/TabsList.props.ts +8 -0
- package/src/components/Tabs/TabsList.tsx +194 -0
- package/src/components/Tabs/index.ts +8 -0
- package/src/components/index.ts +4 -0
- package/src/core/themes.ts +57 -1
- package/src/tokens/color.ts +38 -34
- package/src/tokens/components/dark/button.ts +1 -0
- package/src/tokens/components/dark/checkbox.ts +1 -1
- package/src/tokens/components/dark/icon-button.ts +3 -3
- package/src/tokens/components/dark/radio.ts +1 -1
- package/src/tokens/components/dark/tabs.ts +2 -0
- package/src/tokens/components/light/badge.ts +1 -1
- package/src/tokens/components/light/button.ts +1 -0
- package/src/tokens/components/light/checkbox.ts +3 -3
- package/src/tokens/components/light/icon-button.ts +1 -1
- package/src/tokens/components/light/radio.ts +3 -3
- package/src/tokens/components/light/tabs.ts +2 -0
- package/src/tokens/layout.ts +24 -15
- package/src/tokens/semantic-dark.ts +21 -19
- package/src/tokens/semantic-light.ts +17 -15
- package/src/types/values.ts +3 -1
- package/src/utils/formatThousands.ts +14 -0
- package/src/utils/index.ts +1 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import {
|
|
3
|
+
BroadbandMediumIcon,
|
|
4
|
+
ElectricityMediumIcon,
|
|
5
|
+
InsuranceMediumIcon,
|
|
6
|
+
MobileMediumIcon,
|
|
7
|
+
} from '@utilitywarehouse/hearth-react-native-icons';
|
|
8
|
+
import { useState } from 'react';
|
|
9
|
+
import { Platform } from 'react-native';
|
|
10
|
+
import { BodyText, Button, Flex, Tab, TabPanel, Tabs, TabsList } from '../';
|
|
11
|
+
|
|
12
|
+
const meta = {
|
|
13
|
+
title: 'Stories / Tabs',
|
|
14
|
+
component: Tabs,
|
|
15
|
+
args: {
|
|
16
|
+
size: 'md',
|
|
17
|
+
children: undefined,
|
|
18
|
+
},
|
|
19
|
+
argTypes: {
|
|
20
|
+
size: { control: 'radio', options: ['md', 'lg'] },
|
|
21
|
+
},
|
|
22
|
+
} satisfies Meta<typeof Tabs>;
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
type Story = StoryObj<typeof meta>;
|
|
26
|
+
|
|
27
|
+
export const Playground: Story = {
|
|
28
|
+
args: {
|
|
29
|
+
children: (
|
|
30
|
+
<>
|
|
31
|
+
<Tab value="account">Account</Tab>
|
|
32
|
+
<Tab value="billing">Billing</Tab>
|
|
33
|
+
<Tab value="usage">Usage</Tab>
|
|
34
|
+
<Tab value="settings">Settings</Tab>
|
|
35
|
+
</>
|
|
36
|
+
),
|
|
37
|
+
},
|
|
38
|
+
render: ({ children, ...args }) => (
|
|
39
|
+
<Tabs {...args} defaultValue="account">
|
|
40
|
+
<TabsList>{children}</TabsList>
|
|
41
|
+
<TabPanel value="account">
|
|
42
|
+
<BodyText>Account content</BodyText>
|
|
43
|
+
</TabPanel>
|
|
44
|
+
<TabPanel value="billing">
|
|
45
|
+
<BodyText>Billing content</BodyText>
|
|
46
|
+
</TabPanel>
|
|
47
|
+
<TabPanel value="usage">
|
|
48
|
+
<BodyText>Usage metrics</BodyText>
|
|
49
|
+
</TabPanel>
|
|
50
|
+
<TabPanel value="settings">
|
|
51
|
+
<BodyText>Settings content</BodyText>
|
|
52
|
+
</TabPanel>
|
|
53
|
+
</Tabs>
|
|
54
|
+
),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const Sizes: Story = {
|
|
58
|
+
args: { children: null },
|
|
59
|
+
render: () => (
|
|
60
|
+
<>
|
|
61
|
+
<Tabs size="md" defaultValue="a">
|
|
62
|
+
<TabsList>
|
|
63
|
+
<Tab value="a">Medium A</Tab>
|
|
64
|
+
<Tab value="b">Medium B</Tab>
|
|
65
|
+
<Tab value="c">Medium C</Tab>
|
|
66
|
+
</TabsList>
|
|
67
|
+
<TabPanel value="a">
|
|
68
|
+
<BodyText>Medium A panel</BodyText>
|
|
69
|
+
</TabPanel>
|
|
70
|
+
<TabPanel value="b">
|
|
71
|
+
<BodyText>Medium B panel</BodyText>
|
|
72
|
+
</TabPanel>
|
|
73
|
+
<TabPanel value="c">
|
|
74
|
+
<BodyText>Medium C panel</BodyText>
|
|
75
|
+
</TabPanel>
|
|
76
|
+
</Tabs>
|
|
77
|
+
<Tabs size="lg" defaultValue="a" style={{ marginTop: 24 }}>
|
|
78
|
+
<TabsList>
|
|
79
|
+
<Tab value="a">Large A</Tab>
|
|
80
|
+
<Tab value="b">Large B</Tab>
|
|
81
|
+
<Tab value="c">Large C</Tab>
|
|
82
|
+
</TabsList>
|
|
83
|
+
<TabPanel value="a">
|
|
84
|
+
<BodyText>Large A panel</BodyText>
|
|
85
|
+
</TabPanel>
|
|
86
|
+
<TabPanel value="b">
|
|
87
|
+
<BodyText>Large B panel</BodyText>
|
|
88
|
+
</TabPanel>
|
|
89
|
+
<TabPanel value="c">
|
|
90
|
+
<BodyText>Large C panel</BodyText>
|
|
91
|
+
</TabPanel>
|
|
92
|
+
</Tabs>
|
|
93
|
+
</>
|
|
94
|
+
),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const WithScrolling: Story = {
|
|
98
|
+
args: { children: null },
|
|
99
|
+
render: () => (
|
|
100
|
+
<Tabs defaultValue="one">
|
|
101
|
+
<TabsList>
|
|
102
|
+
<Tab value="one">One</Tab>
|
|
103
|
+
<Tab value="two">Two</Tab>
|
|
104
|
+
<Tab value="three">Three</Tab>
|
|
105
|
+
<Tab value="four">Four</Tab>
|
|
106
|
+
<Tab value="five">Five</Tab>
|
|
107
|
+
<Tab value="six">Six</Tab>
|
|
108
|
+
<Tab value="seven">Seven</Tab>
|
|
109
|
+
<Tab value="eight">Eight</Tab>
|
|
110
|
+
<Tab value="nine">Nine</Tab>
|
|
111
|
+
<Tab value="ten">Ten</Tab>
|
|
112
|
+
<Tab value="eleven">Eleven</Tab>
|
|
113
|
+
<Tab value="twelve">Twelve</Tab>
|
|
114
|
+
<Tab value="thirteen">Thirteen</Tab>
|
|
115
|
+
<Tab value="fourteen">Fourteen</Tab>
|
|
116
|
+
<Tab value="fifteen">Fifteen</Tab>
|
|
117
|
+
<Tab value="sixteen">Sixteen</Tab>
|
|
118
|
+
<Tab value="seventeen">Seventeen</Tab>
|
|
119
|
+
<Tab value="eighteen">Eighteen</Tab>
|
|
120
|
+
<Tab value="nineteen">Nineteen</Tab>
|
|
121
|
+
<Tab value="twenty">Twenty</Tab>
|
|
122
|
+
</TabsList>
|
|
123
|
+
<TabPanel value="one">
|
|
124
|
+
<BodyText>One panel</BodyText>
|
|
125
|
+
</TabPanel>
|
|
126
|
+
<TabPanel value="two">
|
|
127
|
+
<BodyText>Two panel</BodyText>
|
|
128
|
+
</TabPanel>
|
|
129
|
+
<TabPanel value="three">
|
|
130
|
+
<BodyText>Three panel</BodyText>
|
|
131
|
+
</TabPanel>
|
|
132
|
+
<TabPanel value="four">
|
|
133
|
+
<BodyText>Four panel</BodyText>
|
|
134
|
+
</TabPanel>
|
|
135
|
+
<TabPanel value="five">
|
|
136
|
+
<BodyText>Five panel</BodyText>
|
|
137
|
+
</TabPanel>
|
|
138
|
+
<TabPanel value="six">
|
|
139
|
+
<BodyText>Six panel</BodyText>
|
|
140
|
+
</TabPanel>
|
|
141
|
+
<TabPanel value="seven">
|
|
142
|
+
<BodyText>Seven panel</BodyText>
|
|
143
|
+
</TabPanel>
|
|
144
|
+
<TabPanel value="eight">
|
|
145
|
+
<BodyText>Eight panel</BodyText>
|
|
146
|
+
</TabPanel>
|
|
147
|
+
<TabPanel value="nine">
|
|
148
|
+
<BodyText>Nine panel</BodyText>
|
|
149
|
+
</TabPanel>
|
|
150
|
+
<TabPanel value="ten">
|
|
151
|
+
<BodyText>Ten panel</BodyText>
|
|
152
|
+
</TabPanel>
|
|
153
|
+
<TabPanel value="eleven">
|
|
154
|
+
<BodyText>Eleven panel</BodyText>
|
|
155
|
+
</TabPanel>
|
|
156
|
+
<TabPanel value="twelve">
|
|
157
|
+
<BodyText>Twelve panel</BodyText>
|
|
158
|
+
</TabPanel>
|
|
159
|
+
<TabPanel value="thirteen">
|
|
160
|
+
<BodyText>Thirteen panel</BodyText>
|
|
161
|
+
</TabPanel>
|
|
162
|
+
<TabPanel value="fourteen">
|
|
163
|
+
<BodyText>Fourteen panel</BodyText>
|
|
164
|
+
</TabPanel>
|
|
165
|
+
<TabPanel value="fifteen">
|
|
166
|
+
<BodyText>Fifteen panel</BodyText>
|
|
167
|
+
</TabPanel>
|
|
168
|
+
<TabPanel value="sixteen">
|
|
169
|
+
<BodyText>Sixteen panel</BodyText>
|
|
170
|
+
</TabPanel>
|
|
171
|
+
<TabPanel value="seventeen">
|
|
172
|
+
<BodyText>Seventeen panel</BodyText>
|
|
173
|
+
</TabPanel>
|
|
174
|
+
<TabPanel value="eighteen">
|
|
175
|
+
<BodyText>Eighteen panel</BodyText>
|
|
176
|
+
</TabPanel>
|
|
177
|
+
<TabPanel value="nineteen">
|
|
178
|
+
<BodyText>Nineteen panel</BodyText>
|
|
179
|
+
</TabPanel>
|
|
180
|
+
<TabPanel value="twenty">
|
|
181
|
+
<BodyText>Twenty panel</BodyText>
|
|
182
|
+
</TabPanel>
|
|
183
|
+
</Tabs>
|
|
184
|
+
),
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export const WithIcons: Story = {
|
|
188
|
+
args: { children: null },
|
|
189
|
+
render: () => (
|
|
190
|
+
<Tabs defaultValue="one">
|
|
191
|
+
<TabsList>
|
|
192
|
+
<Tab value="one" icon={ElectricityMediumIcon}>
|
|
193
|
+
Energy
|
|
194
|
+
</Tab>
|
|
195
|
+
<Tab value="two" icon={BroadbandMediumIcon}>
|
|
196
|
+
Broadband
|
|
197
|
+
</Tab>
|
|
198
|
+
<Tab value="three" icon={MobileMediumIcon}>
|
|
199
|
+
Mobile
|
|
200
|
+
</Tab>
|
|
201
|
+
<Tab value="four" icon={InsuranceMediumIcon}>
|
|
202
|
+
Insurance
|
|
203
|
+
</Tab>
|
|
204
|
+
</TabsList>
|
|
205
|
+
<TabPanel value="one">
|
|
206
|
+
<BodyText>One panel</BodyText>
|
|
207
|
+
</TabPanel>
|
|
208
|
+
<TabPanel value="two">
|
|
209
|
+
<BodyText>Two panel</BodyText>
|
|
210
|
+
</TabPanel>
|
|
211
|
+
<TabPanel value="three">
|
|
212
|
+
<BodyText>Three panel</BodyText>
|
|
213
|
+
</TabPanel>
|
|
214
|
+
<TabPanel value="four">
|
|
215
|
+
<BodyText>Four panel</BodyText>
|
|
216
|
+
</TabPanel>
|
|
217
|
+
</Tabs>
|
|
218
|
+
),
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export const Disabled: Story = {
|
|
222
|
+
args: { children: null },
|
|
223
|
+
render: () => (
|
|
224
|
+
<Tabs defaultValue="one" disabled>
|
|
225
|
+
<TabsList>
|
|
226
|
+
<Tab value="one">One</Tab>
|
|
227
|
+
<Tab value="two">Two</Tab>
|
|
228
|
+
<Tab value="three">Three</Tab>
|
|
229
|
+
</TabsList>
|
|
230
|
+
<TabPanel value="one">One panel</TabPanel>
|
|
231
|
+
<TabPanel value="two">Two panel</TabPanel>
|
|
232
|
+
<TabPanel value="three">Three panel</TabPanel>
|
|
233
|
+
</Tabs>
|
|
234
|
+
),
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export const Controlled: Story = {
|
|
238
|
+
args: { children: null },
|
|
239
|
+
render: () => {
|
|
240
|
+
const [value, setValue] = useState('account');
|
|
241
|
+
const nextTab = () => {
|
|
242
|
+
const tabs = ['account', 'billing', 'usage'];
|
|
243
|
+
const currentIndex = tabs.indexOf(value);
|
|
244
|
+
const nextIndex = (currentIndex + 1) % tabs.length;
|
|
245
|
+
setValue(tabs[nextIndex]);
|
|
246
|
+
};
|
|
247
|
+
return (
|
|
248
|
+
<Flex align={Platform.OS === 'web' ? 'flex-start' : 'stretch'} space="lg">
|
|
249
|
+
<Tabs value={value}>
|
|
250
|
+
<TabsList>
|
|
251
|
+
<Tab value="account">Account</Tab>
|
|
252
|
+
<Tab value="billing">Billing</Tab>
|
|
253
|
+
<Tab value="usage">Usage</Tab>
|
|
254
|
+
</TabsList>
|
|
255
|
+
<TabPanel value="account">
|
|
256
|
+
<BodyText>Account content</BodyText>
|
|
257
|
+
</TabPanel>
|
|
258
|
+
<TabPanel value="billing">
|
|
259
|
+
<BodyText>Billing content</BodyText>
|
|
260
|
+
</TabPanel>
|
|
261
|
+
<TabPanel value="usage">
|
|
262
|
+
<BodyText>Usage metrics content</BodyText>
|
|
263
|
+
</TabPanel>
|
|
264
|
+
</Tabs>
|
|
265
|
+
|
|
266
|
+
<Button onPress={nextTab}>Next Tab</Button>
|
|
267
|
+
</Flex>
|
|
268
|
+
);
|
|
269
|
+
},
|
|
270
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Children, isValidElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { Easing, useSharedValue, withTiming } from 'react-native-reanimated';
|
|
4
|
+
import { TabsContext } from './Tabs.context';
|
|
5
|
+
import type TabsProps from './Tabs.props';
|
|
6
|
+
|
|
7
|
+
const Tabs = ({
|
|
8
|
+
value: controlledValue,
|
|
9
|
+
defaultValue,
|
|
10
|
+
onValueChange,
|
|
11
|
+
size = 'md',
|
|
12
|
+
disabled,
|
|
13
|
+
children,
|
|
14
|
+
withPanels,
|
|
15
|
+
...props
|
|
16
|
+
}: TabsProps) => {
|
|
17
|
+
// Collect child tab values
|
|
18
|
+
const tabValues = useMemo(() => {
|
|
19
|
+
const vals: string[] = [];
|
|
20
|
+
const walk = (node: any) => {
|
|
21
|
+
Children.forEach(node, child => {
|
|
22
|
+
if (!isValidElement(child)) return;
|
|
23
|
+
const props: any = child.props;
|
|
24
|
+
const type: any = child.type;
|
|
25
|
+
if (type?.displayName === 'Tab' && typeof props?.value === 'string') {
|
|
26
|
+
vals.push(props.value);
|
|
27
|
+
}
|
|
28
|
+
if (props?.children) walk(props.children);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
walk(children);
|
|
32
|
+
return vals;
|
|
33
|
+
}, [children]);
|
|
34
|
+
|
|
35
|
+
const getInitial = () => {
|
|
36
|
+
if (controlledValue !== undefined) return controlledValue;
|
|
37
|
+
if (defaultValue) return defaultValue;
|
|
38
|
+
return tabValues[0];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const [uncontrolled, setUncontrolled] = useState<string | undefined>(getInitial);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (controlledValue !== undefined) setUncontrolled(controlledValue);
|
|
45
|
+
}, [controlledValue]);
|
|
46
|
+
|
|
47
|
+
// Ensure value remains valid if tabs change
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
setUncontrolled(prev => {
|
|
50
|
+
if (!prev) return tabValues[0];
|
|
51
|
+
if (!tabValues.includes(prev)) return tabValues[0];
|
|
52
|
+
return prev;
|
|
53
|
+
});
|
|
54
|
+
}, [tabValues.join('|')]);
|
|
55
|
+
|
|
56
|
+
const currentValue = controlledValue !== undefined ? controlledValue : uncontrolled;
|
|
57
|
+
|
|
58
|
+
const select = useCallback(
|
|
59
|
+
(val: string) => {
|
|
60
|
+
if (disabled) return;
|
|
61
|
+
if (controlledValue === undefined) setUncontrolled(val);
|
|
62
|
+
onValueChange?.(val);
|
|
63
|
+
},
|
|
64
|
+
[controlledValue, disabled, onValueChange]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Indicator shared values (declared early so registerTabLayout can reference)
|
|
68
|
+
const indicatorX = useSharedValue(0);
|
|
69
|
+
const indicatorSize = useSharedValue(0);
|
|
70
|
+
|
|
71
|
+
// Layout registry for animated indicator
|
|
72
|
+
const layoutsRef = useRef<Map<string, { x: number; y: number; width: number; height: number }>>(
|
|
73
|
+
new Map()
|
|
74
|
+
);
|
|
75
|
+
const prevValueRef = useRef<string | undefined>(undefined);
|
|
76
|
+
const initialisedRef = useRef(false);
|
|
77
|
+
const registerTabLayout = useCallback(
|
|
78
|
+
(value: string, layout: { x: number; y: number; width: number; height: number }) => {
|
|
79
|
+
layoutsRef.current.set(value, layout);
|
|
80
|
+
const active = controlledValue !== undefined ? controlledValue : uncontrolled;
|
|
81
|
+
if (!active) return;
|
|
82
|
+
// Initial active tab: seed indicator & prevValue so first change animates
|
|
83
|
+
if (!initialisedRef.current && value === active) {
|
|
84
|
+
indicatorX.value = layout.x;
|
|
85
|
+
indicatorSize.value = layout.width;
|
|
86
|
+
prevValueRef.current = active;
|
|
87
|
+
initialisedRef.current = true;
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
[controlledValue, uncontrolled, indicatorX, indicatorSize]
|
|
91
|
+
);
|
|
92
|
+
const getTabLayout = useCallback((v: string) => layoutsRef.current.get(v), []);
|
|
93
|
+
|
|
94
|
+
const contextValue = useMemo(
|
|
95
|
+
() => ({
|
|
96
|
+
value: currentValue,
|
|
97
|
+
size,
|
|
98
|
+
select,
|
|
99
|
+
disabled,
|
|
100
|
+
registerTabLayout,
|
|
101
|
+
getTabLayout,
|
|
102
|
+
indicatorXSV: indicatorX,
|
|
103
|
+
indicatorSizeSV: indicatorSize,
|
|
104
|
+
}),
|
|
105
|
+
[
|
|
106
|
+
currentValue,
|
|
107
|
+
size,
|
|
108
|
+
select,
|
|
109
|
+
disabled,
|
|
110
|
+
registerTabLayout,
|
|
111
|
+
getTabLayout,
|
|
112
|
+
tabValues,
|
|
113
|
+
indicatorX,
|
|
114
|
+
indicatorSize,
|
|
115
|
+
]
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Animate indicator only on value changes after initialisation
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (!currentValue || !initialisedRef.current) return;
|
|
121
|
+
if (prevValueRef.current === undefined || prevValueRef.current === currentValue) return;
|
|
122
|
+
const layout = getTabLayout(currentValue);
|
|
123
|
+
if (!layout) return;
|
|
124
|
+
const cfg = { duration: 250, easing: Easing.out(Easing.ease) } as const;
|
|
125
|
+
indicatorX.value = withTiming(layout.x, cfg);
|
|
126
|
+
indicatorSize.value = withTiming(layout.width, cfg);
|
|
127
|
+
prevValueRef.current = currentValue;
|
|
128
|
+
}, [currentValue, getTabLayout, indicatorX, indicatorSize, tabValues.join('|')]);
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<TabsContext.Provider value={contextValue}>
|
|
132
|
+
<View {...props}>{children}</View>
|
|
133
|
+
</TabsContext.Provider>
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
Tabs.displayName = 'Tabs';
|
|
138
|
+
|
|
139
|
+
export default Tabs;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChevronLeftSmallIcon,
|
|
3
|
+
ChevronRightSmallIcon,
|
|
4
|
+
} from '@utilitywarehouse/hearth-react-native-icons';
|
|
5
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
6
|
+
import { LayoutChangeEvent, Platform, View } from 'react-native';
|
|
7
|
+
import Animated, {
|
|
8
|
+
runOnJS,
|
|
9
|
+
useAnimatedScrollHandler,
|
|
10
|
+
useAnimatedStyle,
|
|
11
|
+
useSharedValue,
|
|
12
|
+
} from 'react-native-reanimated';
|
|
13
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
14
|
+
import { UnstyledIconButton } from '../UnstyledIconButton';
|
|
15
|
+
import { useTabsContext } from './Tabs.context';
|
|
16
|
+
import type TabsListProps from './TabsList.props';
|
|
17
|
+
|
|
18
|
+
const Indicator = Animated.createAnimatedComponent(View);
|
|
19
|
+
|
|
20
|
+
const SCROLL_STEP_RATIO = 0.6;
|
|
21
|
+
const SCROLL_BUTTON_HITSLOP = { top: 10, bottom: 10, left: 10, right: 10 } as const;
|
|
22
|
+
|
|
23
|
+
const TabsList = ({ children, style, ...rest }: TabsListProps) => {
|
|
24
|
+
const { indicatorSizeSV, indicatorXSV } = useTabsContext();
|
|
25
|
+
|
|
26
|
+
const scrollRef = useRef<Animated.ScrollView | null>(null);
|
|
27
|
+
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
|
28
|
+
const [canScrollRight, setCanScrollRight] = useState(false);
|
|
29
|
+
const containerWidthRef = useRef(0);
|
|
30
|
+
const contentWidthRef = useRef(0);
|
|
31
|
+
const scrollX = useSharedValue(0);
|
|
32
|
+
|
|
33
|
+
const indicatorStyle = useAnimatedStyle(() => ({
|
|
34
|
+
transform: [{ translateX: indicatorXSV.value }],
|
|
35
|
+
width: indicatorSizeSV.value,
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const updateScrollState = useCallback(() => {
|
|
39
|
+
const cw = containerWidthRef.current;
|
|
40
|
+
const contentW = contentWidthRef.current;
|
|
41
|
+
const x = scrollX.value;
|
|
42
|
+
const overflow = contentW > cw + 1;
|
|
43
|
+
setCanScrollLeft(overflow && x > 0);
|
|
44
|
+
setCanScrollRight(overflow && x + cw < contentW - 1);
|
|
45
|
+
}, [scrollX]);
|
|
46
|
+
|
|
47
|
+
const onLayoutContainer = (e: LayoutChangeEvent) => {
|
|
48
|
+
containerWidthRef.current = e.nativeEvent.layout.width;
|
|
49
|
+
updateScrollState();
|
|
50
|
+
};
|
|
51
|
+
const onContentSizeChange = (w: number) => {
|
|
52
|
+
contentWidthRef.current = w;
|
|
53
|
+
updateScrollState();
|
|
54
|
+
};
|
|
55
|
+
const onScrollHandler = useAnimatedScrollHandler({
|
|
56
|
+
onScroll: (e: { contentOffset: { x: number } }) => {
|
|
57
|
+
scrollX.value = e.contentOffset.x;
|
|
58
|
+
runOnJS(updateScrollState)();
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const scrollBy = (direction: 1 | -1) => {
|
|
63
|
+
const viewW = containerWidthRef.current;
|
|
64
|
+
const step = viewW * SCROLL_STEP_RATIO;
|
|
65
|
+
const current = scrollX.value;
|
|
66
|
+
const max = Math.max(0, contentWidthRef.current - viewW);
|
|
67
|
+
const target = Math.max(0, Math.min(current + direction * step, max));
|
|
68
|
+
scrollRef.current?.scrollTo({ x: target, animated: true });
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (Platform.OS === 'web') {
|
|
73
|
+
const handler = () => updateScrollState();
|
|
74
|
+
window.addEventListener('resize', handler);
|
|
75
|
+
return () => window.removeEventListener('resize', handler);
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}, [updateScrollState]);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<View style={[styles.wrapper, style]} {...rest} accessibilityRole="tablist">
|
|
82
|
+
{canScrollLeft && (
|
|
83
|
+
<View
|
|
84
|
+
style={[styles.iconButtonWrap, styles.scrollButtonLeft]}
|
|
85
|
+
accessibilityElementsHidden
|
|
86
|
+
importantForAccessibility="no-hide-descendants"
|
|
87
|
+
{...(Platform.OS === 'web' ? ({ 'aria-hidden': true } as any) : {})}
|
|
88
|
+
>
|
|
89
|
+
<UnstyledIconButton
|
|
90
|
+
accessibilityLabel={undefined}
|
|
91
|
+
icon={ChevronLeftSmallIcon}
|
|
92
|
+
onPress={() => scrollBy(-1)}
|
|
93
|
+
style={styles.iconButton}
|
|
94
|
+
accessibilityElementsHidden
|
|
95
|
+
importantForAccessibility="no-hide-descendants"
|
|
96
|
+
hitSlop={SCROLL_BUTTON_HITSLOP}
|
|
97
|
+
{...(Platform.OS === 'web' ? ({ 'aria-hidden': true, tabIndex: -1 } as any) : {})}
|
|
98
|
+
/>
|
|
99
|
+
</View>
|
|
100
|
+
)}
|
|
101
|
+
<Animated.ScrollView
|
|
102
|
+
ref={scrollRef}
|
|
103
|
+
horizontal
|
|
104
|
+
showsHorizontalScrollIndicator={false}
|
|
105
|
+
onLayout={onLayoutContainer}
|
|
106
|
+
onContentSizeChange={onContentSizeChange}
|
|
107
|
+
onScroll={onScrollHandler}
|
|
108
|
+
scrollEventThrottle={16}
|
|
109
|
+
bounces={false}
|
|
110
|
+
contentContainerStyle={styles.scrollContent}
|
|
111
|
+
>
|
|
112
|
+
<View style={styles.container}>
|
|
113
|
+
{children}
|
|
114
|
+
<Indicator
|
|
115
|
+
accessibilityElementsHidden
|
|
116
|
+
importantForAccessibility="no-hide-descendants"
|
|
117
|
+
style={[styles.indicator, indicatorStyle]}
|
|
118
|
+
/>
|
|
119
|
+
</View>
|
|
120
|
+
</Animated.ScrollView>
|
|
121
|
+
{canScrollRight && (
|
|
122
|
+
<View
|
|
123
|
+
style={[styles.iconButtonWrap, styles.scrollButtonRight]}
|
|
124
|
+
accessibilityElementsHidden
|
|
125
|
+
importantForAccessibility="no-hide-descendants"
|
|
126
|
+
{...(Platform.OS === 'web' ? ({ 'aria-hidden': true } as any) : {})}
|
|
127
|
+
>
|
|
128
|
+
<UnstyledIconButton
|
|
129
|
+
accessibilityLabel={undefined}
|
|
130
|
+
icon={ChevronRightSmallIcon}
|
|
131
|
+
onPress={() => scrollBy(1)}
|
|
132
|
+
style={styles.iconButton}
|
|
133
|
+
accessibilityElementsHidden
|
|
134
|
+
importantForAccessibility="no-hide-descendants"
|
|
135
|
+
hitSlop={SCROLL_BUTTON_HITSLOP}
|
|
136
|
+
{...(Platform.OS === 'web' ? ({ 'aria-hidden': true, tabIndex: -1 } as any) : {})}
|
|
137
|
+
/>
|
|
138
|
+
</View>
|
|
139
|
+
)}
|
|
140
|
+
</View>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
TabsList.displayName = 'TabsList';
|
|
145
|
+
|
|
146
|
+
const styles = StyleSheet.create(theme => ({
|
|
147
|
+
wrapper: {
|
|
148
|
+
position: 'relative',
|
|
149
|
+
width: '100%',
|
|
150
|
+
borderBottomWidth: theme.components.tabs.divider.borderWidth,
|
|
151
|
+
borderColor: theme.components.tabs.divider.color,
|
|
152
|
+
},
|
|
153
|
+
scrollContent: {
|
|
154
|
+
paddingBottom: 0,
|
|
155
|
+
},
|
|
156
|
+
container: {
|
|
157
|
+
position: 'relative',
|
|
158
|
+
flexDirection: 'row',
|
|
159
|
+
gap: theme.components.tabs.gap,
|
|
160
|
+
},
|
|
161
|
+
indicator: {
|
|
162
|
+
position: 'absolute',
|
|
163
|
+
height: 6,
|
|
164
|
+
borderTopLeftRadius: theme.components.tabs.item.selected.borderTopRadius,
|
|
165
|
+
borderTopRightRadius: theme.components.tabs.item.selected.borderTopRadius,
|
|
166
|
+
borderBottomLeftRadius: theme.components.tabs.item.selected.borderBottomRadius,
|
|
167
|
+
borderBottomRightRadius: theme.components.tabs.item.selected.borderBottomRadius,
|
|
168
|
+
backgroundColor: theme.color.surface.brand.default,
|
|
169
|
+
bottom: 0,
|
|
170
|
+
left: 0,
|
|
171
|
+
},
|
|
172
|
+
scrollButtonLeft: {
|
|
173
|
+
position: 'absolute',
|
|
174
|
+
left: 0,
|
|
175
|
+
top: '50%',
|
|
176
|
+
transform: [{ translateY: -theme.space[150] }],
|
|
177
|
+
zIndex: 10,
|
|
178
|
+
},
|
|
179
|
+
scrollButtonRight: {
|
|
180
|
+
position: 'absolute',
|
|
181
|
+
right: 0,
|
|
182
|
+
top: '50%',
|
|
183
|
+
transform: [{ translateY: -theme.space[150] }],
|
|
184
|
+
zIndex: 10,
|
|
185
|
+
},
|
|
186
|
+
iconButtonWrap: {},
|
|
187
|
+
iconButton: {
|
|
188
|
+
backgroundColor: theme.color.surface.neutral.subtle,
|
|
189
|
+
width: theme.space[300],
|
|
190
|
+
height: theme.space[300],
|
|
191
|
+
},
|
|
192
|
+
}));
|
|
193
|
+
|
|
194
|
+
export default TabsList;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { default as Tab } from './Tab';
|
|
2
|
+
export type { TabProps } from './Tab.props';
|
|
3
|
+
export { default as TabPanel } from './TabPanel';
|
|
4
|
+
export type { TabPanelProps } from './TabPanel.props';
|
|
5
|
+
export { default as Tabs } from './Tabs';
|
|
6
|
+
export type { TabsProps } from './Tabs.props';
|
|
7
|
+
export { default as TabsList } from './TabsList';
|
|
8
|
+
export type { TabsListProps } from './TabsList.props';
|
package/src/components/index.ts
CHANGED
|
@@ -9,6 +9,8 @@ export * from './Button';
|
|
|
9
9
|
export * from './Card';
|
|
10
10
|
export * from './Center';
|
|
11
11
|
export * from './Checkbox';
|
|
12
|
+
export * from './CurrencyInput';
|
|
13
|
+
export * from './DescriptionList';
|
|
12
14
|
export * from './DetailText';
|
|
13
15
|
export * from './Divider';
|
|
14
16
|
export * from './Flex';
|
|
@@ -19,6 +21,7 @@ export * from './Helper';
|
|
|
19
21
|
export * from './HTMLElements';
|
|
20
22
|
export * from './Icon';
|
|
21
23
|
export * from './IconButton';
|
|
24
|
+
export * from './IconContainer';
|
|
22
25
|
export * from './InlineLink';
|
|
23
26
|
export * from './Input';
|
|
24
27
|
export * from './Label';
|
|
@@ -32,6 +35,7 @@ export * from './Select';
|
|
|
32
35
|
export * from './Skeleton';
|
|
33
36
|
export * from './Spinner';
|
|
34
37
|
export * from './Switch';
|
|
38
|
+
export * from './Tabs';
|
|
35
39
|
export * from './Textarea';
|
|
36
40
|
export * from './ToggleButtonCard';
|
|
37
41
|
|