Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions assets/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,24 @@
.handler-disabled();
}
}

&-type-spinner {
display: inline-flex;
align-items: center;
}

&-type-spinner &-handler {
flex: 0 0 20px;
line-height: 26px;
height: 100%;
}

&-type-spinner &-handler-up {
border-bottom: 0;
border-left: 1px solid #d9d9d9;
}
&-type-spinner &-handler-down {
border-top: 0;
border-right: 1px solid #d9d9d9;
}
}
78 changes: 78 additions & 0 deletions docs/demo/spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* eslint no-console:0 */
import InputNumber from '@rc-component/input-number';
import React from 'react';
import '../../assets/index.less';

export default () => {
const [disabled, setDisabled] = React.useState(false);
const [readOnly, setReadOnly] = React.useState(false);
const [keyboard, setKeyboard] = React.useState(true);
const [wheel, setWheel] = React.useState(true);
const [stringMode, setStringMode] = React.useState(false);
const [value, setValue] = React.useState<string | number>(93);

const onChange = (val: number) => {
console.warn('onChange:', val, typeof val);
setValue(val);
};

return (
<div style={{ margin: 10 }}>
<h3>Controlled</h3>
<InputNumber
type="spinner"
aria-label="Simple number input example"
min={-8}
max={10}
style={{ width: 100 }}
value={value}
onChange={onChange}
readOnly={readOnly}
disabled={disabled}
keyboard={keyboard}
changeOnWheel={wheel}
stringMode={stringMode}
/>
<p>
<button type="button" onClick={() => setDisabled(!disabled)}>
toggle Disabled ({String(disabled)})
</button>
<button type="button" onClick={() => setReadOnly(!readOnly)}>
toggle readOnly ({String(readOnly)})
</button>
<button type="button" onClick={() => setKeyboard(!keyboard)}>
toggle keyboard ({String(keyboard)})
</button>
<button type="button" onClick={() => setStringMode(!stringMode)}>
toggle stringMode ({String(stringMode)})
</button>
<button type="button" onClick={() => setWheel(!wheel)}>
toggle wheel ({String(wheel)})
</button>
</p>

<hr />
<h3>Uncontrolled</h3>
<InputNumber
type="spinner"
style={{ width: 100 }}
onChange={onChange}
min={-99}
max={99}
defaultValue={33}
/>

<hr />
<h3>!changeOnBlur</h3>
<InputNumber
type="spinner"
style={{ width: 100 }}
min={-9}
max={9}
defaultValue={10}
onChange={onChange}
changeOnBlur={false}
/>
</div>
);
};
4 changes: 4 additions & 0 deletions docs/example.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ nav:
## focus

<code src="./demo/focus.tsx"></code>

## spinner

<code src="./demo/spinner.tsx"></code>
73 changes: 57 additions & 16 deletions src/InputNumber.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BaseInput } from '@rc-component/input';
import getMiniDecimal, {
DecimalClass,
getNumberPrecision,
Expand All @@ -6,20 +7,20 @@ import getMiniDecimal, {
validateNumber,
ValueType,
} from '@rc-component/mini-decimal';
import { clsx } from 'clsx';
import { BaseInput } from '@rc-component/input';
import { useLayoutUpdateEffect } from '@rc-component/util/lib/hooks/useLayoutEffect';
import proxyObject from '@rc-component/util/lib/proxyObject';
import { composeRef } from '@rc-component/util/lib/ref';
import { clsx } from 'clsx';
import * as React from 'react';
import useCursor from './hooks/useCursor';
import SemanticContext from './SemanticContext';
import StepHandler from './StepHandler';
import { getDecupleSteps } from './utils/numberUtil';
import SemanticContext from './SemanticContext';

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

export type { ValueType };
Expand Down Expand Up @@ -54,7 +55,7 @@ const getDecimalIfValidate = (value: ValueType) => {
return decimal.isInvalidate() ? null : decimal;
};

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

type?: 'input' | 'spinner';

defaultValue?: T;
value?: T | null;

Expand Down Expand Up @@ -119,6 +122,7 @@ type InternalInputNumberProps = Omit<InputNumberProps, 'prefix' | 'suffix'> & {
const InternalInputNumber = React.forwardRef(
(props: InternalInputNumberProps, ref: React.Ref<HTMLInputElement>) => {
const {
type,
prefixCls,
className,
style,
Expand Down Expand Up @@ -154,6 +158,8 @@ const InternalInputNumber = React.forwardRef(
...inputProps
} = props;

const { classNames, styles } = React.useContext(SemanticContext) || {};

const inputClassName = `${prefixCls}-input`;

const inputRef = React.useRef<HTMLInputElement>(null);
Expand Down Expand Up @@ -435,7 +441,7 @@ const InternalInputNumber = React.forwardRef(
};

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

inputRef.current?.focus();
};
});

// ============================ Flush =============================
/**
Expand Down Expand Up @@ -586,6 +592,35 @@ const InternalInputNumber = React.forwardRef(
}, [inputValue]);

// ============================ Render ============================
// >>>>>> Handler
const sharedHandlerProps = {
prefixCls,
onStep: onInternalStep,
className: classNames?.action,
style: styles?.action,
};

const upNode = (
<StepHandler
{...sharedHandlerProps}
action="up"
disabled={upDisabled}
>
{upHandler}
</StepHandler>
);

const downNode = (
<StepHandler
{...sharedHandlerProps}
action="down"
disabled={downDisabled}
>
{downHandler}
</StepHandler>
);

// >>>>>> Render
return (
<div
ref={domRef}
Expand All @@ -607,16 +642,18 @@ const InternalInputNumber = React.forwardRef(
onCompositionEnd={onCompositionEnd}
onBeforeInput={onBeforeInput}
>
{controls && (
<StepHandler
prefixCls={prefixCls}
upNode={upHandler}
downNode={downHandler}
upDisabled={upDisabled}
downDisabled={downDisabled}
onStep={onInternalStep}
/>
{type === 'input' && controls && (
<div
className={clsx(`${prefixCls}-handler-wrap`, classNames?.actions)}
style={styles?.actions}
>
{upNode}
{downNode}
</div>
)}

{type === 'spinner' && controls && downNode}

<div className={`${inputClassName}-wrap`}>
<input
autoComplete="off"
Expand All @@ -634,13 +671,16 @@ const InternalInputNumber = React.forwardRef(
readOnly={readOnly}
/>
</div>

{type === 'spinner' && controls && upNode}
</div>
);
},
);

const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, ref) => {
const {
type = 'input',
disabled,
style,
prefixCls = 'rc-input-number',
Expand Down Expand Up @@ -675,7 +715,7 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r
return (
<SemanticContext.Provider value={memoizedValue}>
<BaseInput
className={className}
className={clsx(`${prefixCls}-type-${type}`, className)}
triggerFocus={focus}
prefixCls={prefixCls}
value={value}
Expand All @@ -696,6 +736,7 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r
ref={holderRef}
>
<InternalInputNumber
type={type}
prefixCls={prefixCls}
disabled={disabled}
ref={inputFocusRef}
Expand Down
Loading