@utilitywarehouse/hearth-react-native 0.4.1 → 0.4.2
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 +10 -0
- package/build/components/Checkbox/CheckboxTextContent.d.ts +1 -1
- package/build/components/Checkbox/CheckboxTextContent.js +9 -2
- package/build/components/CurrencyInput/CurrencyInput.d.ts +1 -1
- package/build/components/CurrencyInput/CurrencyInput.js +3 -3
- package/build/components/CurrencyInput/CurrencyInput.props.d.ts +2 -2
- package/build/components/Radio/RadioTextContent.d.ts +1 -1
- package/build/components/Radio/RadioTextContent.js +9 -2
- package/build/utils/getFlattenedColorValue.js +2 -19
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.js +1 -0
- package/build/utils/styleUtils.d.ts +0 -4
- package/build/utils/styleUtils.js +0 -50
- package/build/utils/themeValueHelpers.d.ts +17 -0
- package/build/utils/themeValueHelpers.js +54 -0
- package/docs/getting-started.mdx +13 -5
- package/docs/introduction.mdx +50 -5
- package/package.json +4 -4
- package/src/components/Checkbox/CheckboxTextContent.tsx +11 -3
- package/src/components/CurrencyInput/CurrencyInput.docs.mdx +4 -4
- package/src/components/CurrencyInput/CurrencyInput.props.ts +2 -2
- package/src/components/CurrencyInput/CurrencyInput.stories.tsx +17 -15
- package/src/components/CurrencyInput/CurrencyInput.tsx +3 -3
- package/src/components/Radio/RadioTextContent.tsx +11 -3
- package/src/utils/getFlattenedColorValue.ts +2 -21
- package/src/utils/getStyleValue.ts +0 -3
- package/src/utils/index.ts +1 -0
- package/src/utils/styleUtils.ts +0 -57
- package/src/utils/themeValueHelpers.ts +60 -0
package/.turbo/turbo-build.log
CHANGED
package/.turbo/turbo-lint.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @utilitywarehouse/hearth-react-native@0.4.
|
|
2
|
+
> @utilitywarehouse/hearth-react-native@0.4.2 lint /home/runner/work/hearth/hearth/packages/react-native
|
|
3
3
|
> TIMING=1 eslint --max-warnings 0
|
|
4
4
|
|
|
5
5
|
Rule | Time (ms) | Relative
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @utilitywarehouse/hearth-react-native
|
|
2
2
|
|
|
3
|
+
## 0.4.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#570](https://github.com/utilitywarehouse/hearth/pull/570) [`364e1ac`](https://github.com/utilitywarehouse/hearth/commit/364e1ac1ee6c83327b0614d90bf280e283144ae6) Thanks [@dorota-uw](https://github.com/dorota-uw)! - Switched the logic for enabling/disabling comma separator in CurrencyInput
|
|
8
|
+
|
|
9
|
+
- [#573](https://github.com/utilitywarehouse/hearth/pull/573) [`2c28614`](https://github.com/utilitywarehouse/hearth/commit/2c28614b5a9335dda15895c90e08774574d82d85) Thanks [@jordmccord](https://github.com/jordmccord)! - Fixes `Radio` and `Checkbox` helper overflow issue
|
|
10
|
+
|
|
11
|
+
- [#573](https://github.com/utilitywarehouse/hearth/pull/573) [`2c28614`](https://github.com/utilitywarehouse/hearth/commit/2c28614b5a9335dda15895c90e08774574d82d85) Thanks [@jordmccord](https://github.com/jordmccord)! - Fixes semantic text colour issue
|
|
12
|
+
|
|
3
13
|
## 0.4.1
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import FlexProps from '../Flex/Flex.props';
|
|
2
2
|
declare const CheckboxTextContent: {
|
|
3
|
-
({ children, ...props }: FlexProps): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
({ children, style, ...props }: FlexProps): import("react/jsx-runtime").JSX.Element;
|
|
4
4
|
displayName: string;
|
|
5
5
|
};
|
|
6
6
|
export default CheckboxTextContent;
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
2
3
|
import { Flex } from '../Flex';
|
|
3
|
-
const CheckboxTextContent = ({ children, ...props }) => {
|
|
4
|
-
return (_jsx(Flex, { direction: "column", space: "none", ...props, children: children }));
|
|
4
|
+
const CheckboxTextContent = ({ children, style, ...props }) => {
|
|
5
|
+
return (_jsx(Flex, { style: [styles.content, style], direction: "column", space: "none", ...props, children: children }));
|
|
5
6
|
};
|
|
7
|
+
const styles = StyleSheet.create({
|
|
8
|
+
content: {
|
|
9
|
+
flex: 1,
|
|
10
|
+
flexShrink: 1,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
6
13
|
CheckboxTextContent.displayName = 'CheckboxTextContent';
|
|
7
14
|
export default CheckboxTextContent;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type CurrencyInputProps from './CurrencyInput.props';
|
|
2
2
|
declare const CurrencyInput: {
|
|
3
|
-
({ validationStatus, disabled, focused, readonly, placeholder, inBottomSheet, required,
|
|
3
|
+
({ validationStatus, disabled, focused, readonly, placeholder, inBottomSheet, required, disableGroupSeparator, value, onChangeText, ...rest }: CurrencyInputProps): import("react/jsx-runtime").JSX.Element;
|
|
4
4
|
displayName: string;
|
|
5
5
|
};
|
|
6
6
|
export default CurrencyInput;
|
|
@@ -5,14 +5,14 @@ import { formatThousands } from '../../utils';
|
|
|
5
5
|
import { DetailText } from '../DetailText';
|
|
6
6
|
import { useFormFieldContext } from '../FormField';
|
|
7
7
|
import { Input, InputField, InputSlot } from '../Input';
|
|
8
|
-
const CurrencyInput = ({ validationStatus = 'initial', disabled, focused, readonly, placeholder, inBottomSheet = false, required,
|
|
8
|
+
const CurrencyInput = ({ validationStatus = 'initial', disabled, focused, readonly, placeholder, inBottomSheet = false, required, disableGroupSeparator = false, value, onChangeText, ...rest }) => {
|
|
9
9
|
const formFieldContext = useFormFieldContext();
|
|
10
10
|
const { disabled: formFieldDisabled } = formFieldContext;
|
|
11
11
|
const validationStatusFromContext = formFieldContext?.validationStatus ?? validationStatus;
|
|
12
12
|
const defaultFormat = '0.00';
|
|
13
13
|
const getPlaceholder = placeholder ?? defaultFormat;
|
|
14
14
|
const handleChangeText = (text) => {
|
|
15
|
-
if (
|
|
15
|
+
if (!disableGroupSeparator) {
|
|
16
16
|
const formatted = formatThousands(text);
|
|
17
17
|
onChangeText?.(formatted);
|
|
18
18
|
}
|
|
@@ -20,7 +20,7 @@ const CurrencyInput = ({ validationStatus = 'initial', disabled, focused, readon
|
|
|
20
20
|
onChangeText?.(text);
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
|
-
const displayValue =
|
|
23
|
+
const displayValue = !disableGroupSeparator && typeof value === 'string' ? formatThousands(value) : value;
|
|
24
24
|
return (_jsxs(Input, { validationStatus: validationStatusFromContext, disabled: formFieldDisabled ?? disabled, readonly: readonly, focused: focused, style: styles.wrap, children: [_jsx(InputSlot, { children: _jsx(DetailText, { size: "4xl", style: styles.text, accessible: false, children: "\u00A3" }) }), _jsx(InputField, { inputMode: "decimal", inBottomSheet: inBottomSheet, accessibilityHint: 'Enter the amount in pounds and pence, for example "10.99"', ...rest, placeholder: getPlaceholder, keyboardType: "decimal-pad", style: styles.input, value: displayValue, onChangeText: handleChangeText })] }));
|
|
25
25
|
};
|
|
26
26
|
CurrencyInput.displayName = 'CurrencyInput';
|
|
@@ -7,8 +7,8 @@ export interface CurrencyInputBaseProps {
|
|
|
7
7
|
placeholder?: string;
|
|
8
8
|
inBottomSheet?: boolean;
|
|
9
9
|
required?: boolean;
|
|
10
|
-
/** When
|
|
11
|
-
|
|
10
|
+
/** When not specifically disabled, the numeric value is automatically formatted with thousand separators (e.g. 1234 -> 1,234). */
|
|
11
|
+
disableGroupSeparator?: boolean;
|
|
12
12
|
}
|
|
13
13
|
export type CurrencyInputProps = CurrencyInputBaseProps & Omit<TextInputProps, 'children'> & ViewProps;
|
|
14
14
|
export default CurrencyInputProps;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import FlexProps from '../Flex/Flex.props';
|
|
2
2
|
declare const RadioTextContent: {
|
|
3
|
-
({ children, ...props }: FlexProps): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
({ children, style, ...props }: FlexProps): import("react/jsx-runtime").JSX.Element;
|
|
4
4
|
displayName: string;
|
|
5
5
|
};
|
|
6
6
|
export default RadioTextContent;
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
2
3
|
import { Flex } from '../Flex';
|
|
3
|
-
const RadioTextContent = ({ children, ...props }) => {
|
|
4
|
-
return (_jsx(Flex, { direction: "column", space: "none", ...props, children: children }));
|
|
4
|
+
const RadioTextContent = ({ children, style, ...props }) => {
|
|
5
|
+
return (_jsx(Flex, { direction: "column", space: "none", style: [styles.content, style], ...props, children: children }));
|
|
5
6
|
};
|
|
7
|
+
const styles = StyleSheet.create({
|
|
8
|
+
content: {
|
|
9
|
+
flex: 1,
|
|
10
|
+
flexShrink: 1,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
6
13
|
RadioTextContent.displayName = 'RadioTextContent';
|
|
7
14
|
export default RadioTextContent;
|
|
@@ -1,23 +1,6 @@
|
|
|
1
|
+
import { resolveThemeValue } from './themeValueHelpers';
|
|
1
2
|
export default function getFlattenedColorValue(value, colors) {
|
|
2
3
|
if (!value)
|
|
3
4
|
return undefined;
|
|
4
|
-
|
|
5
|
-
return colors?.[value];
|
|
6
|
-
}
|
|
7
|
-
// Extract trailing digits as shade
|
|
8
|
-
const shadeMatch = value.match(/\d+$/);
|
|
9
|
-
if (!shadeMatch)
|
|
10
|
-
return value;
|
|
11
|
-
const shade = shadeMatch[0];
|
|
12
|
-
const base = value.slice(0, -shade.length);
|
|
13
|
-
if (shade && typeof base !== 'string')
|
|
14
|
-
return value;
|
|
15
|
-
const colorEntry = colors?.[base];
|
|
16
|
-
if (typeof colorEntry === 'object') {
|
|
17
|
-
return (colorEntry[shade] ?? value);
|
|
18
|
-
}
|
|
19
|
-
else if (typeof colorEntry === 'string') {
|
|
20
|
-
return colorEntry;
|
|
21
|
-
}
|
|
22
|
-
return value;
|
|
5
|
+
return resolveThemeValue(value, colors);
|
|
23
6
|
}
|
package/build/utils/index.d.ts
CHANGED
package/build/utils/index.js
CHANGED
|
@@ -15,7 +15,3 @@ export declare const themeStyleMapping: {
|
|
|
15
15
|
* Set of all possible ViewStyle property names
|
|
16
16
|
*/
|
|
17
17
|
export declare const viewStyleProps: Set<string>;
|
|
18
|
-
/**
|
|
19
|
-
* Helper function to resolve a theme value
|
|
20
|
-
*/
|
|
21
|
-
export declare const resolveThemeValue: (value: any, themeMapping: any) => any;
|
|
@@ -218,53 +218,3 @@ export const viewStyleProps = new Set([
|
|
|
218
218
|
// Z-index
|
|
219
219
|
'zIndex',
|
|
220
220
|
]);
|
|
221
|
-
/**
|
|
222
|
-
* Helper function to convert camelCase back to nested object path
|
|
223
|
-
* e.g., feedbackDangerSurfaceDefault -> ['feedback', 'danger', 'surface', 'default']
|
|
224
|
-
*/
|
|
225
|
-
const camelCaseToPath = (camelCased) => {
|
|
226
|
-
// Split on uppercase letters but keep them
|
|
227
|
-
const parts = camelCased.split(/(?=[A-Z])/).map(part => part.toLowerCase());
|
|
228
|
-
return parts;
|
|
229
|
-
};
|
|
230
|
-
/**
|
|
231
|
-
* Helper function to get nested value from object using path array
|
|
232
|
-
*/
|
|
233
|
-
const getNestedValue = (obj, path) => {
|
|
234
|
-
return path.reduce((current, key) => {
|
|
235
|
-
return current && typeof current === 'object' ? current[key] : undefined;
|
|
236
|
-
}, obj);
|
|
237
|
-
};
|
|
238
|
-
/**
|
|
239
|
-
* Helper function to resolve a theme value
|
|
240
|
-
*/
|
|
241
|
-
export const resolveThemeValue = (value, themeMapping) => {
|
|
242
|
-
if (typeof value !== 'string' || !themeMapping || typeof themeMapping !== 'object') {
|
|
243
|
-
return value;
|
|
244
|
-
}
|
|
245
|
-
// First, try direct lookup for simple values
|
|
246
|
-
if (themeMapping[value] !== undefined) {
|
|
247
|
-
return themeMapping[value];
|
|
248
|
-
}
|
|
249
|
-
// Try camelCase to nested path conversion (e.g., feedbackDangerSurfaceDefault)
|
|
250
|
-
if (/^[a-z][a-zA-Z]*$/.test(value)) {
|
|
251
|
-
// Only camelCase strings without numbers
|
|
252
|
-
const path = camelCaseToPath(value);
|
|
253
|
-
const nestedValue = getNestedValue(themeMapping, path);
|
|
254
|
-
if (nestedValue !== undefined) {
|
|
255
|
-
return nestedValue;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
// Then, try the existing numeric suffix pattern (e.g., broadbandBlue100)
|
|
259
|
-
const shadeMatch = value.match(/\d+$/);
|
|
260
|
-
if (shadeMatch) {
|
|
261
|
-
const shade = shadeMatch[0];
|
|
262
|
-
const base = value.slice(0, -shade.length);
|
|
263
|
-
const nested = themeMapping[base];
|
|
264
|
-
if (nested && typeof nested === 'object') {
|
|
265
|
-
return nested[shade] ?? value;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
// If none of the approaches work, return the original value
|
|
269
|
-
return value;
|
|
270
|
-
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper function to convert camelCase back to nested object path
|
|
3
|
+
* e.g., feedbackDangerSurfaceDefault -> ['feedback', 'danger', 'surface', 'default']
|
|
4
|
+
*/
|
|
5
|
+
export declare const camelCaseToPath: (camelCased: string) => string[];
|
|
6
|
+
/**
|
|
7
|
+
* Helper function to get nested value from object using path array
|
|
8
|
+
*/
|
|
9
|
+
export declare const getNestedValue: (obj: any, path: string[]) => any;
|
|
10
|
+
/**
|
|
11
|
+
* Helper function to resolve a theme value
|
|
12
|
+
* Supports:
|
|
13
|
+
* - Direct lookup (value -> themeMapping[value])
|
|
14
|
+
* - Camel case to nested path (feedbackDangerSurfaceDefault -> themeMapping.feedback.danger.surface.default)
|
|
15
|
+
* - Numeric suffix pattern (broadbandBlue100 -> themeMapping.broadbandBlue[100])
|
|
16
|
+
*/
|
|
17
|
+
export declare const resolveThemeValue: (value: any, themeMapping: any) => any;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper function to convert camelCase back to nested object path
|
|
3
|
+
* e.g., feedbackDangerSurfaceDefault -> ['feedback', 'danger', 'surface', 'default']
|
|
4
|
+
*/
|
|
5
|
+
export const camelCaseToPath = (camelCased) => {
|
|
6
|
+
// Split on uppercase letters but keep them
|
|
7
|
+
const parts = camelCased.split(/(?=[A-Z])/).map(part => part.toLowerCase());
|
|
8
|
+
return parts;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Helper function to get nested value from object using path array
|
|
12
|
+
*/
|
|
13
|
+
export const getNestedValue = (obj, path) => {
|
|
14
|
+
return path.reduce((current, key) => {
|
|
15
|
+
return current && typeof current === 'object' ? current[key] : undefined;
|
|
16
|
+
}, obj);
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Helper function to resolve a theme value
|
|
20
|
+
* Supports:
|
|
21
|
+
* - Direct lookup (value -> themeMapping[value])
|
|
22
|
+
* - Camel case to nested path (feedbackDangerSurfaceDefault -> themeMapping.feedback.danger.surface.default)
|
|
23
|
+
* - Numeric suffix pattern (broadbandBlue100 -> themeMapping.broadbandBlue[100])
|
|
24
|
+
*/
|
|
25
|
+
export const resolveThemeValue = (value, themeMapping) => {
|
|
26
|
+
if (typeof value !== 'string' || !themeMapping || typeof themeMapping !== 'object') {
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
// First, try direct lookup for simple values
|
|
30
|
+
if (themeMapping[value] !== undefined) {
|
|
31
|
+
return themeMapping[value];
|
|
32
|
+
}
|
|
33
|
+
// Try camelCase to nested path conversion (e.g., feedbackDangerSurfaceDefault)
|
|
34
|
+
if (/^[a-z][a-zA-Z]*$/.test(value)) {
|
|
35
|
+
// Only camelCase strings without numbers
|
|
36
|
+
const path = camelCaseToPath(value);
|
|
37
|
+
const nestedValue = getNestedValue(themeMapping, path);
|
|
38
|
+
if (nestedValue !== undefined) {
|
|
39
|
+
return nestedValue;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Then, try the existing numeric suffix pattern (e.g., broadbandBlue100)
|
|
43
|
+
const shadeMatch = value.match(/\d+$/);
|
|
44
|
+
if (shadeMatch) {
|
|
45
|
+
const shade = shadeMatch[0];
|
|
46
|
+
const base = value.slice(0, -shade.length);
|
|
47
|
+
const nested = themeMapping[base];
|
|
48
|
+
if (nested && typeof nested === 'object') {
|
|
49
|
+
return nested[shade] ?? value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// If none of the approaches work, return the original value
|
|
53
|
+
return value;
|
|
54
|
+
};
|
package/docs/getting-started.mdx
CHANGED
|
@@ -7,15 +7,23 @@ import { BackToTopButton, NextPrevPage } from './components';
|
|
|
7
7
|
# Getting Started
|
|
8
8
|
|
|
9
9
|
You can just start using the components from the `@utilitywarehouse/hearth-react-native` package.
|
|
10
|
+
Although there are a few components that require a `BottomSheetModalProvider` to be wrapped around your app.
|
|
10
11
|
|
|
11
12
|
```tsx
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
Box,
|
|
15
|
+
Alert,
|
|
16
|
+
BodyText,
|
|
17
|
+
BottomSheetModalProvider,
|
|
18
|
+
} from '@utilitywarehouse/hearth-react-native';
|
|
13
19
|
|
|
14
20
|
const App: React.FC = () => (
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
<BottomSheetModalProvider>
|
|
22
|
+
<Box p="200">
|
|
23
|
+
<Alert variant="info" text="Welcome to Hearth React Native!" />
|
|
24
|
+
<BodyText>Start building your app with Hearth React Native components.</BodyText>
|
|
25
|
+
</Box>
|
|
26
|
+
</BottomSheetModalProvider>
|
|
19
27
|
);
|
|
20
28
|
```
|
|
21
29
|
|
package/docs/introduction.mdx
CHANGED
|
@@ -23,8 +23,11 @@ Hearth React Native is a comprehensive design system library for React Native, p
|
|
|
23
23
|
|
|
24
24
|
- [Installation](#installation)
|
|
25
25
|
- [Additional packages](#additional-packages)
|
|
26
|
+
- [Fonts](#fonts)
|
|
27
|
+
- [iOS Setup](#ios-setup)
|
|
26
28
|
- [Linking the fonts to be used in your project](#linking-the-fonts-to-be-used-in-your-project)
|
|
27
|
-
|
|
29
|
+
- [Android Setup](#android-setup)
|
|
30
|
+
- [Using Expo](#using-expo)
|
|
28
31
|
- [Babel configuration](#babel-configuration)
|
|
29
32
|
- [Jest configuration](#jest-configuration)
|
|
30
33
|
|
|
@@ -66,6 +69,8 @@ as well as fonts and icons. Feel free to additionally install these packages if
|
|
|
66
69
|
npm install @utilitywarehouse/hearth-tokens
|
|
67
70
|
```
|
|
68
71
|
|
|
72
|
+
## Fonts
|
|
73
|
+
|
|
69
74
|
You will need to include the **Comic Hams**, **DM Mono** and **DM Sans** fonts.
|
|
70
75
|
The easiest way to do this is to use the `@utilitywarehouse/hearth-fonts` package.
|
|
71
76
|
|
|
@@ -73,6 +78,14 @@ The easiest way to do this is to use the `@utilitywarehouse/hearth-fonts` packag
|
|
|
73
78
|
npm install @utilitywarehouse/hearth-fonts
|
|
74
79
|
```
|
|
75
80
|
|
|
81
|
+
### iOS Setup
|
|
82
|
+
|
|
83
|
+
For iOS, copy the font files to your project's assets folder:
|
|
84
|
+
|
|
85
|
+
```console
|
|
86
|
+
mkdir -p src/assets/fonts && cp ./node_modules/@utilitywarehouse/hearth-fonts/files/ttf/* src/assets/fonts/
|
|
87
|
+
```
|
|
88
|
+
|
|
76
89
|
Next, create or update your `react-native.config.js` file in the root directory and paste the code below inside it:
|
|
77
90
|
|
|
78
91
|
```js
|
|
@@ -81,7 +94,7 @@ module.exports = {
|
|
|
81
94
|
ios: {},
|
|
82
95
|
android: {},
|
|
83
96
|
},
|
|
84
|
-
assets: ['./
|
|
97
|
+
assets: ['./src/assets/fonts/'],
|
|
85
98
|
};
|
|
86
99
|
```
|
|
87
100
|
|
|
@@ -93,7 +106,36 @@ Now we need to link them so we'll be able to use them in any files inside the pr
|
|
|
93
106
|
npx react-native-asset
|
|
94
107
|
```
|
|
95
108
|
|
|
96
|
-
|
|
109
|
+
### Android Setup
|
|
110
|
+
|
|
111
|
+
For Android, you need to copy the fonts to two locations:
|
|
112
|
+
|
|
113
|
+
1. Copy TTF files to the assets folder:
|
|
114
|
+
|
|
115
|
+
```console
|
|
116
|
+
mkdir -p android/app/src/main/assets/fonts && cp ./node_modules/@utilitywarehouse/hearth-fonts/files/ttf/* android/app/src/main/assets/fonts/
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
2. Copy XML font family definitions to the resources folder:
|
|
120
|
+
|
|
121
|
+
```console
|
|
122
|
+
mkdir -p android/app/src/main/res/font && cp ./node_modules/@utilitywarehouse/hearth-fonts/files/xml/* android/app/src/main/res/font/
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
3. Update your `android/app/src/main/java/com/{yourapp}/MainApplication.kt` file to register the custom fonts:
|
|
126
|
+
|
|
127
|
+
```kotlin
|
|
128
|
+
import com.facebook.react.views.text.ReactFontManager;
|
|
129
|
+
|
|
130
|
+
// ... inside your onCreate() or initialization method:
|
|
131
|
+
ReactFontManager.getInstance().addCustomFont(this, "DM Sans", R.font.dm_sans)
|
|
132
|
+
ReactFontManager.getInstance().addCustomFont(this, "DM Mono", R.font.dm_mono)
|
|
133
|
+
ReactFontManager.getInstance().addCustomFont(this, "Comic Hams", R.font.comic_hams)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This approach ensures fonts work consistently across platforms with a single canonical name, as described in [this article](https://ospfranco.com/post/2023/08/11/react-native,-how-to-set-up-fonts-with-a-single-canonical-name/).
|
|
137
|
+
|
|
138
|
+
### Using Expo
|
|
97
139
|
|
|
98
140
|
If you are using Expo, you can use the `expo-font` and [`expo-xml-font`](https://github.com/Armster15/expo-xml-font) packages to load the fonts.
|
|
99
141
|
Here is an example of how to load the fonts in your `App.tsx` file:
|
|
@@ -131,7 +173,7 @@ You will need to add the following Babel plugin to your `babel.config.js` file:
|
|
|
131
173
|
const unistylesPluginOptions = {
|
|
132
174
|
autoProcessImports: ['@utilitywarehouse/hearth-react-native'],
|
|
133
175
|
autoProcessPaths: ['@utilitywarehouse/hearth-react-native'],
|
|
134
|
-
|
|
176
|
+
root: 'src', // Adjust this to your project's structure
|
|
135
177
|
debug: false, // Set to true for debugging purposes
|
|
136
178
|
};
|
|
137
179
|
|
|
@@ -152,7 +194,10 @@ If you are using Jest for testing, you will need to add the following configurat
|
|
|
152
194
|
```js
|
|
153
195
|
module.exports = {
|
|
154
196
|
// other Jest configurations...
|
|
155
|
-
setupFiles: [
|
|
197
|
+
setupFiles: [
|
|
198
|
+
'react-native-unistyles/mocks',
|
|
199
|
+
'./node_modules/react-native-gesture-handler/jestSetup.js',
|
|
200
|
+
],
|
|
156
201
|
// other Jest configurations...
|
|
157
202
|
};
|
|
158
203
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utilitywarehouse/hearth-react-native",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Utility Warehouse React Native UI library",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@vitest/coverage-v8": "^3.2.4",
|
|
38
38
|
"chromatic": "^13.3.0",
|
|
39
39
|
"eslint-plugin-storybook": "9.1.6",
|
|
40
|
-
"playwright": "^1.
|
|
40
|
+
"playwright": "^1.55.1",
|
|
41
41
|
"prismjs": "^1.30.0",
|
|
42
42
|
"react": "^19.1.0",
|
|
43
43
|
"react-native": "0.80.0",
|
|
@@ -53,10 +53,10 @@
|
|
|
53
53
|
"typescript": "^5.7.3",
|
|
54
54
|
"vite": "^7.1.3",
|
|
55
55
|
"vitest": "^3.2.4",
|
|
56
|
-
"@utilitywarehouse/hearth-fonts": "^0.0.3",
|
|
57
56
|
"@utilitywarehouse/hearth-react-icons": "^0.5.0",
|
|
57
|
+
"@utilitywarehouse/hearth-fonts": "^0.0.4",
|
|
58
58
|
"@utilitywarehouse/hearth-react-native-icons": "^0.5.0",
|
|
59
|
-
"@utilitywarehouse/hearth-tokens": "^0.1.
|
|
59
|
+
"@utilitywarehouse/hearth-tokens": "^0.1.3"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"@gorhom/bottom-sheet": "^5.0.0",
|
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
2
2
|
import { Flex } from '../Flex';
|
|
3
|
+
import FlexProps from '../Flex/Flex.props';
|
|
3
4
|
|
|
4
|
-
const CheckboxTextContent = ({ children, ...props }: FlexProps) => {
|
|
5
|
+
const CheckboxTextContent = ({ children, style, ...props }: FlexProps) => {
|
|
5
6
|
return (
|
|
6
|
-
<Flex direction="column" space="none" {...props}>
|
|
7
|
+
<Flex style={[styles.content, style]} direction="column" space="none" {...props}>
|
|
7
8
|
{children}
|
|
8
9
|
</Flex>
|
|
9
10
|
);
|
|
10
11
|
};
|
|
11
12
|
|
|
13
|
+
const styles = StyleSheet.create({
|
|
14
|
+
content: {
|
|
15
|
+
flex: 1,
|
|
16
|
+
flexShrink: 1,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
12
20
|
CheckboxTextContent.displayName = 'CheckboxTextContent';
|
|
13
21
|
|
|
14
22
|
export default CheckboxTextContent;
|
|
@@ -56,7 +56,7 @@ When using `CurrencyInput`, the component inherits React Native TextInput props
|
|
|
56
56
|
| focused | boolean | `false` | Forces the focused visual state |
|
|
57
57
|
| inBottomSheet | boolean | `false` | Use BottomSheetTextInput when true |
|
|
58
58
|
| placeholder | string | `'0.00'` | Placeholder text |
|
|
59
|
-
|
|
|
59
|
+
| disableGroupSeparator | boolean | `false` | Disables automatic inserting of thousand separators while the user types _(Formatting only works with controlled components via onTextChange)_ |
|
|
60
60
|
|
|
61
61
|
Note: When used inside `FormField`, `validationStatus` and `disabled` are read from the context unless explicitly overridden.
|
|
62
62
|
|
|
@@ -97,12 +97,12 @@ const MyComponent = () => {
|
|
|
97
97
|
|
|
98
98
|
## Formatting
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
Automatic thousand separator formatting can be disabled by setting `disableGroupSeparator`.
|
|
101
101
|
|
|
102
|
-
<Canvas of={Stories.
|
|
102
|
+
<Canvas of={Stories.DisableGroupSeparator} />
|
|
103
103
|
|
|
104
104
|
```tsx
|
|
105
|
-
<CurrencyInput
|
|
105
|
+
<CurrencyInput disableGroupSeparator value="1234567.89" /> // displays 1234567.89
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
Notes:
|
|
@@ -8,8 +8,8 @@ export interface CurrencyInputBaseProps {
|
|
|
8
8
|
placeholder?: string;
|
|
9
9
|
inBottomSheet?: boolean;
|
|
10
10
|
required?: boolean;
|
|
11
|
-
/** When
|
|
12
|
-
|
|
11
|
+
/** When not specifically disabled, the numeric value is automatically formatted with thousand separators (e.g. 1234 -> 1,234). */
|
|
12
|
+
disableGroupSeparator?: boolean;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export type CurrencyInputProps = CurrencyInputBaseProps &
|
|
@@ -37,10 +37,10 @@ const meta = {
|
|
|
37
37
|
description: 'Focused',
|
|
38
38
|
defaultValue: false,
|
|
39
39
|
},
|
|
40
|
-
|
|
40
|
+
disableGroupSeparator: {
|
|
41
41
|
control: 'boolean',
|
|
42
42
|
description:
|
|
43
|
-
'
|
|
43
|
+
'Disable automatic adding of thousand separators while typing _(Formatting only works with controlled components via onTextChange)_',
|
|
44
44
|
defaultValue: false,
|
|
45
45
|
},
|
|
46
46
|
},
|
|
@@ -50,26 +50,28 @@ const meta = {
|
|
|
50
50
|
disabled: false,
|
|
51
51
|
readonly: false,
|
|
52
52
|
focused: false,
|
|
53
|
-
|
|
53
|
+
disableGroupSeparator: false,
|
|
54
54
|
},
|
|
55
55
|
} satisfies Meta<typeof CurrencyInput>;
|
|
56
56
|
|
|
57
57
|
export default meta;
|
|
58
58
|
type Story = StoryObj<typeof meta>;
|
|
59
59
|
|
|
60
|
-
export const Playground: Story = {
|
|
60
|
+
export const Playground: Story = {
|
|
61
|
+
render: args => {
|
|
62
|
+
const [value, setValue] = useState('12345.67');
|
|
63
|
+
return <CurrencyInput {...args} value={value} onChangeText={setValue} />;
|
|
64
|
+
},
|
|
65
|
+
};
|
|
61
66
|
|
|
62
|
-
export const
|
|
67
|
+
export const DisableGroupSeparator: Story = {
|
|
63
68
|
parameters: {
|
|
64
|
-
controls: { include: ['
|
|
69
|
+
controls: { include: ['disableGroupSeparator'] },
|
|
65
70
|
},
|
|
66
|
-
args: {
|
|
71
|
+
args: { disableGroupSeparator: true },
|
|
67
72
|
render: args => {
|
|
68
|
-
const [value, setValue] = useState('
|
|
69
|
-
|
|
70
|
-
setValue(val);
|
|
71
|
-
};
|
|
72
|
-
return <CurrencyInput {...args} value={value} onChangeText={handleChange} />;
|
|
73
|
+
const [value, setValue] = useState('12345.67');
|
|
74
|
+
return <CurrencyInput {...args} value={value} onChangeText={setValue} />;
|
|
73
75
|
},
|
|
74
76
|
};
|
|
75
77
|
|
|
@@ -105,10 +107,10 @@ export const States: Story = {
|
|
|
105
107
|
<CurrencyInput disabled />
|
|
106
108
|
</VariantTitle>
|
|
107
109
|
<VariantTitle title="Readonly">
|
|
108
|
-
<CurrencyInput readonly />
|
|
110
|
+
<CurrencyInput readonly value="11666"/>
|
|
109
111
|
</VariantTitle>
|
|
110
|
-
<VariantTitle title="
|
|
111
|
-
<CurrencyInput
|
|
112
|
+
<VariantTitle title="Disable auto format thousands">
|
|
113
|
+
<CurrencyInput disableGroupSeparator value="1234.56" />
|
|
112
114
|
</VariantTitle>
|
|
113
115
|
</Flex>
|
|
114
116
|
);
|
|
@@ -14,7 +14,7 @@ const CurrencyInput = ({
|
|
|
14
14
|
placeholder,
|
|
15
15
|
inBottomSheet = false,
|
|
16
16
|
required,
|
|
17
|
-
|
|
17
|
+
disableGroupSeparator = false,
|
|
18
18
|
value,
|
|
19
19
|
onChangeText,
|
|
20
20
|
...rest
|
|
@@ -27,7 +27,7 @@ const CurrencyInput = ({
|
|
|
27
27
|
const getPlaceholder = placeholder ?? defaultFormat;
|
|
28
28
|
|
|
29
29
|
const handleChangeText = (text: string) => {
|
|
30
|
-
if (
|
|
30
|
+
if (!disableGroupSeparator) {
|
|
31
31
|
const formatted = formatThousands(text);
|
|
32
32
|
onChangeText?.(formatted);
|
|
33
33
|
} else {
|
|
@@ -36,7 +36,7 @@ const CurrencyInput = ({
|
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
const displayValue =
|
|
39
|
-
|
|
39
|
+
!disableGroupSeparator && typeof value === 'string' ? formatThousands(value) : value;
|
|
40
40
|
|
|
41
41
|
return (
|
|
42
42
|
<Input
|
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
2
2
|
import { Flex } from '../Flex';
|
|
3
|
+
import FlexProps from '../Flex/Flex.props';
|
|
3
4
|
|
|
4
|
-
const RadioTextContent = ({ children, ...props }: FlexProps) => {
|
|
5
|
+
const RadioTextContent = ({ children, style, ...props }: FlexProps) => {
|
|
5
6
|
return (
|
|
6
|
-
<Flex direction="column" space="none" {...props}>
|
|
7
|
+
<Flex direction="column" space="none" style={[styles.content, style]} {...props}>
|
|
7
8
|
{children}
|
|
8
9
|
</Flex>
|
|
9
10
|
);
|
|
10
11
|
};
|
|
11
12
|
|
|
13
|
+
const styles = StyleSheet.create({
|
|
14
|
+
content: {
|
|
15
|
+
flex: 1,
|
|
16
|
+
flexShrink: 1,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
12
20
|
RadioTextContent.displayName = 'RadioTextContent';
|
|
13
21
|
|
|
14
22
|
export default RadioTextContent;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ColorValue } from '../types';
|
|
2
|
+
import { resolveThemeValue } from './themeValueHelpers';
|
|
2
3
|
|
|
3
4
|
type ThemeColor = string | { [key: string]: string | ThemeColor };
|
|
4
5
|
|
|
@@ -8,25 +9,5 @@ export default function getFlattenedColorValue(
|
|
|
8
9
|
): ColorValue {
|
|
9
10
|
if (!value) return undefined;
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
return colors?.[value] as ColorValue;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Extract trailing digits as shade
|
|
16
|
-
const shadeMatch = value.match(/\d+$/);
|
|
17
|
-
|
|
18
|
-
if (!shadeMatch) return value as ColorValue;
|
|
19
|
-
const shade = shadeMatch[0];
|
|
20
|
-
const base = value.slice(0, -shade.length);
|
|
21
|
-
|
|
22
|
-
if (shade && typeof base !== 'string') return value as ColorValue;
|
|
23
|
-
|
|
24
|
-
const colorEntry = colors?.[base];
|
|
25
|
-
|
|
26
|
-
if (typeof colorEntry === 'object') {
|
|
27
|
-
return (colorEntry[shade] ?? value) as ColorValue;
|
|
28
|
-
} else if (typeof colorEntry === 'string') {
|
|
29
|
-
return colorEntry as ColorValue;
|
|
30
|
-
}
|
|
31
|
-
return value as ColorValue;
|
|
12
|
+
return resolveThemeValue(value, colors) as ColorValue;
|
|
32
13
|
}
|
package/src/utils/index.ts
CHANGED
package/src/utils/styleUtils.ts
CHANGED
|
@@ -231,60 +231,3 @@ export const viewStyleProps = new Set<string>([
|
|
|
231
231
|
// Z-index
|
|
232
232
|
'zIndex',
|
|
233
233
|
]);
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Helper function to convert camelCase back to nested object path
|
|
237
|
-
* e.g., feedbackDangerSurfaceDefault -> ['feedback', 'danger', 'surface', 'default']
|
|
238
|
-
*/
|
|
239
|
-
const camelCaseToPath = (camelCased: string): string[] => {
|
|
240
|
-
// Split on uppercase letters but keep them
|
|
241
|
-
const parts = camelCased.split(/(?=[A-Z])/).map(part => part.toLowerCase());
|
|
242
|
-
return parts;
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Helper function to get nested value from object using path array
|
|
247
|
-
*/
|
|
248
|
-
const getNestedValue = (obj: any, path: string[]): any => {
|
|
249
|
-
return path.reduce((current, key) => {
|
|
250
|
-
return current && typeof current === 'object' ? current[key] : undefined;
|
|
251
|
-
}, obj);
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Helper function to resolve a theme value
|
|
256
|
-
*/
|
|
257
|
-
export const resolveThemeValue = (value: any, themeMapping: any): any => {
|
|
258
|
-
if (typeof value !== 'string' || !themeMapping || typeof themeMapping !== 'object') {
|
|
259
|
-
return value;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// First, try direct lookup for simple values
|
|
263
|
-
if (themeMapping[value] !== undefined) {
|
|
264
|
-
return themeMapping[value];
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Try camelCase to nested path conversion (e.g., feedbackDangerSurfaceDefault)
|
|
268
|
-
if (/^[a-z][a-zA-Z]*$/.test(value)) {
|
|
269
|
-
// Only camelCase strings without numbers
|
|
270
|
-
const path = camelCaseToPath(value);
|
|
271
|
-
const nestedValue = getNestedValue(themeMapping, path);
|
|
272
|
-
if (nestedValue !== undefined) {
|
|
273
|
-
return nestedValue;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Then, try the existing numeric suffix pattern (e.g., broadbandBlue100)
|
|
278
|
-
const shadeMatch = value.match(/\d+$/);
|
|
279
|
-
if (shadeMatch) {
|
|
280
|
-
const shade = shadeMatch[0];
|
|
281
|
-
const base = value.slice(0, -shade.length);
|
|
282
|
-
const nested = themeMapping[base];
|
|
283
|
-
if (nested && typeof nested === 'object') {
|
|
284
|
-
return nested[shade] ?? value;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// If none of the approaches work, return the original value
|
|
289
|
-
return value;
|
|
290
|
-
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper function to convert camelCase back to nested object path
|
|
3
|
+
* e.g., feedbackDangerSurfaceDefault -> ['feedback', 'danger', 'surface', 'default']
|
|
4
|
+
*/
|
|
5
|
+
export const camelCaseToPath = (camelCased: string): string[] => {
|
|
6
|
+
// Split on uppercase letters but keep them
|
|
7
|
+
const parts = camelCased.split(/(?=[A-Z])/).map(part => part.toLowerCase());
|
|
8
|
+
return parts;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Helper function to get nested value from object using path array
|
|
13
|
+
*/
|
|
14
|
+
export const getNestedValue = (obj: any, path: string[]): any => {
|
|
15
|
+
return path.reduce((current, key) => {
|
|
16
|
+
return current && typeof current === 'object' ? current[key] : undefined;
|
|
17
|
+
}, obj);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Helper function to resolve a theme value
|
|
22
|
+
* Supports:
|
|
23
|
+
* - Direct lookup (value -> themeMapping[value])
|
|
24
|
+
* - Camel case to nested path (feedbackDangerSurfaceDefault -> themeMapping.feedback.danger.surface.default)
|
|
25
|
+
* - Numeric suffix pattern (broadbandBlue100 -> themeMapping.broadbandBlue[100])
|
|
26
|
+
*/
|
|
27
|
+
export const resolveThemeValue = (value: any, themeMapping: any): any => {
|
|
28
|
+
if (typeof value !== 'string' || !themeMapping || typeof themeMapping !== 'object') {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// First, try direct lookup for simple values
|
|
33
|
+
if (themeMapping[value] !== undefined) {
|
|
34
|
+
return themeMapping[value];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Try camelCase to nested path conversion (e.g., feedbackDangerSurfaceDefault)
|
|
38
|
+
if (/^[a-z][a-zA-Z]*$/.test(value)) {
|
|
39
|
+
// Only camelCase strings without numbers
|
|
40
|
+
const path = camelCaseToPath(value);
|
|
41
|
+
const nestedValue = getNestedValue(themeMapping, path);
|
|
42
|
+
if (nestedValue !== undefined) {
|
|
43
|
+
return nestedValue;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Then, try the existing numeric suffix pattern (e.g., broadbandBlue100)
|
|
48
|
+
const shadeMatch = value.match(/\d+$/);
|
|
49
|
+
if (shadeMatch) {
|
|
50
|
+
const shade = shadeMatch[0];
|
|
51
|
+
const base = value.slice(0, -shade.length);
|
|
52
|
+
const nested = themeMapping[base];
|
|
53
|
+
if (nested && typeof nested === 'object') {
|
|
54
|
+
return nested[shade] ?? value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// If none of the approaches work, return the original value
|
|
59
|
+
return value;
|
|
60
|
+
};
|