├── .watchmanconfig
├── assets
├── icon.png
└── splash.png
├── babel.config.js
├── .gitignore
├── .expo-shared
└── assets.json
├── components
└── CurrencyInput
│ ├── styles.ts
│ └── index.tsx
├── tsconfig.json
├── app.json
├── package.json
└── App.tsx
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larkintuckerllc/RNCurrencyInput/HEAD/assets/icon.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larkintuckerllc/RNCurrencyInput/HEAD/assets/splash.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/*
3 | npm-debug.*
4 | *.jks
5 | *.p8
6 | *.p12
7 | *.key
8 | *.mobileprovision
9 | *.orig.*
10 | web-build/
11 | web-report/
12 |
--------------------------------------------------------------------------------
/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd": true,
3 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true
4 | }
--------------------------------------------------------------------------------
/components/CurrencyInput/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create({
4 | input: {
5 | opacity: 0,
6 | },
7 | text: {
8 | marginBottom: 0,
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "jsx": "react-native",
5 | "lib": ["dom", "esnext"],
6 | "moduleResolution": "node",
7 | "noEmit": true,
8 | "skipLibCheck": true,
9 | "resolveJsonModule": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "RNCurrencyInput",
4 | "slug": "RNCurrencyInput",
5 | "privacy": "public",
6 | "sdkVersion": "35.0.0",
7 | "platforms": [
8 | "ios",
9 | "android",
10 | "web"
11 | ],
12 | "version": "1.0.0",
13 | "orientation": "portrait",
14 | "icon": "./assets/icon.png",
15 | "splash": {
16 | "image": "./assets/splash.png",
17 | "resizeMode": "contain",
18 | "backgroundColor": "#ffffff"
19 | },
20 | "updates": {
21 | "fallbackToCacheTimeout": 0
22 | },
23 | "assetBundlePatterns": [
24 | "**/*"
25 | ],
26 | "ios": {
27 | "supportsTablet": true
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "scripts": {
4 | "start": "expo start",
5 | "android": "expo start --android",
6 | "ios": "expo start --ios",
7 | "web": "expo start --web",
8 | "eject": "expo eject"
9 | },
10 | "dependencies": {
11 | "expo": "^35.0.0",
12 | "react": "16.8.3",
13 | "react-dom": "16.8.3",
14 | "react-native": "https://github.com/expo/react-native/archive/sdk-35.0.0.tar.gz",
15 | "react-native-web": "^0.11.7"
16 | },
17 | "devDependencies": {
18 | "@types/react": "^16.8.23",
19 | "@types/react-native": "^0.57.65",
20 | "babel-preset-expo": "^7.0.0",
21 | "typescript": "^3.4.5"
22 | },
23 | "private": true
24 | }
25 |
--------------------------------------------------------------------------------
/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState } from 'react';
2 | import { StyleSheet, Text, TextInput, View } from 'react-native';
3 | import CurrencyInput from './components/CurrencyInput';
4 |
5 | export default function App() {
6 | const [value, setValue] = useState(0);
7 | const handleValueChange = useCallback(val => {
8 | // eslint-disable-next-line
9 | console.log(val);
10 | setValue(val);
11 | }, []);
12 |
13 |
14 | return (
15 |
16 | Open up App.tsx to start working on your app!
17 |
23 | Validating bottom margin.
24 |
25 | );
26 | }
27 |
28 | const styles = StyleSheet.create({
29 | container: {
30 | flex: 1,
31 | backgroundColor: '#fff',
32 | alignItems: 'center',
33 | justifyContent: 'center',
34 | },
35 | input: {
36 | backgroundColor: 'white',
37 | borderColor: 'gray',
38 | borderWidth: 1,
39 | fontSize: 20,
40 | marginBottom: 20,
41 | marginTop: 20,
42 | padding: 20,
43 | textAlign: 'right',
44 | width: 300,
45 | },
46 | });
47 |
--------------------------------------------------------------------------------
/components/CurrencyInput/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useCallback, useState } from 'react';
2 | import { LayoutChangeEvent, StyleProp, StyleSheet, Text, TextStyle, TextInput } from 'react-native';
3 | import styles from './styles';
4 |
5 | const VALID = /^[1-9]{1}[0-9]*$/;
6 |
7 | interface Props {
8 | max?: number;
9 | onValueChange: (value: number) => void;
10 | style?: StyleProp;
11 | value: number;
12 | }
13 |
14 | const CurrencyInput: FC = ({
15 | max = Number.MAX_SAFE_INTEGER,
16 | onValueChange,
17 | style = { marginBottom: 0 },
18 | value
19 | }) => {
20 | const valueAbsTrunc = Math.trunc(Math.abs(value));
21 | if (value !== valueAbsTrunc || !Number.isFinite(value) || Number.isNaN(value)) {
22 | throw new Error(`invalid value property`);
23 | }
24 | const [inputHeight, setInputHeight] = useState(0);
25 | const [inputWidth, setInputWidth] = useState(0);
26 | const handleChangeText = useCallback((text: string) => {
27 | if (text === '') {
28 | onValueChange(0);
29 | return;
30 | }
31 | if (!VALID.test(text)) {
32 | return;
33 | }
34 | const nextValue = parseInt(text, 10);
35 | if (nextValue > max) {
36 | return;
37 | }
38 | onValueChange(nextValue);
39 | }, []);
40 | const handleLayout = useCallback((event: LayoutChangeEvent) => {
41 | const { height, width } = event.nativeEvent.layout;
42 | setInputHeight(height);
43 | setInputWidth(width);
44 | }, []);
45 | const valueInput = value === 0 ? '' : value.toString();
46 | const valueDisplay = (value / 100).toLocaleString('en-US', { style: 'currency', currency: 'USD' });
47 | const { marginBottom } = StyleSheet.flatten(style);
48 |
49 | return (
50 | <>
51 |
52 | {valueDisplay}
53 |
54 |
69 | >
70 | );
71 | };
72 |
73 | export default CurrencyInput;
74 |
--------------------------------------------------------------------------------