@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
@@ -1,5 +1,6 @@
1
- import { type StyleProp, type ViewStyle, type ViewProps, View } from 'react-native';
1
+ import { type StyleProp, type ViewProps, type ViewStyle, View } from 'react-native';
2
2
  import { StyleSheet } from 'react-native-unistyles';
3
+ import { SpacingValues } from '../../types';
3
4
 
4
5
  const ButtonGroupRoot = ({
5
6
  children,
@@ -12,7 +13,7 @@ const ButtonGroupRoot = ({
12
13
  flexDirection?: ViewStyle['flexDirection'];
13
14
  reversed?: boolean;
14
15
  attached?: boolean;
15
- space?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
16
+ space?: SpacingValues;
16
17
  }) => {
17
18
  let direction = flexDirection;
18
19
  if (reversed) {
@@ -41,6 +42,12 @@ const styles = StyleSheet.create(theme => ({
41
42
  text: {
42
43
  variants: {
43
44
  space: {
45
+ none: {
46
+ gap: theme.layout.mobile.spacing.none,
47
+ },
48
+ '2xs': {
49
+ gap: theme.layout.mobile.spacing['2xs'],
50
+ },
44
51
  xs: {
45
52
  gap: theme.layout.mobile.spacing.xs,
46
53
  },
@@ -56,6 +63,9 @@ const styles = StyleSheet.create(theme => ({
56
63
  xl: {
57
64
  gap: theme.layout.mobile.spacing.xl,
58
65
  },
66
+ '2xl': {
67
+ gap: theme.layout.mobile.spacing['2xl'],
68
+ },
59
69
  },
60
70
  attached: {
61
71
  true: {
@@ -57,7 +57,7 @@ const MyComponent = () => (
57
57
  | selected | `boolean` | Whether the card is selected. | `false` |
58
58
  | onPress | `() => void` | Callback function to be called. | - |
59
59
  | disabled | `boolean` | Whether the card is disabled. | `false` |
60
- | space | `'none' \| 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | The space between the content. | `none` |
60
+ | space | `'none' \| '2xs' \| 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | The space between the content. | `none` |
61
61
  | alignItems | `'flex-start' \| 'flex-end' \| `<br />`'center' \| 'stretch' \| 'baseline'` | The align items of the flex container. | `flex-start` |
62
62
  | justifyContent | `'flex-start' \| 'flex-end' \| 'center' \| 'space-between' \| `<br />` 'space-around' \| 'space-evenly'` | The justify content of the flex container. | `flex-start` |
63
63
  | flexWrap | `'wrap' \| 'nowrap' \| 'wrap-reverse'` | The wrap of the flex container. | `nowrap` |
@@ -1,5 +1,5 @@
1
1
  import { PressableProps, ViewStyle } from 'react-native';
2
- import { SpaceValue } from '../../types';
2
+ import { SpaceValue, SpacingValues } from '../../types';
3
3
 
4
4
  interface CardProps extends PressableProps {
5
5
  variant?: 'emphasis' | 'subtle';
@@ -15,7 +15,7 @@ interface CardProps extends PressableProps {
15
15
  | 'pig';
16
16
  noPadding?: boolean;
17
17
  disabled?: boolean;
18
- space?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
18
+ space?: SpacingValues;
19
19
  alignItems?: ViewStyle['alignItems'];
20
20
  justifyContent?: ViewStyle['justifyContent'];
21
21
  flexDirection?: ViewStyle['flexDirection'];
@@ -0,0 +1,120 @@
1
+ import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks';
2
+ import { Center, CurrencyInput, FormField } from '../../';
3
+ import { BackToTopButton, UsageWrap, ViewFigmaButton } from '../../../docs/components';
4
+ import * as Stories from './CurrencyInput.stories';
5
+
6
+ <Meta title="Forms / Currency Input" />
7
+
8
+ <BackToTopButton />
9
+
10
+ <ViewFigmaButton url="https://www.figma.com/design/pjuYVErQWaAvs8rCAVgPKX/Motion-Tokens?node-id=2161-1336" />
11
+
12
+ # Currency Input
13
+
14
+ An input specialised for monetary amounts. It shows a currency symbol prefix and uses a decimal keypad where supported.
15
+
16
+ - [Playground](#playground)
17
+ - [Usage](#usage)
18
+ - [Props](#props)
19
+ - [States](#states)
20
+ - [Formatting](#formatting)
21
+ - [Accessibility](#accessibility)
22
+
23
+ ## Playground
24
+
25
+ <Canvas of={Stories.Playground} />
26
+ <Controls of={Stories.Playground} />
27
+
28
+ ## Usage
29
+
30
+ <UsageWrap>
31
+ <Center>
32
+ <CurrencyInput placeholder="0.00" />
33
+ </Center>
34
+ </UsageWrap>
35
+
36
+ ```tsx
37
+ import { CurrencyInput } from '@utilitywarehouse/hearth-react-native';
38
+
39
+ const MyComponent = () => {
40
+ const [value, setValue] = useState('');
41
+ return (
42
+ <CurrencyInput value={value} onChange={e => setValue(e.nativeEvent.text)} placeholder="0.00" />
43
+ );
44
+ };
45
+ ```
46
+
47
+ ## Props
48
+
49
+ When using `CurrencyInput`, the component inherits React Native TextInput props (except `children`). In addition, it supports:
50
+
51
+ | Prop | Type | Default | Description |
52
+ | ------------------- | ----------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------- |
53
+ | validationStatus | `'initial' \| 'valid' \| 'invalid'` | `'initial'` | Validation styling state |
54
+ | disabled | boolean | `false` | Disables the input |
55
+ | readonly | boolean | `false` | Makes the input read-only |
56
+ | focused | boolean | `false` | Forces the focused visual state |
57
+ | inBottomSheet | boolean | `false` | Use BottomSheetTextInput when true |
58
+ | placeholder | string | `'0.00'` | Placeholder text |
59
+ | autoFormatThousands | boolean | `false` | Automatically inserts thousand separators while the user types _(Only works with controlled components via onTextChange)_ |
60
+
61
+ Note: When used inside `FormField`, `validationStatus` and `disabled` are read from the context unless explicitly overridden.
62
+
63
+ ## Examples
64
+
65
+ ### With `FormField`
66
+
67
+ The `CurrencyInput` component can be used with the `FormField` component to create a custom input field.
68
+ For more information on the `FormField` component view the [docs here](/docs/components-form-field--docs).
69
+
70
+ <UsageWrap>
71
+ <Center>
72
+ <FormField label="Label" helperText="Helper text" helperPosition="bottom">
73
+ <CurrencyInput onChange={() => console.log('###')} />
74
+ </FormField>
75
+ </Center>
76
+ </UsageWrap>
77
+
78
+ ```tsx
79
+ import { FormField, CurrencyInput } from '@utilitywarehouse/hearth-react-native';
80
+
81
+ const MyComponent = () => {
82
+ const [value, setValue] = useState('');
83
+ const handleChange = e => {
84
+ setValue(e.target.value);
85
+ };
86
+ return (
87
+ <FormField label="Label" helperText="Helper text" helperPosition="bottom">
88
+ <CurrencyInput onChange={handleChange} value={value} />
89
+ </FormField>
90
+ );
91
+ };
92
+ ```
93
+
94
+ ## States
95
+
96
+ <Canvas of={Stories.States} />
97
+
98
+ ## Formatting
99
+
100
+ Enable automatic thousand separator formatting by setting `autoFormatThousands`.
101
+
102
+ <Canvas of={Stories.AutoFormatThousands} />
103
+
104
+ ```tsx
105
+ <CurrencyInput autoFormatThousands value="1234567.89" /> // displays 1,234,567.89
106
+ ```
107
+
108
+ Notes:
109
+
110
+ - Only digits, an optional leading minus and a single decimal point are formatted.
111
+ - Formatting is applied as you type; caret will move to the end (standard RN behaviour when programmatically updating value).
112
+ - Provide a controlled `value` if you need to manipulate or strip commas before persisting.
113
+
114
+ ## Accessibility
115
+
116
+ The component uses a standard TextInput field and supports:
117
+
118
+ - Screen readers (VoiceOver/TalkBack)
119
+ - `aria-disabled`, `aria-required`, `aria-invalid` via underlying Input
120
+ - Decimal keypad and selection/cursor colors aligned with theme
@@ -0,0 +1,19 @@
1
+ import type { TextInputProps, ViewProps } from 'react-native';
2
+
3
+ export interface CurrencyInputBaseProps {
4
+ disabled?: boolean;
5
+ validationStatus?: 'initial' | 'valid' | 'invalid';
6
+ readonly?: boolean;
7
+ focused?: boolean;
8
+ placeholder?: string;
9
+ inBottomSheet?: boolean;
10
+ required?: boolean;
11
+ /** When true, automatically formats the numeric value with thousand separators (e.g. 1234 -> 1,234). */
12
+ autoFormatThousands?: boolean;
13
+ }
14
+
15
+ export type CurrencyInputProps = CurrencyInputBaseProps &
16
+ Omit<TextInputProps, 'children'> &
17
+ ViewProps;
18
+
19
+ export default CurrencyInputProps;
@@ -0,0 +1,116 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { useState } from 'react';
3
+ import { CurrencyInput } from '..';
4
+ import { VariantTitle } from '../../../docs/components';
5
+ import { Flex } from '../Flex';
6
+
7
+ const meta = {
8
+ title: 'Stories / CurrencyInput',
9
+ component: CurrencyInput,
10
+ parameters: {
11
+ layout: 'centered',
12
+ },
13
+ argTypes: {
14
+ placeholder: {
15
+ control: 'text',
16
+ description: 'The CurrencyInput field placeholder',
17
+ defaultValue: '0.00',
18
+ },
19
+ validationStatus: {
20
+ control: 'select',
21
+ options: ['initial', 'valid', 'invalid'],
22
+ description: 'The validation status',
23
+ defaultValue: 'initial',
24
+ },
25
+ disabled: {
26
+ control: 'boolean',
27
+ description: 'Disable the input',
28
+ defaultValue: false,
29
+ },
30
+ readonly: {
31
+ control: 'boolean',
32
+ description: 'Read only',
33
+ defaultValue: false,
34
+ },
35
+ focused: {
36
+ control: 'boolean',
37
+ description: 'Focused',
38
+ defaultValue: false,
39
+ },
40
+ autoFormatThousands: {
41
+ control: 'boolean',
42
+ description:
43
+ 'Automatically add thousand separators while typing _(Only works with controlled components via onTextChange)_',
44
+ defaultValue: false,
45
+ },
46
+ },
47
+ args: {
48
+ placeholder: '0.00',
49
+ validationStatus: 'initial',
50
+ disabled: false,
51
+ readonly: false,
52
+ focused: false,
53
+ autoFormatThousands: false,
54
+ },
55
+ } satisfies Meta<typeof CurrencyInput>;
56
+
57
+ export default meta;
58
+ type Story = StoryObj<typeof meta>;
59
+
60
+ export const Playground: Story = {};
61
+
62
+ export const AutoFormatThousands: Story = {
63
+ parameters: {
64
+ controls: { include: ['autoFormatThousands'] },
65
+ },
66
+ args: { autoFormatThousands: true },
67
+ render: args => {
68
+ const [value, setValue] = useState('1234.56');
69
+ const handleChange = (val: string) => {
70
+ setValue(val);
71
+ };
72
+ return <CurrencyInput {...args} value={value} onChangeText={handleChange} />;
73
+ },
74
+ };
75
+
76
+ export const States: Story = {
77
+ parameters: {
78
+ controls: { include: [] },
79
+ },
80
+ render: () => {
81
+ return (
82
+ <Flex direction="column" space="lg">
83
+ <VariantTitle title="Default">
84
+ <CurrencyInput />
85
+ </VariantTitle>
86
+ <VariantTitle title="With placeholder">
87
+ <CurrencyInput placeholder="0.00" />
88
+ </VariantTitle>
89
+ <VariantTitle title="Focused">
90
+ <CurrencyInput focused value="12.34" />
91
+ </VariantTitle>
92
+ <VariantTitle title="Valid">
93
+ <CurrencyInput validationStatus="valid" />
94
+ </VariantTitle>
95
+ <VariantTitle title="Invalid">
96
+ <CurrencyInput validationStatus="invalid" />
97
+ </VariantTitle>
98
+ <VariantTitle title="Valid - Focused">
99
+ <CurrencyInput validationStatus="valid" focused />
100
+ </VariantTitle>
101
+ <VariantTitle title="Invalid - Focused">
102
+ <CurrencyInput validationStatus="invalid" focused />
103
+ </VariantTitle>
104
+ <VariantTitle title="Disabled">
105
+ <CurrencyInput disabled />
106
+ </VariantTitle>
107
+ <VariantTitle title="Readonly">
108
+ <CurrencyInput readonly />
109
+ </VariantTitle>
110
+ <VariantTitle title="Auto format thousands">
111
+ <CurrencyInput autoFormatThousands value="1234.56" />
112
+ </VariantTitle>
113
+ </Flex>
114
+ );
115
+ },
116
+ };
@@ -0,0 +1,91 @@
1
+ import { Platform } from 'react-native';
2
+ import { StyleSheet } from 'react-native-unistyles';
3
+ import { formatThousands } from '../../utils';
4
+ import { DetailText } from '../DetailText';
5
+ import { useFormFieldContext } from '../FormField';
6
+ import { Input, InputField, InputSlot } from '../Input';
7
+ import type CurrencyInputProps from './CurrencyInput.props';
8
+
9
+ const CurrencyInput = ({
10
+ validationStatus = 'initial',
11
+ disabled,
12
+ focused,
13
+ readonly,
14
+ placeholder,
15
+ inBottomSheet = false,
16
+ required,
17
+ autoFormatThousands = false,
18
+ value,
19
+ onChangeText,
20
+ ...rest
21
+ }: CurrencyInputProps) => {
22
+ const formFieldContext = useFormFieldContext();
23
+ const { disabled: formFieldDisabled } = formFieldContext;
24
+ const validationStatusFromContext = formFieldContext?.validationStatus ?? validationStatus;
25
+
26
+ const defaultFormat = '0.00';
27
+ const getPlaceholder = placeholder ?? defaultFormat;
28
+
29
+ const handleChangeText = (text: string) => {
30
+ if (autoFormatThousands) {
31
+ const formatted = formatThousands(text);
32
+ onChangeText?.(formatted);
33
+ } else {
34
+ onChangeText?.(text);
35
+ }
36
+ };
37
+
38
+ const displayValue =
39
+ autoFormatThousands && typeof value === 'string' ? formatThousands(value) : value;
40
+
41
+ return (
42
+ <Input
43
+ validationStatus={validationStatusFromContext}
44
+ disabled={formFieldDisabled ?? disabled}
45
+ readonly={readonly}
46
+ focused={focused}
47
+ style={styles.wrap}
48
+ >
49
+ <InputSlot>
50
+ <DetailText size="4xl" style={styles.text}>
51
+ £
52
+ </DetailText>
53
+ </InputSlot>
54
+ <InputField
55
+ inputMode="decimal"
56
+ inBottomSheet={inBottomSheet}
57
+ {...rest}
58
+ placeholder={getPlaceholder}
59
+ keyboardType="decimal-pad"
60
+ style={styles.input}
61
+ value={displayValue as any}
62
+ onChangeText={handleChangeText}
63
+ />
64
+ </Input>
65
+ );
66
+ };
67
+
68
+ CurrencyInput.displayName = 'CurrencyInput';
69
+
70
+ const styles = StyleSheet.create(theme => ({
71
+ wrap: {
72
+ height: theme.components.input.currency.height,
73
+ gap: theme.components.input.currency.gap,
74
+ },
75
+ text: {
76
+ ...(Platform.OS === 'ios' && { lineHeight: 46 }),
77
+ _web: {
78
+ marginTop: 1,
79
+ },
80
+ },
81
+ input: {
82
+ fontSize: theme.typography.mobile.detailText['4xl'].fontSize,
83
+ fontFamily: theme.typography.mobile.detailText.fontFamily,
84
+ fontWeight: theme.typography.mobile.detailText.fontWeight,
85
+ paddingTop: 0,
86
+ paddingBottom: 0,
87
+ paddingLeft: 0,
88
+ },
89
+ }));
90
+
91
+ export default CurrencyInput;
@@ -0,0 +1 @@
1
+ export { default as CurrencyInput } from './CurrencyInput';
@@ -0,0 +1,18 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ export interface DescriptionListContextValue {
4
+ direction: 'row' | 'column';
5
+ itemHeadingWidth?: number;
6
+ }
7
+
8
+ export const DescriptionListContext = createContext<DescriptionListContextValue | undefined>(
9
+ undefined
10
+ );
11
+
12
+ export const useDescriptionListContext = () => {
13
+ const ctx = useContext(DescriptionListContext);
14
+ if (!ctx) {
15
+ throw new Error('DescriptionListItem must be used within a DescriptionList');
16
+ }
17
+ return ctx;
18
+ };
@@ -0,0 +1,98 @@
1
+ import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks';
2
+ import { DescriptionList, DescriptionListItem } from '../../';
3
+ import { BackToTopButton, UsageWrap } from '../../../docs/components';
4
+ import * as Stories from './DescriptionList.stories';
5
+
6
+ <BackToTopButton />
7
+
8
+ <Meta title="Components / Description List" />
9
+
10
+ # Description List
11
+
12
+ Display pairs of related metadata (heading + description). Supports column (stacked) and row (two-column) layouts, optional SectionHeader (heading + helper text), and per-item action links.
13
+
14
+ - [Playground](#playground)
15
+ - [Usage](#usage)
16
+ - [Props](#props)
17
+ - [Variants](#variants)
18
+ - [Accessibility](#accessibility)
19
+
20
+ ## Playground
21
+
22
+ <Canvas of={Stories.Playground} />
23
+ <Controls of={Stories.Playground} />
24
+
25
+ ## Usage
26
+
27
+ <UsageWrap>
28
+ <DescriptionList
29
+ direction="column"
30
+ heading="Account details"
31
+ helperText="Static account metadata"
32
+ >
33
+ <DescriptionListItem heading="Account Number" description="123456789" />
34
+ <DescriptionListItem heading="Sort Code" description="12-34-56" />
35
+ <DescriptionListItem heading="Status" description="Active" />
36
+ </DescriptionList>
37
+ </UsageWrap>
38
+
39
+ ```tsx
40
+ import { DescriptionList, DescriptionListItem } from '@utilitywarehouse/hearth-react-native';
41
+
42
+ <DescriptionList direction="column" heading="Account details" helperText="Static account metadata">
43
+ <DescriptionListItem heading="Account Number" description="123456789" />
44
+ <DescriptionListItem heading="Sort Code" description="12-34-56" />
45
+ <DescriptionListItem heading="Status" description="Active" />
46
+ </DescriptionList>;
47
+ ```
48
+
49
+ ## Props
50
+
51
+ ### DescriptionList
52
+
53
+ | Prop | Type | Description | Default |
54
+ | ------------------ | -------------------------------------------- | ------------------------------------------------ | ----------- |
55
+ | `direction` | `'row' \| 'column'` | Layout orientation | `column` |
56
+ | `itemHeadingWidth` | `number` | Override heading column width in row layout | token value |
57
+ | `heading` | `string` | Optional overall heading (renders SectionHeader) | - |
58
+ | `helperText` | `string` | Supporting text under heading | - |
59
+ | `linkText` | `string` | Section header link text | - |
60
+ | `linkHref` | `string` | Header link URL (web) | - |
61
+ | `linkIcon` | `ComponentType` | Header link icon | - |
62
+ | `linkIconPosition` | `'left' \| 'right'` | Header link icon position | `right` |
63
+ | `linkOnPress` | `() => void` | Header link press handler | - |
64
+ | `linkTarget` | `'_blank' \| '_self' \| '_parent' \| '_top'` | Header link target | `_self` |
65
+ | `linkShowIcon` | `boolean` | Whether header link icon is shown | `false` |
66
+
67
+ ### DescriptionListItem
68
+
69
+ | Prop | Type | Description | Default |
70
+ | ------------------ | -------------------------------------------- | -------------------------------------------- | ---------- |
71
+ | `heading` | `ReactNode` | Heading (label) content | (required) |
72
+ | `description` | `ReactNode` | Description (value) content | (required) |
73
+ | `headingWidth` | `number` | Per-item heading width override (row layout) | inherits |
74
+ | `linkText` | `string` | Inline action link text | - |
75
+ | `linkHref` | `string` | Inline action link URL (web) | - |
76
+ | `linkIcon` | `ComponentType` | Inline action link icon | - |
77
+ | `linkIconPosition` | `'left' \| 'right'` | Inline link icon position | `right` |
78
+ | `linkOnPress` | `() => void` | Inline link press handler | - |
79
+ | `linkTarget` | `'_blank' \| '_self' \| '_parent' \| '_top'` | Inline link target | `_self` |
80
+ | `linkShowIcon` | `boolean` | Show inline link icon | `false` |
81
+
82
+ > Uses `theme.components.descriptionList` tokens for spacing & column width.
83
+
84
+ ## Variants
85
+
86
+ <Canvas of={Stories.KitchenSink} />
87
+ <Canvas of={Stories.WithLinks} />
88
+
89
+ ## Accessibility
90
+
91
+ The component applies the following roles:
92
+
93
+ | Element | Role |
94
+ | ----------------------------- | ------------------------------------- |
95
+ | `DescriptionList` root | `list` |
96
+ | `DescriptionListItem` wrapper | `text` (combined label when possible) |
97
+
98
+ When both heading and description are plain text they are merged into one accessibility node (e.g. “Account Number: 123456789”) and the child elements are hidden from the a11y tree to avoid duplicate reading (TalkBack / VoiceOver). If either part is non‑text the children remain individually exposed.
@@ -0,0 +1,20 @@
1
+ import { ComponentType } from 'react';
2
+ import type { ViewProps } from 'react-native';
3
+
4
+ export interface DescriptionListProps extends ViewProps {
5
+ /** Direction orientation for items */
6
+ direction?: 'row' | 'column';
7
+ /** Override heading/term column width when layout is row (defaults to token) */
8
+ itemHeadingWidth?: number;
9
+ heading?: string;
10
+ helperText?: string;
11
+ linkText?: string;
12
+ linkHref?: string;
13
+ linkIcon?: ComponentType;
14
+ linkIconPosition?: 'left' | 'right';
15
+ linkOnPress?: () => void;
16
+ linkTarget?: '_blank' | '_self' | '_parent' | '_top';
17
+ linkShowIcon?: boolean;
18
+ }
19
+
20
+ export default DescriptionListProps;