@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.
Files changed (187) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/CHANGELOG.md +22 -0
  4. package/build/components/Badge/Badge.js +101 -14
  5. package/build/components/Badge/Badge.props.d.ts +2 -2
  6. package/build/components/Badge/BadgeIcon.js +5 -79
  7. package/build/components/Badge/BadgeText.js +7 -81
  8. package/build/components/Button/Button.d.ts +2 -2
  9. package/build/components/Button/ButtonGroupRoot.d.ts +3 -2
  10. package/build/components/Button/ButtonGroupRoot.js +9 -0
  11. package/build/components/Card/Card.props.d.ts +2 -2
  12. package/build/components/CurrencyInput/CurrencyInput.d.ts +6 -0
  13. package/build/components/CurrencyInput/CurrencyInput.js +47 -0
  14. package/build/components/CurrencyInput/CurrencyInput.props.d.ts +14 -0
  15. package/build/components/CurrencyInput/CurrencyInput.props.js +1 -0
  16. package/build/components/CurrencyInput/index.d.ts +1 -0
  17. package/build/components/CurrencyInput/index.js +1 -0
  18. package/build/components/DescriptionList/DescriptionList.context.d.ts +6 -0
  19. package/build/components/DescriptionList/DescriptionList.context.js +9 -0
  20. package/build/components/DescriptionList/DescriptionList.d.ts +6 -0
  21. package/build/components/DescriptionList/DescriptionList.js +25 -0
  22. package/build/components/DescriptionList/DescriptionList.props.d.ts +18 -0
  23. package/build/components/DescriptionList/DescriptionList.props.js +1 -0
  24. package/build/components/DescriptionList/DescriptionListItem.d.ts +6 -0
  25. package/build/components/DescriptionList/DescriptionListItem.js +49 -0
  26. package/build/components/DescriptionList/DescriptionListItem.props.d.ts +17 -0
  27. package/build/components/DescriptionList/DescriptionListItem.props.js +1 -0
  28. package/build/components/DescriptionList/index.d.ts +4 -0
  29. package/build/components/DescriptionList/index.js +2 -0
  30. package/build/components/Divider/Divider.js +46 -0
  31. package/build/components/Divider/Divider.props.d.ts +2 -2
  32. package/build/components/Flex/Flex.props.d.ts +3 -2
  33. package/build/components/Grid/Grid.props.d.ts +2 -2
  34. package/build/components/IconContainer/IconContainer.d.ts +5 -0
  35. package/build/components/IconContainer/IconContainer.js +161 -0
  36. package/build/components/IconContainer/IconContainer.props.d.ts +15 -0
  37. package/build/components/IconContainer/IconContainer.props.js +1 -0
  38. package/build/components/IconContainer/index.d.ts +2 -0
  39. package/build/components/IconContainer/index.js +1 -0
  40. package/build/components/Icons/CircleIcon.js +3 -3
  41. package/build/components/Input/Input.js +2 -34
  42. package/build/components/Input/Input.props.d.ts +1 -17
  43. package/build/components/Input/InputField.js +0 -7
  44. package/build/components/Modal/Modal.js +17 -1
  45. package/build/components/SectionHeader/SectionHeader.js +1 -0
  46. package/build/components/Tabs/Tab.d.ts +18 -0
  47. package/build/components/Tabs/Tab.js +74 -0
  48. package/build/components/Tabs/Tab.props.d.ts +14 -0
  49. package/build/components/Tabs/Tab.props.js +1 -0
  50. package/build/components/Tabs/TabPanel.d.ts +3 -0
  51. package/build/components/Tabs/TabPanel.js +34 -0
  52. package/build/components/Tabs/TabPanel.props.d.ts +8 -0
  53. package/build/components/Tabs/TabPanel.props.js +1 -0
  54. package/build/components/Tabs/Tabs.context.d.ts +23 -0
  55. package/build/components/Tabs/Tabs.context.js +8 -0
  56. package/build/components/Tabs/Tabs.d.ts +6 -0
  57. package/build/components/Tabs/Tabs.js +114 -0
  58. package/build/components/Tabs/Tabs.props.d.ts +19 -0
  59. package/build/components/Tabs/Tabs.props.js +1 -0
  60. package/build/components/Tabs/TabsList.d.ts +6 -0
  61. package/build/components/Tabs/TabsList.js +112 -0
  62. package/build/components/Tabs/TabsList.props.d.ts +6 -0
  63. package/build/components/Tabs/TabsList.props.js +1 -0
  64. package/build/components/Tabs/index.d.ts +8 -0
  65. package/build/components/Tabs/index.js +4 -0
  66. package/build/components/index.d.ts +4 -0
  67. package/build/components/index.js +4 -0
  68. package/build/core/themes.d.ts +416 -148
  69. package/build/core/themes.js +57 -1
  70. package/build/tokens/color.d.ts +76 -68
  71. package/build/tokens/color.js +38 -34
  72. package/build/tokens/components/dark/button.d.ts +1 -0
  73. package/build/tokens/components/dark/button.js +1 -0
  74. package/build/tokens/components/dark/checkbox.d.ts +1 -1
  75. package/build/tokens/components/dark/checkbox.js +1 -1
  76. package/build/tokens/components/dark/icon-button.d.ts +3 -3
  77. package/build/tokens/components/dark/icon-button.js +3 -3
  78. package/build/tokens/components/dark/radio.d.ts +1 -1
  79. package/build/tokens/components/dark/radio.js +1 -1
  80. package/build/tokens/components/dark/tabs.d.ts +2 -0
  81. package/build/tokens/components/dark/tabs.js +2 -0
  82. package/build/tokens/components/light/badge.d.ts +1 -1
  83. package/build/tokens/components/light/badge.js +1 -1
  84. package/build/tokens/components/light/button.d.ts +1 -0
  85. package/build/tokens/components/light/button.js +1 -0
  86. package/build/tokens/components/light/checkbox.d.ts +3 -3
  87. package/build/tokens/components/light/checkbox.js +3 -3
  88. package/build/tokens/components/light/icon-button.d.ts +1 -1
  89. package/build/tokens/components/light/icon-button.js +1 -1
  90. package/build/tokens/components/light/radio.d.ts +3 -3
  91. package/build/tokens/components/light/radio.js +3 -3
  92. package/build/tokens/components/light/tabs.d.ts +2 -0
  93. package/build/tokens/components/light/tabs.js +2 -0
  94. package/build/tokens/layout.d.ts +48 -30
  95. package/build/tokens/layout.js +24 -15
  96. package/build/tokens/semantic-dark.d.ts +21 -19
  97. package/build/tokens/semantic-dark.js +21 -19
  98. package/build/tokens/semantic-light.d.ts +17 -15
  99. package/build/tokens/semantic-light.js +17 -15
  100. package/build/types/values.d.ts +2 -1
  101. package/build/utils/formatThousands.d.ts +2 -0
  102. package/build/utils/formatThousands.js +16 -0
  103. package/build/utils/index.d.ts +1 -0
  104. package/build/utils/index.js +1 -0
  105. package/docs/components/AllComponents.web.tsx +97 -8
  106. package/docs/components/NextPrevPage.tsx +11 -3
  107. package/docs/components/UsageWrap.tsx +2 -2
  108. package/docs/heplers/addReactNativePrefix.ts +8 -0
  109. package/docs/heplers/index.ts +1 -0
  110. package/docs/theme-tokens.mdx +42 -0
  111. package/package.json +2 -3
  112. package/src/components/Badge/Badge.docs.mdx +7 -7
  113. package/src/components/Badge/Badge.props.ts +3 -2
  114. package/src/components/Badge/Badge.stories.tsx +81 -92
  115. package/src/components/Badge/Badge.tsx +101 -14
  116. package/src/components/Badge/BadgeIcon.tsx +5 -79
  117. package/src/components/Badge/BadgeText.tsx +7 -81
  118. package/src/components/Button/ButtonGroupRoot.tsx +12 -2
  119. package/src/components/Card/Card.docs.mdx +1 -1
  120. package/src/components/Card/Card.props.ts +2 -2
  121. package/src/components/CurrencyInput/CurrencyInput.docs.mdx +120 -0
  122. package/src/components/CurrencyInput/CurrencyInput.props.ts +19 -0
  123. package/src/components/CurrencyInput/CurrencyInput.stories.tsx +116 -0
  124. package/src/components/CurrencyInput/CurrencyInput.tsx +91 -0
  125. package/src/components/CurrencyInput/index.ts +1 -0
  126. package/src/components/DescriptionList/DescriptionList.context.ts +18 -0
  127. package/src/components/DescriptionList/DescriptionList.docs.mdx +98 -0
  128. package/src/components/DescriptionList/DescriptionList.props.ts +20 -0
  129. package/src/components/DescriptionList/DescriptionList.stories.tsx +154 -0
  130. package/src/components/DescriptionList/DescriptionList.tsx +64 -0
  131. package/src/components/DescriptionList/DescriptionListItem.props.ts +19 -0
  132. package/src/components/DescriptionList/DescriptionListItem.tsx +101 -0
  133. package/src/components/DescriptionList/index.ts +4 -0
  134. package/src/components/Divider/Divider.props.ts +2 -2
  135. package/src/components/Divider/Divider.stories.tsx +3 -3
  136. package/src/components/Divider/Divider.tsx +46 -0
  137. package/src/components/Flex/Flex.docs.mdx +4 -4
  138. package/src/components/Flex/Flex.props.ts +3 -2
  139. package/src/components/Flex/Flex.stories.tsx +1 -1
  140. package/src/components/Grid/Grid.docs.mdx +12 -12
  141. package/src/components/Grid/Grid.props.ts +2 -2
  142. package/src/components/Grid/Grid.stories.tsx +2 -2
  143. package/src/components/IconContainer/IconContainer.docs.mdx +90 -0
  144. package/src/components/IconContainer/IconContainer.props.ts +17 -0
  145. package/src/components/IconContainer/IconContainer.stories.tsx +130 -0
  146. package/src/components/IconContainer/IconContainer.tsx +180 -0
  147. package/src/components/IconContainer/index.tsx +2 -0
  148. package/src/components/Icons/CircleIcon.tsx +9 -11
  149. package/src/components/Input/Input.docs.mdx +3 -3
  150. package/src/components/Input/Input.props.ts +0 -20
  151. package/src/components/Input/Input.stories.tsx +0 -6
  152. package/src/components/Input/Input.tsx +2 -49
  153. package/src/components/Input/InputField.tsx +0 -7
  154. package/src/components/Modal/Modal.tsx +18 -0
  155. package/src/components/SectionHeader/SectionHeader.tsx +1 -0
  156. package/src/components/Tabs/Tab.props.ts +16 -0
  157. package/src/components/Tabs/Tab.tsx +113 -0
  158. package/src/components/Tabs/TabPanel.props.ts +10 -0
  159. package/src/components/Tabs/TabPanel.tsx +46 -0
  160. package/src/components/Tabs/Tabs.context.ts +26 -0
  161. package/src/components/Tabs/Tabs.docs.mdx +214 -0
  162. package/src/components/Tabs/Tabs.props.ts +21 -0
  163. package/src/components/Tabs/Tabs.stories.tsx +270 -0
  164. package/src/components/Tabs/Tabs.tsx +139 -0
  165. package/src/components/Tabs/TabsList.props.ts +8 -0
  166. package/src/components/Tabs/TabsList.tsx +194 -0
  167. package/src/components/Tabs/index.ts +8 -0
  168. package/src/components/index.ts +4 -0
  169. package/src/core/themes.ts +57 -1
  170. package/src/tokens/color.ts +38 -34
  171. package/src/tokens/components/dark/button.ts +1 -0
  172. package/src/tokens/components/dark/checkbox.ts +1 -1
  173. package/src/tokens/components/dark/icon-button.ts +3 -3
  174. package/src/tokens/components/dark/radio.ts +1 -1
  175. package/src/tokens/components/dark/tabs.ts +2 -0
  176. package/src/tokens/components/light/badge.ts +1 -1
  177. package/src/tokens/components/light/button.ts +1 -0
  178. package/src/tokens/components/light/checkbox.ts +3 -3
  179. package/src/tokens/components/light/icon-button.ts +1 -1
  180. package/src/tokens/components/light/radio.ts +3 -3
  181. package/src/tokens/components/light/tabs.ts +2 -0
  182. package/src/tokens/layout.ts +24 -15
  183. package/src/tokens/semantic-dark.ts +21 -19
  184. package/src/tokens/semantic-light.ts +17 -15
  185. package/src/types/values.ts +3 -1
  186. package/src/utils/formatThousands.ts +14 -0
  187. package/src/utils/index.ts +1 -0
@@ -0,0 +1,113 @@
1
+ import { useCallback, useRef } from 'react';
2
+ import { Platform, Pressable, View } from 'react-native';
3
+ import { StyleSheet } from 'react-native-unistyles';
4
+ import { DetailText } from '../DetailText';
5
+ import { Icon } from '../Icon';
6
+ import type TabProps from './Tab.props';
7
+ import { useTabsContext } from './Tabs.context';
8
+
9
+ import { createPressable } from '@gluestack-ui/pressable';
10
+
11
+ const Tab = ({
12
+ value,
13
+ children,
14
+ icon,
15
+ disabled,
16
+ style,
17
+ states,
18
+ ...props
19
+ }: TabProps & { states?: { active?: boolean; disabled?: boolean } }) => {
20
+ const {
21
+ value: active,
22
+ select,
23
+ size,
24
+ disabled: allDisabled,
25
+ registerTabLayout,
26
+ } = useTabsContext();
27
+ const { active: pressed } = states || { active: false };
28
+ const isActive = active === value;
29
+ styles.useVariants({ size, pressed });
30
+ const ref = useRef<View | null>(null);
31
+ const handlePress = () => {
32
+ if (disabled || allDisabled) return;
33
+ select(value);
34
+ };
35
+ const handleLayout = useCallback(
36
+ (e: any) => {
37
+ const { x, y, width, height } = e.nativeEvent.layout;
38
+ registerTabLayout(value, { x, y, width, height });
39
+ },
40
+ [value, registerTabLayout]
41
+ );
42
+ return (
43
+ <Pressable
44
+ ref={ref}
45
+ accessibilityRole="tab"
46
+ accessibilityState={{ selected: isActive, disabled: !!(disabled || allDisabled) }}
47
+ onPress={handlePress}
48
+ onLayout={handleLayout}
49
+ style={[styles.tab, style]}
50
+ {...(Platform.OS === 'web'
51
+ ? { id: `tab-${value}`, 'aria-controls': `tabpanel-${value}` }
52
+ : null)}
53
+ {...props}
54
+ >
55
+ <View style={styles.content}>
56
+ {icon ? <Icon as={icon} /> : null}
57
+ <DetailText size={size}>{children}</DetailText>
58
+ </View>
59
+ </Pressable>
60
+ );
61
+ };
62
+
63
+ Tab.displayName = 'Tab';
64
+
65
+ const styles = StyleSheet.create(theme => ({
66
+ tab: {
67
+ position: 'relative',
68
+ backgroundColor: 'transparent',
69
+ alignItems: 'center',
70
+ justifyContent: 'center',
71
+ paddingHorizontal: theme.components.tabs.item.paddingHorizontal,
72
+ paddingVertical: theme.components.tabs.item.paddingVertical,
73
+ _web: {
74
+ _hover: {
75
+ backgroundColor: theme.color.interactive.neutral.surface.subtle.hover,
76
+ },
77
+ '_focus-visible': {
78
+ ...theme.helpers.focusVisible,
79
+ outlineOffset: -2,
80
+ borderRadius: theme.borderRadius.sm,
81
+ },
82
+ _active: {
83
+ backgroundColor: theme.color.interactive.neutral.surface.subtle.active,
84
+ },
85
+ },
86
+ variants: {
87
+ size: {
88
+ md: { height: theme.components.tabs.md.height },
89
+ lg: { height: theme.components.tabs.lg.height },
90
+ },
91
+ pressed: {
92
+ true: {
93
+ backgroundColor: theme.color.interactive.neutral.surface.subtle.active,
94
+ },
95
+ false: {},
96
+ },
97
+ },
98
+ },
99
+ content: {
100
+ flexDirection: 'row',
101
+ alignItems: 'center',
102
+ gap: theme.components.tabs.item.gap,
103
+ },
104
+ badge: {
105
+ marginLeft: theme.space[25],
106
+ },
107
+ }));
108
+
109
+ const PressableTab = createPressable({ Root: Tab });
110
+
111
+ PressableTab.displayName = 'Tab';
112
+
113
+ export default PressableTab;
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ViewProps } from 'react-native';
3
+
4
+ export interface TabPanelProps extends ViewProps {
5
+ value: string;
6
+ children: ReactNode;
7
+ keepMounted?: boolean;
8
+ }
9
+
10
+ export default TabPanelProps;
@@ -0,0 +1,46 @@
1
+ import { memo } from 'react';
2
+ import { Platform, View } from 'react-native';
3
+ import { StyleSheet } from 'react-native-unistyles';
4
+ import type TabPanelProps from './TabPanel.props';
5
+ import { useTabsContext } from './Tabs.context';
6
+
7
+ const TabPanel = ({ value, children, keepMounted, style, ...rest }: TabPanelProps) => {
8
+ const { value: active } = useTabsContext();
9
+ const isActive = active === value;
10
+ if (!isActive && !keepMounted) return null;
11
+ styles.useVariants({ active: isActive });
12
+ return (
13
+ <View
14
+ {...(Platform.OS === 'web'
15
+ ? {
16
+ id: `tabpanel-${value}`,
17
+ role: 'tabpanel',
18
+ 'aria-labelledby': `tab-${value}`,
19
+ hidden: !isActive || undefined,
20
+ }
21
+ : { accessibilityRole: 'summary' })}
22
+ importantForAccessibility={isActive ? undefined : 'no-hide-descendants'}
23
+ accessibilityElementsHidden={isActive ? undefined : true}
24
+ style={[styles.panel, style]}
25
+ {...rest}
26
+ >
27
+ {children}
28
+ </View>
29
+ );
30
+ };
31
+
32
+ const styles = StyleSheet.create(theme => ({
33
+ panel: {
34
+ flex: 1,
35
+ width: '100%',
36
+ paddingTop: theme.space[100],
37
+ variants: {
38
+ active: {
39
+ true: { display: 'flex' },
40
+ false: { display: 'none' },
41
+ },
42
+ },
43
+ },
44
+ }));
45
+
46
+ export default memo(TabPanel);
@@ -0,0 +1,26 @@
1
+ import { createContext, useContext } from 'react';
2
+ import { SharedValue } from 'react-native-reanimated';
3
+
4
+ export interface TabsContextValue {
5
+ value: string | undefined;
6
+ size: 'md' | 'lg';
7
+ disabled?: boolean;
8
+ select: (value: string) => void;
9
+ registerTabLayout: (
10
+ value: string,
11
+ layout: { x: number; y: number; width: number; height: number }
12
+ ) => void;
13
+ getTabLayout: (
14
+ value: string
15
+ ) => { x: number; y: number; width: number; height: number } | undefined;
16
+ indicatorXSV: SharedValue<number>;
17
+ indicatorSizeSV: SharedValue<number>;
18
+ }
19
+
20
+ export const TabsContext = createContext<TabsContextValue | undefined>(undefined);
21
+
22
+ export const useTabsContext = () => {
23
+ const ctx = useContext(TabsContext);
24
+ if (!ctx) throw new Error('Tab / TabPanel must be used within Tabs');
25
+ return ctx;
26
+ };
@@ -0,0 +1,214 @@
1
+ import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks';
2
+ import { Badge, BodyText, Tab, TabPanel, Tabs, TabsList } from '../../';
3
+ import { BackToTopButton, UsageWrap, ViewFigmaButton } from '../../../docs/components';
4
+ import * as Stories from './Tabs.stories';
5
+
6
+ <BackToTopButton />
7
+
8
+ <ViewFigmaButton url="https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=6463-1113" />
9
+
10
+ <Meta title="Components / Tabs" />
11
+
12
+ # Tabs
13
+
14
+ Tabs are a navigational component that allows users to move easily between groups of related content.
15
+
16
+ - [Playground](#playground)
17
+ - [Usage](#usage)
18
+ - [Examples](#examples)
19
+ - [Controlled](#controlled)
20
+ - [Overflow & Scrolling](#overflow--scrolling)
21
+ - [With Icons](#with-icons)
22
+ - [Props](#props)
23
+ - [Size Variants](#size-variants)
24
+ - [Accessibility](#accessibility)
25
+
26
+ ## Playground
27
+
28
+ <Canvas of={Stories.Playground} />
29
+ <Controls of={Stories.Playground} />
30
+
31
+ ## Usage
32
+
33
+ <UsageWrap>
34
+ <Tabs defaultValue="account">
35
+ <TabsList>
36
+ <Tab value="account">Account</Tab>
37
+ <Tab value="billing">Billing</Tab>
38
+ <Tab value="usage">Usage</Tab>
39
+ </TabsList>
40
+ <TabPanel value="account">
41
+ <BodyText>Account content</BodyText>
42
+ </TabPanel>
43
+ <TabPanel value="billing">
44
+ <BodyText>Billing content</BodyText>
45
+ </TabPanel>
46
+ <TabPanel value="usage">
47
+ <BodyText>Usage metrics content</BodyText>
48
+ </TabPanel>
49
+ </Tabs>
50
+ </UsageWrap>
51
+
52
+ ```tsx
53
+ import { Tabs, TabsList, Tab, TabPanel } from '@utilitywarehouse/hearth-react-native';
54
+
55
+ <Tabs defaultValue="account">
56
+ <TabsList>
57
+ <Tab value="account">Account</Tab>
58
+ <Tab value="billing">Billing</Tab>
59
+ <Tab value="usage">Usage</Tab>
60
+ </TabsList>
61
+ <TabPanel value="account">
62
+ <BodyText>Account content</BodyText>
63
+ </TabPanel>
64
+ <TabPanel value="billing">
65
+ <BodyText>Billing content</BodyText>
66
+ </TabPanel>
67
+ <TabPanel value="usage">
68
+ <BodyText>Usage metrics content</BodyText>
69
+ </TabPanel>
70
+ </Tabs>;
71
+ ```
72
+
73
+ ## Examples
74
+
75
+ ### Controlled
76
+
77
+ Tabs can be controlled by passing `value` and `onValueChange` props to the `Tabs` component.
78
+ This allows you to manage the active tab state externally.
79
+
80
+ ```tsx
81
+ const [tab, setTab] = useState('account');
82
+ <Tabs value={tab} onValueChange={setTab}>
83
+ <TabsList>
84
+ <Tab value="account">Account</Tab>
85
+ <Tab value="billing">Billing</Tab>
86
+ <Tab value="usage">Usage</Tab>
87
+ </TabsList>
88
+ <TabPanel value="account">
89
+ <BodyText>Account content</BodyText>
90
+ </TabPanel>
91
+ <TabPanel value="billing">
92
+ <BodyText>Billing content</BodyText>
93
+ </TabPanel>
94
+ <TabPanel value="usage">
95
+ <BodyText>Usage metrics content</BodyText>
96
+ </TabPanel>
97
+ </Tabs>;
98
+ ```
99
+
100
+ ### Overflow & Scrolling
101
+
102
+ <Canvas of={Stories.WithScrolling} />
103
+
104
+ When the combined tab labels exceed the horizontal space, the list becomes horizontally scrollable.
105
+ Contextual navigation buttons (previous / next) appear just outside the scrollable area. They:
106
+
107
+ - Scroll by ~60% of the visible width per press (for efficient paging)
108
+ - Are hidden from assistive technology (screen readers) - users rely on the tab structure itself
109
+ - Only render when further scrolling in that direction is possible
110
+ - Honor reduced motion: only the indicator animation is affected (system reduced motion will minimise excessive transitions)
111
+
112
+ ### With Icons
113
+
114
+ You can add icons to each `Tab` by passing an icon component to the `icon` prop. The icon will be placed to the left of the tab label.
115
+
116
+ <Canvas of={Stories.WithIcons} />
117
+
118
+ ```tsx
119
+ import { Tabs, TabsList, Tab, TabPanel } from '@utilitywarehouse/hearth-react-native';
120
+ import {
121
+ ElectricityMediumIcon,
122
+ MobileMediumIcon,
123
+ InsuranceMediumIcon,
124
+ } from '@utilitywarehouse/hearth-react-native-icons';
125
+
126
+ const MyComponent = () => (
127
+ <Tabs defaultValue="one">
128
+ <TabsList>
129
+ <Tab value="one" icon={ElectricityMediumIcon}>
130
+ Energy
131
+ </Tab>
132
+ <Tab value="two" icon={BroadbandMediumIcon}>
133
+ Broadband
134
+ </Tab>
135
+ <Tab value="three" icon={MobileMediumIcon}>
136
+ Mobile
137
+ </Tab>
138
+ <Tab value="four" icon={InsuranceMediumIcon}>
139
+ Insurance
140
+ </Tab>
141
+ </TabsList>
142
+ <TabPanel value="one">
143
+ <BodyText>One panel</BodyText>
144
+ </TabPanel>
145
+ <TabPanel value="two">
146
+ <BodyText>Two panel</BodyText>
147
+ </TabPanel>
148
+ <TabPanel value="three">
149
+ <BodyText>Three panel</BodyText>
150
+ </TabPanel>
151
+ <TabPanel value="four">
152
+ <BodyText>Four panel</BodyText>
153
+ </TabPanel>
154
+ </Tabs>
155
+ );
156
+ ```
157
+
158
+ ## Props
159
+
160
+ ### Tabs
161
+
162
+ | Prop | Type | Description | Default |
163
+ | --------------- | ------------------------- | ------------------------------------------------------------------- | ---------- |
164
+ | `value` | `string` | Controlled active tab value | - |
165
+ | `defaultValue` | `string` | Uncontrolled initial tab value | first Tab |
166
+ | `onValueChange` | `(value: string) => void` | Change handler | - |
167
+ | `size` | `'md' \| 'lg'` | Size variant (affects tab height & list padding) | `md` |
168
+ | `disabled` | `boolean` | Disables all interaction | `false` |
169
+ | `withPanels` | `boolean` | If true, expects sibling `TabPanel` elements and wires ARIA linkage | `true`\* |
170
+ | `children` | `ReactNode` | Composition (typically `TabsList` + `TabPanel` children) | (required) |
171
+
172
+ \*If omitted you can still compose panels manually, but accessibility linkage (`aria-controls` / `id`) is provided when `withPanels` is true.
173
+
174
+ ### TabsList / Tab / TabPanel
175
+
176
+ #### TabsList
177
+
178
+ | Prop | Type | Description | Default |
179
+ | ---------- | ----------- | -------------------------- | ---------- |
180
+ | `children` | `ReactNode` | One or more `Tab` elements | (required) |
181
+
182
+ #### Tab
183
+
184
+ | Prop | Type | Description | Default |
185
+ | ---------- | --------------- | --------------------------------- | ---------- |
186
+ | `value` | `string` | Unique tab value | (required) |
187
+ | `children` | `ReactNode` | Label content (can include badge) | (required) |
188
+ | `icon` | `ComponentType` | Optional leading icon | - |
189
+ | `disabled` | `boolean` | Disabled state | `false` |
190
+
191
+ #### TabPanel
192
+
193
+ | Prop | Type | Description | Default |
194
+ | ------------- | ----------- | ------------------------------------------- | ---------- |
195
+ | `value` | `string` | Matches a `Tab` value | (required) |
196
+ | `children` | `ReactNode` | Panel content | (required) |
197
+ | `keepMounted` | `boolean` | Keep hidden panels in tree (preserve state) | `false` |
198
+
199
+ ## Size Variants
200
+
201
+ <Canvas of={Stories.Sizes} />
202
+
203
+ ## Accessibility
204
+
205
+ | Element | Role | Notes |
206
+ | ----------- | --------------------------------------- | ----------------------------------------------- |
207
+ | `Tabs` root | `tablist` | Container for related `tab` elements |
208
+ | `Tab` | `tab` | Uses `accessibilityState.selected` & `disabled` |
209
+ | `TabPanel` | `tabpanel` (web) / hidden view (native) | Inactive panels removed from a11y tree |
210
+
211
+ Selection state is communicated through `accessibilityState`. Provide concise labels (children text) for best screen reader clarity.
212
+ Any extra content you compose is read in order unless you explicitly hide it. The animated indicator reflects the active tab;
213
+ reduced motion preferences are respected. Hidden panels are removed from the accessibility tree.
214
+ Overflow scroll buttons are hidden from assistive tech and only appear visually when scrolling is possible.
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ViewProps } from 'react-native';
3
+
4
+ export interface TabsProps extends ViewProps {
5
+ /** Controlled active tab value */
6
+ value?: string;
7
+ /** Uncontrolled initial value */
8
+ defaultValue?: string;
9
+ /** Callback when active tab changes */
10
+ onValueChange?: (value: string) => void;
11
+ /** Size variant */
12
+ size?: 'md' | 'lg';
13
+ /** Tabs (Tab components) */
14
+ children: ReactNode;
15
+ /** Disable all tabs */
16
+ disabled?: boolean;
17
+ /** If true, expect sibling TabPanel components and manage their rendering/ARIA linkage */
18
+ withPanels?: boolean;
19
+ }
20
+
21
+ export default TabsProps;