@startupjs-ui/number-input 0.1.3
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/CHANGELOG.md +20 -0
- package/README.mdx +207 -0
- package/buttons.tsx +54 -0
- package/index.cssx.styl +87 -0
- package/index.d.ts +48 -0
- package/index.tsx +248 -0
- package/package.json +23 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
## [0.1.3](https://github.com/startupjs/startupjs-ui/compare/v0.1.2...v0.1.3) (2025-12-29)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @startupjs-ui/number-input
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.1.2](https://github.com/startupjs/startupjs-ui/compare/v0.1.1...v0.1.2) (2025-12-29)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* add mdx and docs packages. Refactor docs to get rid of any @startupjs/ui usage and use startupjs-ui instead ([703c926](https://github.com/startupjs/startupjs-ui/commit/703c92636efb0421ffd11783f692fc892b74018f))
|
|
20
|
+
* **number-input:** refactor NumberInput component ([61f01c3](https://github.com/startupjs/startupjs-ui/commit/61f01c370cb12ab7c9aa110e829c2e8a08ff3f2b))
|
package/README.mdx
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import NumberInput, { _PropsJsonSchema as NumberInputPropsJsonSchema } from './index'
|
|
3
|
+
import Div from '@startupjs-ui/div'
|
|
4
|
+
import Br from '@startupjs-ui/br'
|
|
5
|
+
import { Sandbox } from '@startupjs-ui/docs'
|
|
6
|
+
|
|
7
|
+
# NumberInput
|
|
8
|
+
|
|
9
|
+
NumberInput allows user to enter or edit number.
|
|
10
|
+
|
|
11
|
+
```jsx
|
|
12
|
+
import { NumberInput } from 'startupjs-ui'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Simple example
|
|
16
|
+
|
|
17
|
+
```jsx example
|
|
18
|
+
const [value, setValue] = useState()
|
|
19
|
+
return (
|
|
20
|
+
<NumberInput
|
|
21
|
+
value={value}
|
|
22
|
+
onChangeNumber={setValue}
|
|
23
|
+
/>
|
|
24
|
+
)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Disabled
|
|
28
|
+
|
|
29
|
+
```jsx example
|
|
30
|
+
return (
|
|
31
|
+
<NumberInput
|
|
32
|
+
disabled
|
|
33
|
+
value={10}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Readonly
|
|
39
|
+
|
|
40
|
+
```jsx example
|
|
41
|
+
return (
|
|
42
|
+
<NumberInput
|
|
43
|
+
readonly
|
|
44
|
+
value={10}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Step
|
|
50
|
+
|
|
51
|
+
The step specifies how many decimal places can be entered, set by the numbers 1, 0.1, 0.01, etc. in the `step` property (1 by default).
|
|
52
|
+
|
|
53
|
+
```jsx example
|
|
54
|
+
const [value, setValue] = useState()
|
|
55
|
+
return (
|
|
56
|
+
<Div>
|
|
57
|
+
<NumberInput
|
|
58
|
+
value={value}
|
|
59
|
+
step={0.001}
|
|
60
|
+
onChangeNumber={setValue}
|
|
61
|
+
/>
|
|
62
|
+
</Div>
|
|
63
|
+
)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Minimum and maximum value
|
|
67
|
+
|
|
68
|
+
The minimum and maximum values are set by the `min` and `max` properties
|
|
69
|
+
|
|
70
|
+
```jsx example
|
|
71
|
+
const [value, setValue] = useState()
|
|
72
|
+
return (
|
|
73
|
+
<Div>
|
|
74
|
+
<NumberInput
|
|
75
|
+
value={value}
|
|
76
|
+
min={-30}
|
|
77
|
+
max={20}
|
|
78
|
+
onChangeNumber={setValue}
|
|
79
|
+
/>
|
|
80
|
+
</Div>
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Buttons mode
|
|
85
|
+
|
|
86
|
+
Buttons allow user to change the number at the specified step. The position of the buttons can be changed by passing the `buttonsMode` property with the value `vertical` or `horizontal` (`vertical` by default) to the component or hidden by passing the `buttonsMode` property with the value `none` to the component.
|
|
87
|
+
|
|
88
|
+
```jsx example
|
|
89
|
+
const [valueVertical, setValueVertical] = useState()
|
|
90
|
+
const [valueHorizontal, setValueHorizontal] = useState()
|
|
91
|
+
const [valueNone, setValueNone] = useState()
|
|
92
|
+
return (
|
|
93
|
+
<Div>
|
|
94
|
+
<NumberInput
|
|
95
|
+
buttonsMode='vertical'
|
|
96
|
+
value={valueVertical}
|
|
97
|
+
onChangeNumber={setValueVertical}
|
|
98
|
+
/>
|
|
99
|
+
<Br />
|
|
100
|
+
<NumberInput
|
|
101
|
+
buttonsMode='horizontal'
|
|
102
|
+
value={valueHorizontal}
|
|
103
|
+
onChangeNumber={setValueHorizontal}
|
|
104
|
+
/>
|
|
105
|
+
<Br />
|
|
106
|
+
<NumberInput
|
|
107
|
+
buttonsMode='none'
|
|
108
|
+
value={valueNone}
|
|
109
|
+
onChangeNumber={setValueNone}
|
|
110
|
+
/>
|
|
111
|
+
</Div>
|
|
112
|
+
)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Sizes
|
|
116
|
+
|
|
117
|
+
Size can be modified using the `size` prop. Default size is `'m'`.
|
|
118
|
+
|
|
119
|
+
```jsx example
|
|
120
|
+
const [valueL, setValueL] = useState()
|
|
121
|
+
const [valueM, setValueM] = useState()
|
|
122
|
+
const [valueS, setValueS] = useState()
|
|
123
|
+
return (
|
|
124
|
+
<Div>
|
|
125
|
+
<NumberInput
|
|
126
|
+
size='s'
|
|
127
|
+
value={valueS}
|
|
128
|
+
onChangeNumber={setValueS}
|
|
129
|
+
/>
|
|
130
|
+
<Br />
|
|
131
|
+
<NumberInput
|
|
132
|
+
size='m'
|
|
133
|
+
value={valueM}
|
|
134
|
+
onChangeNumber={setValueM}
|
|
135
|
+
/>
|
|
136
|
+
<Br />
|
|
137
|
+
<NumberInput
|
|
138
|
+
size='l'
|
|
139
|
+
value={valueL}
|
|
140
|
+
onChangeNumber={setValueL}
|
|
141
|
+
/>
|
|
142
|
+
</Div>
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Units
|
|
147
|
+
|
|
148
|
+
The `units` property displays the units of the input. By default, units are displayed to the left of the input. Use `unitsPosition='right'` to display the units on the right side of the input.
|
|
149
|
+
|
|
150
|
+
```jsx example
|
|
151
|
+
const [value, setValue] = useState()
|
|
152
|
+
return (
|
|
153
|
+
<NumberInput
|
|
154
|
+
value={value}
|
|
155
|
+
units='$'
|
|
156
|
+
onChangeNumber={setValue}
|
|
157
|
+
/>
|
|
158
|
+
)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Stylization
|
|
162
|
+
|
|
163
|
+
For stylization can apply the properties:
|
|
164
|
+
|
|
165
|
+
- `style` - to style the root component
|
|
166
|
+
- `inputStyle` - to style the input
|
|
167
|
+
- `buttonStyle` - for styling buttons
|
|
168
|
+
|
|
169
|
+
```jsx example
|
|
170
|
+
const [value, setValue] = useState()
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<NumberInput
|
|
174
|
+
style={{
|
|
175
|
+
width: 250
|
|
176
|
+
}}
|
|
177
|
+
buttonStyle={{
|
|
178
|
+
borderWidth: 0
|
|
179
|
+
}}
|
|
180
|
+
inputStyle={{
|
|
181
|
+
borderTopWidth: 0,
|
|
182
|
+
borderLeftWidth: 0,
|
|
183
|
+
borderRightWidth: 0,
|
|
184
|
+
borderRadius: 0
|
|
185
|
+
}}
|
|
186
|
+
placeholder='Enter number'
|
|
187
|
+
value={value}
|
|
188
|
+
onChangeNumber={setValue}
|
|
189
|
+
/>
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Sandbox
|
|
194
|
+
|
|
195
|
+
<Sandbox
|
|
196
|
+
Component={NumberInput}
|
|
197
|
+
propsJsonSchema={NumberInputPropsJsonSchema}
|
|
198
|
+
extraParams={{
|
|
199
|
+
step: {
|
|
200
|
+
step: 0.0001
|
|
201
|
+
},
|
|
202
|
+
value: {
|
|
203
|
+
step: 0.0001
|
|
204
|
+
}
|
|
205
|
+
}}
|
|
206
|
+
block
|
|
207
|
+
/>
|
package/buttons.tsx
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type ReactNode } from 'react'
|
|
2
|
+
import { type StyleProp, type ViewStyle } from 'react-native'
|
|
3
|
+
import { pug, observer } from 'startupjs'
|
|
4
|
+
import { themed } from '@startupjs-ui/core'
|
|
5
|
+
import Button from '@startupjs-ui/button'
|
|
6
|
+
import { faAngleDown } from '@fortawesome/free-solid-svg-icons/faAngleDown'
|
|
7
|
+
import { faAngleUp } from '@fortawesome/free-solid-svg-icons/faAngleUp'
|
|
8
|
+
import { faMinus } from '@fortawesome/free-solid-svg-icons/faMinus'
|
|
9
|
+
import { faPlus } from '@fortawesome/free-solid-svg-icons/faPlus'
|
|
10
|
+
import './index.cssx.styl'
|
|
11
|
+
|
|
12
|
+
interface NumberInputButtonsProps {
|
|
13
|
+
buttonStyle?: StyleProp<ViewStyle>
|
|
14
|
+
mode?: 'none' | 'horizontal' | 'vertical'
|
|
15
|
+
size?: 'l' | 'm' | 's'
|
|
16
|
+
disabled?: boolean
|
|
17
|
+
onIncrement?: (value: number) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function NumberInputButtons ({
|
|
21
|
+
buttonStyle,
|
|
22
|
+
mode,
|
|
23
|
+
size,
|
|
24
|
+
disabled,
|
|
25
|
+
onIncrement
|
|
26
|
+
}: NumberInputButtonsProps): ReactNode {
|
|
27
|
+
const buttonStyleNames = [mode]
|
|
28
|
+
|
|
29
|
+
return pug`
|
|
30
|
+
if mode !== 'none'
|
|
31
|
+
Button.input-button.increase(
|
|
32
|
+
style=buttonStyle
|
|
33
|
+
styleName=buttonStyleNames
|
|
34
|
+
focusable=false
|
|
35
|
+
disabled=disabled
|
|
36
|
+
icon=mode === 'horizontal' ? faPlus : faAngleUp
|
|
37
|
+
size=size
|
|
38
|
+
variant='outlined'
|
|
39
|
+
onPress=() => onIncrement?.(1)
|
|
40
|
+
)
|
|
41
|
+
Button.input-button.decrease(
|
|
42
|
+
style=buttonStyle
|
|
43
|
+
styleName=buttonStyleNames
|
|
44
|
+
focusable=false
|
|
45
|
+
disabled=disabled
|
|
46
|
+
icon=mode === 'horizontal' ? faMinus : faAngleDown
|
|
47
|
+
size=size
|
|
48
|
+
variant='outlined'
|
|
49
|
+
onPress=() => onIncrement?.(-1)
|
|
50
|
+
)
|
|
51
|
+
`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default observer(themed('NumberInput', NumberInputButtons))
|
package/index.cssx.styl
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
$sizes = 'l' 'm' 's'
|
|
2
|
+
// border color should be taken from text input config
|
|
3
|
+
// because the input border and the button border overlap each other
|
|
4
|
+
$inputBorderColor = var(--color-border-main)
|
|
5
|
+
$padding = {
|
|
6
|
+
l: 6u,
|
|
7
|
+
m: 5u,
|
|
8
|
+
s: 4u
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.input-wrapper
|
|
12
|
+
flex-grow 1
|
|
13
|
+
|
|
14
|
+
&.right
|
|
15
|
+
flex-direction row-reverse
|
|
16
|
+
|
|
17
|
+
&.readonly
|
|
18
|
+
align-self flex-start
|
|
19
|
+
|
|
20
|
+
.input-units
|
|
21
|
+
align-self center
|
|
22
|
+
|
|
23
|
+
&.l
|
|
24
|
+
font(body1)
|
|
25
|
+
|
|
26
|
+
&.readonly
|
|
27
|
+
&.left
|
|
28
|
+
margin-right 0.5u
|
|
29
|
+
|
|
30
|
+
&.right
|
|
31
|
+
margin-left 0.5u
|
|
32
|
+
|
|
33
|
+
.input-container
|
|
34
|
+
flex-grow 1
|
|
35
|
+
flex-shrink 1
|
|
36
|
+
|
|
37
|
+
&.left
|
|
38
|
+
margin-left 1u
|
|
39
|
+
|
|
40
|
+
&.right
|
|
41
|
+
margin-right 1u
|
|
42
|
+
|
|
43
|
+
.input-input
|
|
44
|
+
for size in $sizes
|
|
45
|
+
padding = $padding[size]
|
|
46
|
+
|
|
47
|
+
&.vertical.{size}
|
|
48
|
+
padding-right padding
|
|
49
|
+
|
|
50
|
+
&.horizontal.{size}
|
|
51
|
+
padding-left padding
|
|
52
|
+
padding-right @padding-left
|
|
53
|
+
|
|
54
|
+
.input-button
|
|
55
|
+
position absolute
|
|
56
|
+
border-color $inputBorderColor
|
|
57
|
+
|
|
58
|
+
&.vertical
|
|
59
|
+
right 0
|
|
60
|
+
height 50%
|
|
61
|
+
|
|
62
|
+
&.increase
|
|
63
|
+
top 0
|
|
64
|
+
border-top-left-radius 0
|
|
65
|
+
border-bottom-left-radius 0
|
|
66
|
+
border-bottom-right-radius 0
|
|
67
|
+
|
|
68
|
+
&.decrease
|
|
69
|
+
bottom 0
|
|
70
|
+
border-top-left-radius 0
|
|
71
|
+
border-bottom-left-radius 0
|
|
72
|
+
border-top-right-radius 0
|
|
73
|
+
|
|
74
|
+
&.horizontal
|
|
75
|
+
top 0
|
|
76
|
+
bottom 0
|
|
77
|
+
height auto
|
|
78
|
+
|
|
79
|
+
&.increase
|
|
80
|
+
right 0
|
|
81
|
+
border-top-left-radius 0
|
|
82
|
+
border-bottom-left-radius 0
|
|
83
|
+
|
|
84
|
+
&.decrease
|
|
85
|
+
left 0
|
|
86
|
+
border-top-right-radius 0
|
|
87
|
+
border-bottom-right-radius 0
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// DO NOT MODIFY THIS FILE - IT IS AUTOMATICALLY GENERATED ON COMMITS.
|
|
3
|
+
|
|
4
|
+
import { type RefObject } from 'react';
|
|
5
|
+
import { type StyleProp, type TextStyle, type ViewStyle } from 'react-native';
|
|
6
|
+
import './index.cssx.styl';
|
|
7
|
+
export declare const _PropsJsonSchema: {};
|
|
8
|
+
export interface NumberInputProps {
|
|
9
|
+
/** Custom styles for the wrapper */
|
|
10
|
+
style?: StyleProp<ViewStyle>;
|
|
11
|
+
/** Custom styles for increment and decrement buttons */
|
|
12
|
+
buttonStyle?: StyleProp<ViewStyle>;
|
|
13
|
+
/** Current numeric value */
|
|
14
|
+
value?: number;
|
|
15
|
+
/** Input size preset @default 'm' */
|
|
16
|
+
size?: 'l' | 'm' | 's';
|
|
17
|
+
/** Buttons layout @default 'vertical' */
|
|
18
|
+
buttonsMode?: 'none' | 'horizontal' | 'vertical';
|
|
19
|
+
/** Disable interactions @default false */
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
/** Render a non-editable value @default false */
|
|
22
|
+
readonly?: boolean;
|
|
23
|
+
/** Maximum allowed value */
|
|
24
|
+
max?: number;
|
|
25
|
+
/** Minimum allowed value */
|
|
26
|
+
min?: number;
|
|
27
|
+
/** Increment step @default 1 */
|
|
28
|
+
step?: number;
|
|
29
|
+
/** Units label displayed next to the value */
|
|
30
|
+
units?: string;
|
|
31
|
+
/** Units position @default 'left' */
|
|
32
|
+
unitsPosition?: 'left' | 'right';
|
|
33
|
+
/** Return key type for the keyboard @default 'done' */
|
|
34
|
+
returnKeyType?: string;
|
|
35
|
+
/** Handler triggered when numeric value changes */
|
|
36
|
+
onChangeNumber?: (value?: number) => void;
|
|
37
|
+
/** Ref to access the underlying TextInput */
|
|
38
|
+
ref?: RefObject<any>;
|
|
39
|
+
/** Custom styles for the input element */
|
|
40
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
41
|
+
/** Placeholder text */
|
|
42
|
+
placeholder?: string | number;
|
|
43
|
+
/** Error flag @private */
|
|
44
|
+
_hasError?: boolean;
|
|
45
|
+
[key: string]: any;
|
|
46
|
+
}
|
|
47
|
+
declare const _default: import("react").ComponentType<NumberInputProps>;
|
|
48
|
+
export default _default;
|
package/index.tsx
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useMemo,
|
|
4
|
+
useState,
|
|
5
|
+
useRef,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
type RefObject
|
|
8
|
+
} from 'react'
|
|
9
|
+
import {
|
|
10
|
+
Platform,
|
|
11
|
+
type StyleProp,
|
|
12
|
+
type TextStyle,
|
|
13
|
+
type ViewStyle
|
|
14
|
+
} from 'react-native'
|
|
15
|
+
import { pug, observer } from 'startupjs'
|
|
16
|
+
import { themed } from '@startupjs-ui/core'
|
|
17
|
+
import Div from '@startupjs-ui/div'
|
|
18
|
+
import Span from '@startupjs-ui/span'
|
|
19
|
+
import TextInput from '@startupjs-ui/text-input'
|
|
20
|
+
import Buttons from './buttons'
|
|
21
|
+
import './index.cssx.styl'
|
|
22
|
+
|
|
23
|
+
const IS_IOS = Platform.OS === 'ios'
|
|
24
|
+
|
|
25
|
+
export const _PropsJsonSchema = {/* NumberInputProps */}
|
|
26
|
+
|
|
27
|
+
export interface NumberInputProps {
|
|
28
|
+
/** Custom styles for the wrapper */
|
|
29
|
+
style?: StyleProp<ViewStyle>
|
|
30
|
+
/** Custom styles for increment and decrement buttons */
|
|
31
|
+
buttonStyle?: StyleProp<ViewStyle>
|
|
32
|
+
/** Current numeric value */
|
|
33
|
+
value?: number
|
|
34
|
+
/** Input size preset @default 'm' */
|
|
35
|
+
size?: 'l' | 'm' | 's'
|
|
36
|
+
/** Buttons layout @default 'vertical' */
|
|
37
|
+
buttonsMode?: 'none' | 'horizontal' | 'vertical'
|
|
38
|
+
/** Disable interactions @default false */
|
|
39
|
+
disabled?: boolean
|
|
40
|
+
/** Render a non-editable value @default false */
|
|
41
|
+
readonly?: boolean
|
|
42
|
+
/** Maximum allowed value */
|
|
43
|
+
max?: number
|
|
44
|
+
/** Minimum allowed value */
|
|
45
|
+
min?: number
|
|
46
|
+
/** Increment step @default 1 */
|
|
47
|
+
step?: number
|
|
48
|
+
/** Units label displayed next to the value */
|
|
49
|
+
units?: string
|
|
50
|
+
/** Units position @default 'left' */
|
|
51
|
+
unitsPosition?: 'left' | 'right'
|
|
52
|
+
/** Return key type for the keyboard @default 'done' */
|
|
53
|
+
returnKeyType?: string
|
|
54
|
+
/** Handler triggered when numeric value changes */
|
|
55
|
+
onChangeNumber?: (value?: number) => void
|
|
56
|
+
/** Ref to access the underlying TextInput */
|
|
57
|
+
ref?: RefObject<any>
|
|
58
|
+
/** Custom styles for the input element */
|
|
59
|
+
inputStyle?: StyleProp<TextStyle>
|
|
60
|
+
/** Placeholder text */
|
|
61
|
+
placeholder?: string | number
|
|
62
|
+
/** Error flag @private */
|
|
63
|
+
_hasError?: boolean
|
|
64
|
+
[key: string]: any
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function NumberInput ({
|
|
68
|
+
style,
|
|
69
|
+
buttonStyle,
|
|
70
|
+
value,
|
|
71
|
+
size = 'm',
|
|
72
|
+
buttonsMode = 'vertical',
|
|
73
|
+
disabled = false,
|
|
74
|
+
readonly = false,
|
|
75
|
+
max,
|
|
76
|
+
min,
|
|
77
|
+
step = 1,
|
|
78
|
+
units,
|
|
79
|
+
unitsPosition = 'left',
|
|
80
|
+
onChangeNumber,
|
|
81
|
+
returnKeyType = 'done',
|
|
82
|
+
ref,
|
|
83
|
+
...props
|
|
84
|
+
}: NumberInputProps): ReactNode {
|
|
85
|
+
const [inputValue, setInputValue] = useState<string | undefined>(undefined)
|
|
86
|
+
const inputValueRef = useRef<string | undefined>(undefined)
|
|
87
|
+
|
|
88
|
+
const precision = useMemo(() => {
|
|
89
|
+
return String(step).split('.')?.[1]?.length || 0
|
|
90
|
+
}, [step])
|
|
91
|
+
|
|
92
|
+
const regexp = useMemo(() => {
|
|
93
|
+
return precision > 0
|
|
94
|
+
? new RegExp('^-?\\d*(\\.(\\d{0,' + precision + '})?)?$')
|
|
95
|
+
: /^-?\d*$/
|
|
96
|
+
}, [precision])
|
|
97
|
+
|
|
98
|
+
function updateInputValue (newValue: string | undefined) {
|
|
99
|
+
inputValueRef.current = newValue
|
|
100
|
+
setInputValue(newValue)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (value == null) {
|
|
105
|
+
updateInputValue('')
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!isNaN(value) && Number(inputValueRef.current) !== value) {
|
|
110
|
+
let nextValue = value
|
|
111
|
+
if (min != null && nextValue < min) {
|
|
112
|
+
nextValue = min
|
|
113
|
+
} else if (max != null && nextValue > max) {
|
|
114
|
+
nextValue = max
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
nextValue = +nextValue.toFixed(precision)
|
|
118
|
+
|
|
119
|
+
updateInputValue(String(nextValue))
|
|
120
|
+
onChangeNumber && onChangeNumber(nextValue)
|
|
121
|
+
}
|
|
122
|
+
}, [value, min, max, precision, onChangeNumber])
|
|
123
|
+
|
|
124
|
+
function onChangeText (newValue: string) {
|
|
125
|
+
let formattedValue = newValue
|
|
126
|
+
// replace comma with dot for some locales
|
|
127
|
+
if (precision > 0) formattedValue = formattedValue.replace(/,/g, '.')
|
|
128
|
+
|
|
129
|
+
if (!regexp.test(formattedValue)) return
|
|
130
|
+
|
|
131
|
+
let updateValue: number | undefined
|
|
132
|
+
// check for an empty string and undefined
|
|
133
|
+
// and check for strings '-' or '.'
|
|
134
|
+
// to convert newValue to number
|
|
135
|
+
// otherwise should value should be undefined
|
|
136
|
+
if (formattedValue && !isNaN(Number(formattedValue))) {
|
|
137
|
+
const numericValue = Number(formattedValue)
|
|
138
|
+
|
|
139
|
+
if ((min != null && numericValue < min) || (max != null && numericValue > max)) {
|
|
140
|
+
// TODO: display tip?
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
updateValue = numericValue
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
updateInputValue(formattedValue)
|
|
148
|
+
|
|
149
|
+
// prevent update for the same values
|
|
150
|
+
// for example
|
|
151
|
+
// when add dot (values NUMBER and NUMBER. are the same)
|
|
152
|
+
// when add -
|
|
153
|
+
// when change value from -NUMBER to -
|
|
154
|
+
if (updateValue === value) return
|
|
155
|
+
|
|
156
|
+
onChangeNumber && onChangeNumber(updateValue)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function onIncrement (byNumber: number) {
|
|
160
|
+
const newValue = +((value ?? 0) + byNumber * step).toFixed(precision)
|
|
161
|
+
// we use string because this is the value for TextInput
|
|
162
|
+
onChangeText(String(newValue))
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getReturnKeyType (): string | undefined {
|
|
166
|
+
let res
|
|
167
|
+
|
|
168
|
+
if (IS_IOS && returnKeyType === 'none') {
|
|
169
|
+
res = 'default'
|
|
170
|
+
} else {
|
|
171
|
+
res = returnKeyType
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return res
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const extraStyleName: Record<string, any> = {}
|
|
178
|
+
|
|
179
|
+
if (units) {
|
|
180
|
+
extraStyleName[unitsPosition] = unitsPosition
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const renderWrapper = (
|
|
184
|
+
{ style: wrapperStyle }: { style?: StyleProp<ViewStyle> },
|
|
185
|
+
children: ReactNode
|
|
186
|
+
): ReactNode => {
|
|
187
|
+
return pug`
|
|
188
|
+
Div(style=wrapperStyle)
|
|
189
|
+
Div.input-wrapper(
|
|
190
|
+
styleName=[extraStyleName, { readonly }]
|
|
191
|
+
row
|
|
192
|
+
)
|
|
193
|
+
if units
|
|
194
|
+
Span.input-units(
|
|
195
|
+
styleName=[size, extraStyleName, { readonly }]
|
|
196
|
+
)
|
|
197
|
+
= units
|
|
198
|
+
= children
|
|
199
|
+
`
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (readonly) {
|
|
203
|
+
return renderWrapper({
|
|
204
|
+
style: [style]
|
|
205
|
+
}, pug`
|
|
206
|
+
Span= value
|
|
207
|
+
`)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function renderInputWrapper (
|
|
211
|
+
wrapperProps: { style?: StyleProp<ViewStyle> },
|
|
212
|
+
children: ReactNode
|
|
213
|
+
): ReactNode {
|
|
214
|
+
return renderWrapper(
|
|
215
|
+
wrapperProps,
|
|
216
|
+
pug`
|
|
217
|
+
Div.input-container(styleName=[extraStyleName])
|
|
218
|
+
Buttons(
|
|
219
|
+
buttonStyle=buttonStyle
|
|
220
|
+
mode=buttonsMode
|
|
221
|
+
size=size
|
|
222
|
+
disabled=disabled
|
|
223
|
+
onIncrement=onIncrement
|
|
224
|
+
)
|
|
225
|
+
= children
|
|
226
|
+
`)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return pug`
|
|
230
|
+
TextInput(
|
|
231
|
+
style=style
|
|
232
|
+
ref=ref
|
|
233
|
+
inputStyleName=['input-input', buttonsMode, size]
|
|
234
|
+
value=inputValue
|
|
235
|
+
size=size
|
|
236
|
+
disabled=disabled
|
|
237
|
+
keyboardType='numeric'
|
|
238
|
+
returnKeyType=getReturnKeyType()
|
|
239
|
+
onChangeText=onChangeText
|
|
240
|
+
_renderWrapper=renderInputWrapper
|
|
241
|
+
...props
|
|
242
|
+
)
|
|
243
|
+
`
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export default observer(
|
|
247
|
+
themed('NumberInput', NumberInput)
|
|
248
|
+
)
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@startupjs-ui/number-input",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"main": "index.tsx",
|
|
8
|
+
"types": "index.d.ts",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@startupjs-ui/button": "^0.1.3",
|
|
12
|
+
"@startupjs-ui/core": "^0.1.3",
|
|
13
|
+
"@startupjs-ui/div": "^0.1.3",
|
|
14
|
+
"@startupjs-ui/span": "^0.1.3",
|
|
15
|
+
"@startupjs-ui/text-input": "^0.1.3"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"react": "*",
|
|
19
|
+
"react-native": "*",
|
|
20
|
+
"startupjs": "*"
|
|
21
|
+
},
|
|
22
|
+
"gitHead": "fd964ebc3892d3dd0a6c85438c0af619cc50c3f0"
|
|
23
|
+
}
|