Skip to content

Commit aa6c583

Browse files
guoyunhezombieJ
andauthored
feat(spinner): support spinner type (#721)
* feat(spinner): support spinner type * test: update snapshot * fix: dom * refactor: step handler * refactor: step handler * chore: adjust logic * chore: support naming --------- Co-authored-by: 二货机器人 <[email protected]>
1 parent 622bc78 commit aa6c583

File tree

7 files changed

+227
-89
lines changed

7 files changed

+227
-89
lines changed

assets/index.less

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,24 @@
133133
.handler-disabled();
134134
}
135135
}
136+
137+
&-type-spinner {
138+
display: inline-flex;
139+
align-items: center;
140+
}
141+
142+
&-type-spinner &-handler {
143+
flex: 0 0 20px;
144+
line-height: 26px;
145+
height: 100%;
146+
}
147+
148+
&-type-spinner &-handler-up {
149+
border-bottom: 0;
150+
border-left: 1px solid #d9d9d9;
151+
}
152+
&-type-spinner &-handler-down {
153+
border-top: 0;
154+
border-right: 1px solid #d9d9d9;
155+
}
136156
}

docs/demo/spinner.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/* eslint no-console:0 */
2+
import InputNumber from '@rc-component/input-number';
3+
import React from 'react';
4+
import '../../assets/index.less';
5+
6+
export default () => {
7+
const [disabled, setDisabled] = React.useState(false);
8+
const [readOnly, setReadOnly] = React.useState(false);
9+
const [keyboard, setKeyboard] = React.useState(true);
10+
const [wheel, setWheel] = React.useState(true);
11+
const [stringMode, setStringMode] = React.useState(false);
12+
const [value, setValue] = React.useState<string | number>(93);
13+
14+
const onChange = (val: number) => {
15+
console.warn('onChange:', val, typeof val);
16+
setValue(val);
17+
};
18+
19+
return (
20+
<div style={{ margin: 10 }}>
21+
<h3>Controlled</h3>
22+
<InputNumber
23+
type="spinner"
24+
aria-label="Simple number input example"
25+
min={-8}
26+
max={10}
27+
style={{ width: 100 }}
28+
value={value}
29+
onChange={onChange}
30+
readOnly={readOnly}
31+
disabled={disabled}
32+
keyboard={keyboard}
33+
changeOnWheel={wheel}
34+
stringMode={stringMode}
35+
/>
36+
<p>
37+
<button type="button" onClick={() => setDisabled(!disabled)}>
38+
toggle Disabled ({String(disabled)})
39+
</button>
40+
<button type="button" onClick={() => setReadOnly(!readOnly)}>
41+
toggle readOnly ({String(readOnly)})
42+
</button>
43+
<button type="button" onClick={() => setKeyboard(!keyboard)}>
44+
toggle keyboard ({String(keyboard)})
45+
</button>
46+
<button type="button" onClick={() => setStringMode(!stringMode)}>
47+
toggle stringMode ({String(stringMode)})
48+
</button>
49+
<button type="button" onClick={() => setWheel(!wheel)}>
50+
toggle wheel ({String(wheel)})
51+
</button>
52+
</p>
53+
54+
<hr />
55+
<h3>Uncontrolled</h3>
56+
<InputNumber
57+
type="spinner"
58+
style={{ width: 100 }}
59+
onChange={onChange}
60+
min={-99}
61+
max={99}
62+
defaultValue={33}
63+
/>
64+
65+
<hr />
66+
<h3>!changeOnBlur</h3>
67+
<InputNumber
68+
type="spinner"
69+
style={{ width: 100 }}
70+
min={-9}
71+
max={9}
72+
defaultValue={10}
73+
onChange={onChange}
74+
changeOnBlur={false}
75+
/>
76+
</div>
77+
);
78+
};

docs/example.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,7 @@ nav:
5252
## focus
5353

5454
<code src="./demo/focus.tsx"></code>
55+
56+
## spinner
57+
58+
<code src="./demo/spinner.tsx"></code>

src/InputNumber.tsx

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { BaseInput } from '@rc-component/input';
12
import getMiniDecimal, {
23
DecimalClass,
34
getNumberPrecision,
@@ -6,20 +7,20 @@ import getMiniDecimal, {
67
validateNumber,
78
ValueType,
89
} from '@rc-component/mini-decimal';
9-
import { clsx } from 'clsx';
10-
import { BaseInput } from '@rc-component/input';
1110
import { useLayoutUpdateEffect } from '@rc-component/util/lib/hooks/useLayoutEffect';
1211
import proxyObject from '@rc-component/util/lib/proxyObject';
1312
import { composeRef } from '@rc-component/util/lib/ref';
13+
import { clsx } from 'clsx';
1414
import * as React from 'react';
1515
import useCursor from './hooks/useCursor';
16+
import SemanticContext from './SemanticContext';
1617
import StepHandler from './StepHandler';
1718
import { getDecupleSteps } from './utils/numberUtil';
18-
import SemanticContext from './SemanticContext';
1919

2020
import type { HolderRef } from '@rc-component/input/lib/BaseInput';
2121
import { BaseInputProps } from '@rc-component/input/lib/interface';
2222
import { InputFocusOptions, triggerFocus } from '@rc-component/input/lib/utils/commonUtils';
23+
import { useEvent } from '@rc-component/util';
2324
import useFrame from './hooks/useFrame';
2425

2526
export type { ValueType };
@@ -54,7 +55,7 @@ const getDecimalIfValidate = (value: ValueType) => {
5455
return decimal.isInvalidate() ? null : decimal;
5556
};
5657

57-
type SemanticName = 'actions' | 'input';
58+
type SemanticName = 'actions' | 'input' | 'action';
5859
export interface InputNumberProps<T extends ValueType = ValueType>
5960
extends Omit<
6061
React.InputHTMLAttributes<HTMLInputElement>,
@@ -63,6 +64,8 @@ export interface InputNumberProps<T extends ValueType = ValueType>
6364
/** value will show as string */
6465
stringMode?: boolean;
6566

67+
type?: 'input' | 'spinner';
68+
6669
defaultValue?: T;
6770
value?: T | null;
6871

@@ -119,6 +122,7 @@ type InternalInputNumberProps = Omit<InputNumberProps, 'prefix' | 'suffix'> & {
119122
const InternalInputNumber = React.forwardRef(
120123
(props: InternalInputNumberProps, ref: React.Ref<HTMLInputElement>) => {
121124
const {
125+
type,
122126
prefixCls,
123127
className,
124128
style,
@@ -154,6 +158,8 @@ const InternalInputNumber = React.forwardRef(
154158
...inputProps
155159
} = props;
156160

161+
const { classNames, styles } = React.useContext(SemanticContext) || {};
162+
157163
const inputClassName = `${prefixCls}-input`;
158164

159165
const inputRef = React.useRef<HTMLInputElement>(null);
@@ -435,7 +441,7 @@ const InternalInputNumber = React.forwardRef(
435441
};
436442

437443
// ============================= Step =============================
438-
const onInternalStep = (up: boolean, emitter: 'handler' | 'keyboard' | 'wheel') => {
444+
const onInternalStep = useEvent((up: boolean, emitter: 'handler' | 'keyboard' | 'wheel') => {
439445
// Ignore step since out of range
440446
if ((up && upDisabled) || (!up && downDisabled)) {
441447
return;
@@ -461,7 +467,7 @@ const InternalInputNumber = React.forwardRef(
461467
});
462468

463469
inputRef.current?.focus();
464-
};
470+
});
465471

466472
// ============================ Flush =============================
467473
/**
@@ -586,6 +592,35 @@ const InternalInputNumber = React.forwardRef(
586592
}, [inputValue]);
587593

588594
// ============================ Render ============================
595+
// >>>>>> Handler
596+
const sharedHandlerProps = {
597+
prefixCls,
598+
onStep: onInternalStep,
599+
className: classNames?.action,
600+
style: styles?.action,
601+
};
602+
603+
const upNode = (
604+
<StepHandler
605+
{...sharedHandlerProps}
606+
action="up"
607+
disabled={upDisabled}
608+
>
609+
{upHandler}
610+
</StepHandler>
611+
);
612+
613+
const downNode = (
614+
<StepHandler
615+
{...sharedHandlerProps}
616+
action="down"
617+
disabled={downDisabled}
618+
>
619+
{downHandler}
620+
</StepHandler>
621+
);
622+
623+
// >>>>>> Render
589624
return (
590625
<div
591626
ref={domRef}
@@ -607,16 +642,18 @@ const InternalInputNumber = React.forwardRef(
607642
onCompositionEnd={onCompositionEnd}
608643
onBeforeInput={onBeforeInput}
609644
>
610-
{controls && (
611-
<StepHandler
612-
prefixCls={prefixCls}
613-
upNode={upHandler}
614-
downNode={downHandler}
615-
upDisabled={upDisabled}
616-
downDisabled={downDisabled}
617-
onStep={onInternalStep}
618-
/>
645+
{type === 'input' && controls && (
646+
<div
647+
className={clsx(`${prefixCls}-handler-wrap`, classNames?.actions)}
648+
style={styles?.actions}
649+
>
650+
{upNode}
651+
{downNode}
652+
</div>
619653
)}
654+
655+
{type === 'spinner' && controls && downNode}
656+
620657
<div className={`${inputClassName}-wrap`}>
621658
<input
622659
autoComplete="off"
@@ -634,13 +671,16 @@ const InternalInputNumber = React.forwardRef(
634671
readOnly={readOnly}
635672
/>
636673
</div>
674+
675+
{type === 'spinner' && controls && upNode}
637676
</div>
638677
);
639678
},
640679
);
641680

642681
const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, ref) => {
643682
const {
683+
type = 'input',
644684
disabled,
645685
style,
646686
prefixCls = 'rc-input-number',
@@ -675,7 +715,7 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r
675715
return (
676716
<SemanticContext.Provider value={memoizedValue}>
677717
<BaseInput
678-
className={className}
718+
className={clsx(`${prefixCls}-type-${type}`, className)}
679719
triggerFocus={focus}
680720
prefixCls={prefixCls}
681721
value={value}
@@ -696,6 +736,7 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r
696736
ref={holderRef}
697737
>
698738
<InternalInputNumber
739+
type={type}
699740
prefixCls={prefixCls}
700741
disabled={disabled}
701742
ref={inputFocusRef}

0 commit comments

Comments
 (0)