@toptal/picasso-number-input 1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-77646e396.4080 → 1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-25389459e.4083
Sign up to get free protection for your applications and to get access to all the features.
- package/dist-package/tsconfig.tsbuildinfo +1 -0
- package/package.json +11 -15
- package/src/NumberInput/NumberInput.tsx +152 -0
- package/src/NumberInput/__snapshots__/test.tsx.snap +73 -0
- package/src/NumberInput/index.ts +7 -0
- package/src/NumberInput/story/Default.example.tsx +25 -0
- package/src/NumberInput/story/Disabled.example.tsx +26 -0
- package/src/NumberInput/story/Sizes.example.tsx +57 -0
- package/src/NumberInput/story/Status.example.tsx +23 -0
- package/src/NumberInput/story/WithIcon.example.tsx +27 -0
- package/src/NumberInput/story/index.jsx +42 -0
- package/src/NumberInput/styles.ts +23 -0
- package/src/NumberInput/test.tsx +153 -0
- package/src/NumberInputEndAdornment/NumberInputEndAdornment.tsx +173 -0
- package/src/NumberInputEndAdornment/index.ts +2 -0
- package/src/NumberInputEndAdornment/styles.ts +58 -0
- package/src/index.ts +2 -0
- package/tsconfig.json +15 -0
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@toptal/picasso-number-input",
|
3
|
-
"version": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-
|
3
|
+
"version": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-25389459e.4083+25389459e",
|
4
4
|
"description": "Toptal UI components library - NumberInput",
|
5
5
|
"publishConfig": {
|
6
6
|
"access": "public"
|
@@ -22,14 +22,14 @@
|
|
22
22
|
},
|
23
23
|
"homepage": "https://github.com/toptal/picasso/tree/master/packages/picasso#readme",
|
24
24
|
"dependencies": {
|
25
|
-
"@toptal/picasso-container": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-
|
26
|
-
"@toptal/picasso-form": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-
|
27
|
-
"@toptal/picasso-icons": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-
|
28
|
-
"@toptal/picasso-input": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-
|
29
|
-
"@toptal/picasso-input-adornment": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-
|
30
|
-
"@toptal/picasso-outlined-input": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-
|
31
|
-
"@toptal/picasso-shared": "13.1.3-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-
|
32
|
-
"@toptal/picasso-utils": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-
|
25
|
+
"@toptal/picasso-container": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-25389459e.4083+25389459e",
|
26
|
+
"@toptal/picasso-form": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-25389459e.4083+25389459e",
|
27
|
+
"@toptal/picasso-icons": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-25389459e.4083+25389459e",
|
28
|
+
"@toptal/picasso-input": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-25389459e.4083+25389459e",
|
29
|
+
"@toptal/picasso-input-adornment": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-25389459e.4083+25389459e",
|
30
|
+
"@toptal/picasso-outlined-input": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-25389459e.4083+25389459e",
|
31
|
+
"@toptal/picasso-shared": "13.1.3-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-25389459e.26+25389459e",
|
32
|
+
"@toptal/picasso-utils": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-25389459e.4083+25389459e",
|
33
33
|
"classnames": "^2.3.1"
|
34
34
|
},
|
35
35
|
"sideEffects": [
|
@@ -45,11 +45,7 @@
|
|
45
45
|
".": "./dist-package/src/index.js"
|
46
46
|
},
|
47
47
|
"devDependencies": {
|
48
|
-
"@toptal/picasso-test-utils": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-
|
48
|
+
"@toptal/picasso-test-utils": "1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-25389459e.4083+25389459e"
|
49
49
|
},
|
50
|
-
"
|
51
|
-
"dist-package/**",
|
52
|
-
"!dist-package/tsconfig.tsbuildinfo"
|
53
|
-
],
|
54
|
-
"gitHead": "77646e396bff10ff86e3a85e5a1a8c3af86093ef"
|
50
|
+
"gitHead": "25389459e8d238e1cadceeda472ae9cc5b53b162"
|
55
51
|
}
|
@@ -0,0 +1,152 @@
|
|
1
|
+
import type { ReactNode } from 'react'
|
2
|
+
import React, { forwardRef, useRef } from 'react'
|
3
|
+
import type { Theme } from '@material-ui/core/styles'
|
4
|
+
import { makeStyles } from '@material-ui/core/styles'
|
5
|
+
import type { BaseProps, OmitInternalProps } from '@toptal/picasso-shared'
|
6
|
+
import cx from 'classnames'
|
7
|
+
import { OutlinedInput } from '@toptal/picasso-outlined-input'
|
8
|
+
import { InputAdornment } from '@toptal/picasso-input-adornment'
|
9
|
+
import {
|
10
|
+
useCombinedRefs,
|
11
|
+
usePropDeprecationWarning,
|
12
|
+
} from '@toptal/picasso-utils'
|
13
|
+
import type { Props as OutlinedInputProps } from '@toptal/picasso-outlined-input'
|
14
|
+
import { useFieldsLayoutContext } from '@toptal/picasso-form'
|
15
|
+
|
16
|
+
import styles from './styles'
|
17
|
+
import { NumberInputEndAdornment } from '../NumberInputEndAdornment'
|
18
|
+
|
19
|
+
export interface Props
|
20
|
+
extends Omit<
|
21
|
+
OmitInternalProps<OutlinedInputProps>,
|
22
|
+
'defaultValue' | 'type' | 'multiline' | 'rows'
|
23
|
+
>,
|
24
|
+
BaseProps {
|
25
|
+
/** Value of the `input` element. */
|
26
|
+
value?: string | number
|
27
|
+
/** Minimum value for the `input` element */
|
28
|
+
min?: number | string
|
29
|
+
/** Maximum value for the `input` element */
|
30
|
+
max?: number | string
|
31
|
+
/** Next value of the `input` element will be calculated based on step */
|
32
|
+
step?: number | string
|
33
|
+
/** Should controls be hidden or not */
|
34
|
+
hideControls?: boolean
|
35
|
+
/** Specify icon which should be rendered inside NumberInput */
|
36
|
+
icon?: ReactNode
|
37
|
+
/** Indicates whether component is in disabled state */
|
38
|
+
disabled?: boolean
|
39
|
+
/** Callback invoked when `NumberInput` changes its state. */
|
40
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
41
|
+
highlight?: 'autofill'
|
42
|
+
}
|
43
|
+
|
44
|
+
const useStyles = makeStyles<Theme, Props>(styles, {
|
45
|
+
name: 'PicassoNumberInput',
|
46
|
+
})
|
47
|
+
|
48
|
+
export const NumberInput = forwardRef<HTMLInputElement, Props>(
|
49
|
+
function NumberInput(props, ref) {
|
50
|
+
const {
|
51
|
+
step = 1,
|
52
|
+
min = -Infinity,
|
53
|
+
max = Infinity,
|
54
|
+
hideControls,
|
55
|
+
value,
|
56
|
+
onChange,
|
57
|
+
disabled,
|
58
|
+
error,
|
59
|
+
status,
|
60
|
+
onResetClick,
|
61
|
+
enableReset,
|
62
|
+
width,
|
63
|
+
icon,
|
64
|
+
size,
|
65
|
+
testIds,
|
66
|
+
highlight,
|
67
|
+
...rest
|
68
|
+
} = props
|
69
|
+
|
70
|
+
// TODO: [FX-4715]
|
71
|
+
usePropDeprecationWarning({
|
72
|
+
props,
|
73
|
+
name: 'error',
|
74
|
+
componentName: 'NumberInput',
|
75
|
+
description:
|
76
|
+
'Use the `status` prop instead. `error` is deprecated and will be removed in the next major release.',
|
77
|
+
})
|
78
|
+
|
79
|
+
const { layout } = useFieldsLayoutContext()
|
80
|
+
|
81
|
+
const classes = useStyles(props)
|
82
|
+
|
83
|
+
const inputRef = useCombinedRefs<HTMLInputElement>(
|
84
|
+
ref,
|
85
|
+
useRef<HTMLInputElement>(null)
|
86
|
+
)
|
87
|
+
|
88
|
+
const endAdornment = hideControls ? null : (
|
89
|
+
<NumberInputEndAdornment
|
90
|
+
step={step}
|
91
|
+
min={min}
|
92
|
+
max={max}
|
93
|
+
value={value}
|
94
|
+
disabled={disabled}
|
95
|
+
size={size}
|
96
|
+
inputRef={inputRef}
|
97
|
+
/>
|
98
|
+
)
|
99
|
+
|
100
|
+
const startAdornment = icon ? (
|
101
|
+
<InputAdornment position='start' disablePointerEvents>
|
102
|
+
{icon}
|
103
|
+
</InputAdornment>
|
104
|
+
) : null
|
105
|
+
|
106
|
+
return (
|
107
|
+
<OutlinedInput
|
108
|
+
classes={{
|
109
|
+
root: cx(classes.root, {
|
110
|
+
[classes.highlightAutofill]: highlight === 'autofill',
|
111
|
+
[classes.horizontalLayout]: layout === 'horizontal',
|
112
|
+
}),
|
113
|
+
input: classes.input,
|
114
|
+
}}
|
115
|
+
inputProps={{
|
116
|
+
...rest,
|
117
|
+
step,
|
118
|
+
min,
|
119
|
+
max,
|
120
|
+
}}
|
121
|
+
width={width}
|
122
|
+
onResetClick={onResetClick}
|
123
|
+
enableReset={enableReset}
|
124
|
+
status={error ? 'error' : status}
|
125
|
+
inputRef={inputRef}
|
126
|
+
type='number'
|
127
|
+
value={value}
|
128
|
+
disabled={disabled}
|
129
|
+
onChange={onChange}
|
130
|
+
endAdornment={endAdornment}
|
131
|
+
startAdornment={startAdornment}
|
132
|
+
size={size}
|
133
|
+
testIds={testIds}
|
134
|
+
/>
|
135
|
+
)
|
136
|
+
}
|
137
|
+
)
|
138
|
+
|
139
|
+
NumberInput.defaultProps = {
|
140
|
+
onChange: () => {},
|
141
|
+
value: 0,
|
142
|
+
step: 1,
|
143
|
+
min: -Infinity,
|
144
|
+
max: Infinity,
|
145
|
+
hideControls: false,
|
146
|
+
size: 'medium',
|
147
|
+
status: 'default',
|
148
|
+
}
|
149
|
+
|
150
|
+
NumberInput.displayName = 'NumberInput'
|
151
|
+
|
152
|
+
export default NumberInput
|
@@ -0,0 +1,73 @@
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
2
|
+
|
3
|
+
exports[`NumberInput renders 1`] = `
|
4
|
+
<div
|
5
|
+
class="Picasso-root"
|
6
|
+
>
|
7
|
+
<div
|
8
|
+
class="MuiInputBase-root MuiOutlinedInput-root PicassoOutlinedInput-root PicassoNumberInput-root PicassoOutlinedInput-rootAuto PicassoOutlinedInput-rootMedium MuiInputBase-adornedEnd MuiOutlinedInput-adornedEnd"
|
9
|
+
>
|
10
|
+
<input
|
11
|
+
aria-invalid="false"
|
12
|
+
class="MuiInputBase-input MuiOutlinedInput-input PicassoOutlinedInput-input PicassoNumberInput-input PicassoOutlinedInput-inputMedium MuiInputBase-inputAdornedEnd MuiOutlinedInput-inputAdornedEnd"
|
13
|
+
max="100"
|
14
|
+
min="-100"
|
15
|
+
step="5"
|
16
|
+
type="number"
|
17
|
+
value="10"
|
18
|
+
/>
|
19
|
+
<div
|
20
|
+
class="MuiInputAdornment-root PicassoInputAdornment-root MuiInputAdornment-positionEnd"
|
21
|
+
>
|
22
|
+
<div
|
23
|
+
class="PicassoContainer-flex PicassoContainer-inline PicassoContainer-column"
|
24
|
+
>
|
25
|
+
<button
|
26
|
+
class="MuiButtonBase-root NumberInputEndAdornment-medium NumberInputEndAdornment-root"
|
27
|
+
tabindex="0"
|
28
|
+
type="button"
|
29
|
+
>
|
30
|
+
<svg
|
31
|
+
class="PicassoSvgArrowUpMinor16-root"
|
32
|
+
style="min-width: 16px; min-height: 16px;"
|
33
|
+
viewBox="0 0 16 16"
|
34
|
+
>
|
35
|
+
<path
|
36
|
+
d="m7.997 5.29 4.707 4.707-.707.707-4-4-4 4-.707-.707 4-4 .707-.707Z"
|
37
|
+
/>
|
38
|
+
</svg>
|
39
|
+
</button>
|
40
|
+
<button
|
41
|
+
class="MuiButtonBase-root NumberInputEndAdornment-medium NumberInputEndAdornment-root"
|
42
|
+
tabindex="0"
|
43
|
+
type="button"
|
44
|
+
>
|
45
|
+
<svg
|
46
|
+
class="PicassoSvgArrowDownMinor16-root"
|
47
|
+
style="min-width: 16px; min-height: 16px;"
|
48
|
+
viewBox="0 0 16 16"
|
49
|
+
>
|
50
|
+
<path
|
51
|
+
d="m11.997 5.29.707.707-4 4-.707.707-.707-.707-4-4 .707-.707 4 4 4-4Z"
|
52
|
+
/>
|
53
|
+
</svg>
|
54
|
+
</button>
|
55
|
+
</div>
|
56
|
+
</div>
|
57
|
+
<fieldset
|
58
|
+
aria-hidden="true"
|
59
|
+
class="PrivateNotchedOutline-root MuiOutlinedInput-notchedOutline PicassoOutlinedInput-notchedOutline"
|
60
|
+
style="padding-left: 8px;"
|
61
|
+
>
|
62
|
+
<legend
|
63
|
+
class="PrivateNotchedOutline-legend"
|
64
|
+
style="width: 0.01px;"
|
65
|
+
>
|
66
|
+
<span>
|
67
|
+
|
68
|
+
</span>
|
69
|
+
</legend>
|
70
|
+
</fieldset>
|
71
|
+
</div>
|
72
|
+
</div>
|
73
|
+
`;
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import type { ChangeEventHandler } from 'react'
|
2
|
+
import React, { useState } from 'react'
|
3
|
+
import { NumberInput, Container } from '@toptal/picasso'
|
4
|
+
|
5
|
+
const DefaultExample = () => {
|
6
|
+
const [value, setValue] = useState('1')
|
7
|
+
|
8
|
+
const handleChange: ChangeEventHandler<HTMLInputElement> = event => {
|
9
|
+
setValue(event.target.value)
|
10
|
+
}
|
11
|
+
|
12
|
+
return (
|
13
|
+
<Container>
|
14
|
+
<NumberInput
|
15
|
+
value={value}
|
16
|
+
onChange={handleChange}
|
17
|
+
step='1'
|
18
|
+
max='100'
|
19
|
+
min='-100'
|
20
|
+
/>
|
21
|
+
</Container>
|
22
|
+
)
|
23
|
+
}
|
24
|
+
|
25
|
+
export default DefaultExample
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import type { ChangeEventHandler } from 'react'
|
2
|
+
import React, { useState } from 'react'
|
3
|
+
import { NumberInput, Container } from '@toptal/picasso'
|
4
|
+
|
5
|
+
const DisabledExample = () => {
|
6
|
+
const [value, setValue] = useState('1')
|
7
|
+
|
8
|
+
const handleChange: ChangeEventHandler<HTMLInputElement> = event => {
|
9
|
+
setValue(event.target.value)
|
10
|
+
}
|
11
|
+
|
12
|
+
return (
|
13
|
+
<Container>
|
14
|
+
<NumberInput
|
15
|
+
disabled
|
16
|
+
value={value}
|
17
|
+
onChange={handleChange}
|
18
|
+
step='5'
|
19
|
+
max='100'
|
20
|
+
min='-100'
|
21
|
+
/>
|
22
|
+
</Container>
|
23
|
+
)
|
24
|
+
}
|
25
|
+
|
26
|
+
export default DisabledExample
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import type { ChangeEventHandler } from 'react'
|
2
|
+
import React, { useState } from 'react'
|
3
|
+
import { NumberInput, Container, FormLabel } from '@toptal/picasso'
|
4
|
+
import { SPACING_4 } from '@toptal/picasso-utils'
|
5
|
+
|
6
|
+
const SizesExample = () => {
|
7
|
+
const [value, setValue] = useState('1')
|
8
|
+
|
9
|
+
const handleChange: ChangeEventHandler<HTMLInputElement> = event => {
|
10
|
+
setValue(event.target.value)
|
11
|
+
}
|
12
|
+
|
13
|
+
return (
|
14
|
+
<Container>
|
15
|
+
<Container padded={SPACING_4}>
|
16
|
+
<FormLabel htmlFor='small-number-input'>Small</FormLabel>
|
17
|
+
<NumberInput
|
18
|
+
id='small-number-input'
|
19
|
+
value={value}
|
20
|
+
size='small'
|
21
|
+
onChange={handleChange}
|
22
|
+
step='1'
|
23
|
+
max='100'
|
24
|
+
min='-100'
|
25
|
+
/>
|
26
|
+
</Container>
|
27
|
+
|
28
|
+
<Container padded={SPACING_4}>
|
29
|
+
<FormLabel htmlFor='medium-number-input'>Medium (default)</FormLabel>
|
30
|
+
<NumberInput
|
31
|
+
id='medium-number-input'
|
32
|
+
value={value}
|
33
|
+
size='medium'
|
34
|
+
onChange={handleChange}
|
35
|
+
step='1'
|
36
|
+
max='100'
|
37
|
+
min='-100'
|
38
|
+
/>
|
39
|
+
</Container>
|
40
|
+
|
41
|
+
<Container padded={SPACING_4}>
|
42
|
+
<FormLabel htmlFor='large-number-input'>Large</FormLabel>
|
43
|
+
<NumberInput
|
44
|
+
id='large-number-input'
|
45
|
+
value={value}
|
46
|
+
size='large'
|
47
|
+
onChange={handleChange}
|
48
|
+
step='1'
|
49
|
+
max='100'
|
50
|
+
min='-100'
|
51
|
+
/>
|
52
|
+
</Container>
|
53
|
+
</Container>
|
54
|
+
)
|
55
|
+
}
|
56
|
+
|
57
|
+
export default SizesExample
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { NumberInput, Form } from '@toptal/picasso'
|
3
|
+
|
4
|
+
const Example = () => {
|
5
|
+
return (
|
6
|
+
<Form>
|
7
|
+
<Form.Field>
|
8
|
+
<Form.Label>Default</Form.Label>
|
9
|
+
<NumberInput value='100' status='default' />
|
10
|
+
</Form.Field>
|
11
|
+
<Form.Field>
|
12
|
+
<Form.Label>Error</Form.Label>
|
13
|
+
<NumberInput value='100' status='error' />
|
14
|
+
</Form.Field>
|
15
|
+
<Form.Field>
|
16
|
+
<Form.Label>Success</Form.Label>
|
17
|
+
<NumberInput value='100' status='success' />
|
18
|
+
</Form.Field>
|
19
|
+
</Form>
|
20
|
+
)
|
21
|
+
}
|
22
|
+
|
23
|
+
export default Example
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import type { ChangeEventHandler } from 'react'
|
2
|
+
import React, { useState } from 'react'
|
3
|
+
import { NumberInput, Container } from '@toptal/picasso'
|
4
|
+
import { ReferralBonus16 } from '@toptal/picasso-icons'
|
5
|
+
|
6
|
+
const WithIconExample = () => {
|
7
|
+
const [value, setValue] = useState('1')
|
8
|
+
|
9
|
+
const handleChange: ChangeEventHandler<HTMLInputElement> = event => {
|
10
|
+
setValue(event.target.value)
|
11
|
+
}
|
12
|
+
|
13
|
+
return (
|
14
|
+
<Container>
|
15
|
+
<NumberInput
|
16
|
+
value={value}
|
17
|
+
onChange={handleChange}
|
18
|
+
step='5'
|
19
|
+
max='100'
|
20
|
+
min='-100'
|
21
|
+
icon={<ReferralBonus16 />}
|
22
|
+
/>
|
23
|
+
</Container>
|
24
|
+
)
|
25
|
+
}
|
26
|
+
|
27
|
+
export default WithIconExample
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import { NumberInput } from '../NumberInput'
|
2
|
+
import PicassoBook from '~/.storybook/components/PicassoBook'
|
3
|
+
|
4
|
+
const page = PicassoBook.section('Forms').createPage(
|
5
|
+
'NumberInput',
|
6
|
+
`Input component for numbers
|
7
|
+
|
8
|
+
${PicassoBook.createSourceLink(__filename)}
|
9
|
+
`
|
10
|
+
)
|
11
|
+
|
12
|
+
page
|
13
|
+
.createTabChapter('Props')
|
14
|
+
.addComponentDocs({ component: NumberInput, name: 'NumberInput' })
|
15
|
+
|
16
|
+
page
|
17
|
+
.createChapter()
|
18
|
+
.addExample(
|
19
|
+
'NumberInput/story/Default.example.tsx',
|
20
|
+
'Default',
|
21
|
+
'base/NumberInput'
|
22
|
+
)
|
23
|
+
.addExample(
|
24
|
+
'NumberInput/story/Disabled.example.tsx',
|
25
|
+
'Disabled',
|
26
|
+
'base/NumberInput'
|
27
|
+
)
|
28
|
+
.addExample(
|
29
|
+
'NumberInput/story/Status.example.tsx',
|
30
|
+
'Status',
|
31
|
+
'base/NumberInput'
|
32
|
+
)
|
33
|
+
.addExample(
|
34
|
+
'NumberInput/story/WithIcon.example.tsx',
|
35
|
+
'With Icon',
|
36
|
+
'base/NumberInput'
|
37
|
+
)
|
38
|
+
.addExample(
|
39
|
+
'NumberInput/story/Sizes.example.tsx',
|
40
|
+
'Sizes',
|
41
|
+
'base/NumberInput'
|
42
|
+
)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import type { Theme } from '@material-ui/core/styles'
|
2
|
+
import { createStyles } from '@material-ui/core/styles'
|
3
|
+
import { highlightStyles as highlightAutofillStyles } from '@toptal/picasso-input'
|
4
|
+
|
5
|
+
export default (theme: Theme) =>
|
6
|
+
createStyles({
|
7
|
+
root: {
|
8
|
+
paddingRight: 0,
|
9
|
+
cursor: 'text',
|
10
|
+
},
|
11
|
+
input: {
|
12
|
+
'&::-webkit-outer-spin-button, &::-webkit-inner-spin-button': {
|
13
|
+
'-webkit-appearance': 'none',
|
14
|
+
appearance: 'none',
|
15
|
+
margin: 0,
|
16
|
+
},
|
17
|
+
'-moz-appearance': 'textfield',
|
18
|
+
},
|
19
|
+
horizontalLayout: {
|
20
|
+
width: '100%',
|
21
|
+
},
|
22
|
+
...highlightAutofillStyles(theme),
|
23
|
+
})
|
@@ -0,0 +1,153 @@
|
|
1
|
+
import type { ChangeEventHandler } from 'react'
|
2
|
+
import React, { useState } from 'react'
|
3
|
+
import { render, fireEvent } from '@toptal/picasso-test-utils'
|
4
|
+
|
5
|
+
import type { Props as NumberInputProps } from './NumberInput'
|
6
|
+
import { NumberInput } from './NumberInput'
|
7
|
+
|
8
|
+
const NumberInputRenderer = (
|
9
|
+
props: { initialValue: number | string } & Partial<NumberInputProps>
|
10
|
+
) => {
|
11
|
+
const [value, setValue] = useState(props.initialValue)
|
12
|
+
|
13
|
+
const handleChange: ChangeEventHandler<HTMLInputElement> = e => {
|
14
|
+
setValue(e.target.value)
|
15
|
+
}
|
16
|
+
|
17
|
+
return (
|
18
|
+
<NumberInput
|
19
|
+
step={5}
|
20
|
+
max={100}
|
21
|
+
min={-100}
|
22
|
+
value={value}
|
23
|
+
onChange={handleChange}
|
24
|
+
status={props.status}
|
25
|
+
testIds={props.testIds}
|
26
|
+
/>
|
27
|
+
)
|
28
|
+
}
|
29
|
+
|
30
|
+
const renderNumberInput = (
|
31
|
+
props: Partial<NumberInputProps> & { initialValue?: number | string } = {}
|
32
|
+
) => {
|
33
|
+
const { initialValue = '10' } = props
|
34
|
+
|
35
|
+
return render(<NumberInputRenderer initialValue={initialValue} {...props} />)
|
36
|
+
}
|
37
|
+
|
38
|
+
describe('NumberInput', () => {
|
39
|
+
it('renders', () => {
|
40
|
+
const { container } = renderNumberInput()
|
41
|
+
|
42
|
+
expect(container.firstChild).toMatchSnapshot()
|
43
|
+
})
|
44
|
+
|
45
|
+
it('increase value', async () => {
|
46
|
+
const { getByDisplayValue, queryAllByRole } = renderNumberInput()
|
47
|
+
|
48
|
+
const input = getByDisplayValue('10') as HTMLInputElement
|
49
|
+
|
50
|
+
const controls = queryAllByRole('button')
|
51
|
+
const controlUp = controls[0]
|
52
|
+
|
53
|
+
fireEvent.click(controlUp)
|
54
|
+
|
55
|
+
expect(input.value).toBe('15')
|
56
|
+
})
|
57
|
+
|
58
|
+
it('decrease value', () => {
|
59
|
+
const { getByDisplayValue, queryAllByRole } = renderNumberInput()
|
60
|
+
|
61
|
+
const input = getByDisplayValue('10') as HTMLInputElement
|
62
|
+
|
63
|
+
const controls = queryAllByRole('button')
|
64
|
+
const controlDown = controls[1]
|
65
|
+
|
66
|
+
fireEvent.click(controlDown)
|
67
|
+
|
68
|
+
expect(input.value).toBe('5')
|
69
|
+
})
|
70
|
+
|
71
|
+
describe('near max/min limits', () => {
|
72
|
+
it('increase value near max limit', () => {
|
73
|
+
const { getByDisplayValue, queryAllByRole } = renderNumberInput({
|
74
|
+
initialValue: '97',
|
75
|
+
})
|
76
|
+
|
77
|
+
const input = getByDisplayValue('97') as HTMLInputElement
|
78
|
+
|
79
|
+
const controls = queryAllByRole('button')
|
80
|
+
const controlUp = controls[0]
|
81
|
+
|
82
|
+
fireEvent.click(controlUp)
|
83
|
+
|
84
|
+
expect(input.value).toBe('100')
|
85
|
+
})
|
86
|
+
|
87
|
+
it('decrease value near min limit', () => {
|
88
|
+
const { getByDisplayValue, queryAllByRole } = renderNumberInput({
|
89
|
+
initialValue: '-97',
|
90
|
+
})
|
91
|
+
|
92
|
+
const input = getByDisplayValue('-97') as HTMLInputElement
|
93
|
+
|
94
|
+
const controls = queryAllByRole('button')
|
95
|
+
const controlDown = controls[1]
|
96
|
+
|
97
|
+
fireEvent.click(controlDown)
|
98
|
+
|
99
|
+
expect(input.value).toBe('-100')
|
100
|
+
})
|
101
|
+
|
102
|
+
it('decrease value when it is closer than step to max', () => {
|
103
|
+
const { getByDisplayValue, queryAllByRole } = renderNumberInput({
|
104
|
+
initialValue: '97',
|
105
|
+
})
|
106
|
+
|
107
|
+
const input = getByDisplayValue('97') as HTMLInputElement
|
108
|
+
|
109
|
+
const controls = queryAllByRole('button')
|
110
|
+
const controlDown = controls[1]
|
111
|
+
|
112
|
+
fireEvent.click(controlDown)
|
113
|
+
|
114
|
+
expect(input.value).toBe('95')
|
115
|
+
})
|
116
|
+
|
117
|
+
it('increase value when it is closer to min than step', () => {
|
118
|
+
const { getByDisplayValue, queryAllByRole } = renderNumberInput({
|
119
|
+
initialValue: '-97',
|
120
|
+
})
|
121
|
+
|
122
|
+
const input = getByDisplayValue('-97') as HTMLInputElement
|
123
|
+
|
124
|
+
const controls = queryAllByRole('button')
|
125
|
+
const controlUp = controls[0]
|
126
|
+
|
127
|
+
fireEvent.click(controlUp)
|
128
|
+
|
129
|
+
expect(input.value).toBe('-95')
|
130
|
+
})
|
131
|
+
})
|
132
|
+
|
133
|
+
describe('when in a valid state', () => {
|
134
|
+
it('shows valid icon', () => {
|
135
|
+
const testProps: NumberInputProps = {
|
136
|
+
value: '10',
|
137
|
+
status: 'success',
|
138
|
+
testIds: { validIcon: 'valid-icon' },
|
139
|
+
}
|
140
|
+
|
141
|
+
const { getByTestId, rerender } = renderNumberInput(testProps)
|
142
|
+
|
143
|
+
const validIcon = getByTestId('valid-icon')
|
144
|
+
|
145
|
+
expect(validIcon).toBeVisible()
|
146
|
+
|
147
|
+
// re-render with different props
|
148
|
+
rerender(<NumberInput {...testProps} status='error' />)
|
149
|
+
|
150
|
+
expect(validIcon).not.toBeVisible()
|
151
|
+
})
|
152
|
+
})
|
153
|
+
})
|