@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
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type StyleProp, type
|
|
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?:
|
|
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'`
|
|
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?:
|
|
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;
|