Skip to content

Commit 9dc7828

Browse files
committed
Add Stepper
1 parent ed6203c commit 9dc7828

File tree

1 file changed

+165
-2
lines changed
  • packages/components/src/components/Stepper

1 file changed

+165
-2
lines changed
Lines changed: 165 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,166 @@
1-
export function Stepper() {
2-
return <></>
1+
'use client'
2+
3+
import { css, Flex, Text } from '@devup-ui/react'
4+
import clsx from 'clsx'
5+
import { ChangeEvent, useState } from 'react'
6+
7+
import { Button } from '../Button'
8+
import { Input } from '../Input'
9+
10+
type InputProps = Omit<
11+
React.InputHTMLAttributes<HTMLInputElement>,
12+
| 'value'
13+
| 'onChange'
14+
| 'defaultValue'
15+
| 'checked'
16+
| 'defaultChecked'
17+
| 'min'
18+
| 'max'
19+
> & {
20+
type?: 'text' | 'input'
21+
value?: number
22+
onChange?: (value: number) => void
23+
defaultValue?: number
24+
min?: number | null
25+
max?: number | null
26+
classNames?: {
27+
button?: string
28+
container?: string
29+
input?: string
30+
}
31+
styles?: {
32+
button?: React.CSSProperties
33+
container?: React.CSSProperties
34+
input?: React.CSSProperties
35+
}
36+
}
37+
38+
function valid(min: number | null, max: number | null, inp: number): boolean {
39+
return !((max !== null && inp > max) || (min !== null && inp < min))
40+
}
41+
42+
export function Stepper({
43+
className,
44+
classNames,
45+
type = 'input',
46+
value,
47+
onChange,
48+
defaultValue,
49+
min = 0,
50+
max = 100,
51+
disabled = false,
52+
styles,
53+
...props
54+
}: InputProps) {
55+
const [internalValue, setInternalValue] = useState(value ?? defaultValue ?? 0)
56+
const resultValue = value ?? internalValue
57+
function handleChange(_value: number) {
58+
onChange?.(_value)
59+
setInternalValue(_value)
60+
}
61+
function handleInputChange(event: ChangeEvent<HTMLInputElement>) {
62+
if (!event.target.value.length) {
63+
const _value =
64+
min !== null && min > 0 ? min : max !== null && max < 0 ? max : 0
65+
handleChange(_value)
66+
return
67+
}
68+
const _value = parseInt(event.target.value)
69+
if (!valid(min, max, _value)) {
70+
return
71+
}
72+
handleChange(_value)
73+
}
74+
75+
const handleClick = (type: 'add' | 'sub') => {
76+
const targetValue = resultValue + (type === 'sub' ? -1 : 1)
77+
handleChange(targetValue)
78+
}
79+
80+
return (
81+
<Flex
82+
alignItems="center"
83+
aria-disabled={disabled}
84+
gap={5}
85+
selectors={{ '&, & *': { boxSizing: 'border-box' } }}
86+
>
87+
<Button
88+
className={clsx(
89+
css({
90+
boxSize: '28px',
91+
p: 0,
92+
borderRadius: 1,
93+
fontSize: '18px',
94+
fontWeight: 500,
95+
styleOrder: 2,
96+
display: 'flex',
97+
alignItems: 'center',
98+
justifyContent: 'center',
99+
}),
100+
classNames?.button,
101+
)}
102+
disabled={disabled || resultValue === min}
103+
onClick={() => handleClick('sub')}
104+
style={styles?.button}
105+
>
106+
-
107+
</Button>
108+
{type === 'text' && (
109+
<Text className={className} fontSize="14px">
110+
{resultValue}
111+
</Text>
112+
)}
113+
<Input
114+
className={clsx(
115+
css({
116+
textAlign: 'center',
117+
color: '$text',
118+
p: 0,
119+
_placeholder: { fontSize: ['13px', null, '14px'] },
120+
_invalid: {
121+
borderColor: '$error',
122+
},
123+
selectors: {
124+
'&::-webkit-outer-spin-button, &::-webkit-inner-spin-button': {
125+
display: 'none',
126+
},
127+
},
128+
styleOrder: 2,
129+
}),
130+
classNames?.input,
131+
)}
132+
data-value={resultValue.toString()}
133+
disabled={disabled}
134+
onChange={handleInputChange}
135+
readOnly={type === 'text'}
136+
style={{
137+
display: type === 'text' ? 'none' : 'block',
138+
}}
139+
type="number"
140+
{...props}
141+
value={resultValue.toString()}
142+
/>
143+
<Button
144+
className={clsx(
145+
css({
146+
boxSize: '28px',
147+
p: 0,
148+
borderRadius: 1,
149+
fontSize: '18px',
150+
fontWeight: 500,
151+
styleOrder: 2,
152+
display: 'flex',
153+
alignItems: 'center',
154+
justifyContent: 'center',
155+
}),
156+
classNames?.button,
157+
)}
158+
disabled={disabled || resultValue === max}
159+
onClick={() => handleClick('add')}
160+
style={styles?.button}
161+
>
162+
+
163+
</Button>
164+
</Flex>
165+
)
3166
}

0 commit comments

Comments
 (0)