Skip to content

Commit 38b4908

Browse files
authored
feat: support classNames and styles (#717)
* feat: support classNames and styles * rm * handle to actions * fix ci
1 parent 4470642 commit 38b4908

File tree

4 files changed

+115
-46
lines changed

4 files changed

+115
-46
lines changed

src/InputNumber.tsx

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import * as React from 'react';
1515
import useCursor from './hooks/useCursor';
1616
import StepHandler from './StepHandler';
1717
import { getDecupleSteps } from './utils/numberUtil';
18+
import SemanticContext from './SemanticContext';
1819

1920
import type { HolderRef } from '@rc-component/input/lib/BaseInput';
2021
import { BaseInputProps } from '@rc-component/input/lib/interface';
@@ -53,6 +54,7 @@ const getDecimalIfValidate = (value: ValueType) => {
5354
return decimal.isInvalidate() ? null : decimal;
5455
};
5556

57+
type SemanticName = 'actions' | 'input';
5658
export interface InputNumberProps<T extends ValueType = ValueType>
5759
extends Omit<
5860
React.InputHTMLAttributes<HTMLInputElement>,
@@ -76,9 +78,8 @@ export interface InputNumberProps<T extends ValueType = ValueType>
7678
suffix?: React.ReactNode;
7779
addonBefore?: React.ReactNode;
7880
addonAfter?: React.ReactNode;
79-
classNames?: BaseInputProps['classNames'] & {
80-
input?: string;
81-
};
81+
classNames?: BaseInputProps['classNames'] & Partial<Record<SemanticName, string>>;
82+
styles?: BaseInputProps['styles'] & Partial<Record<SemanticName, React.CSSProperties>>;
8283

8384
// Customize handler node
8485
upHandler?: React.ReactNode;
@@ -99,7 +100,10 @@ export interface InputNumberProps<T extends ValueType = ValueType>
99100
onChange?: (value: T | null) => void;
100101
onPressEnter?: React.KeyboardEventHandler<HTMLInputElement>;
101102

102-
onStep?: (value: T, info: { offset: ValueType; type: 'up' | 'down', emitter: 'handler' | 'keyboard' | 'wheel' }) => void;
103+
onStep?: (
104+
value: T,
105+
info: { offset: ValueType; type: 'up' | 'down'; emitter: 'handler' | 'keyboard' | 'wheel' },
106+
) => void;
103107

104108
/**
105109
* Trigger change onBlur event.
@@ -131,7 +135,6 @@ const InternalInputNumber = React.forwardRef(
131135
changeOnWheel = false,
132136
controls = true,
133137

134-
classNames,
135138
stringMode,
136139

137140
parser,
@@ -151,6 +154,8 @@ const InternalInputNumber = React.forwardRef(
151154
...inputProps
152155
} = props;
153156

157+
const { classNames, styles } = React.useContext(SemanticContext) || {};
158+
154159
const inputClassName = `${prefixCls}-input`;
155160

156161
const inputRef = React.useRef<HTMLInputElement>(null);
@@ -614,7 +619,10 @@ const InternalInputNumber = React.forwardRef(
614619
onStep={onInternalStep}
615620
/>
616621
)}
617-
<div className={`${inputClassName}-wrap`}>
622+
<div
623+
className={clsx(`${inputClassName}-wrap`, classNames?.actions)}
624+
style={styles?.actions}
625+
>
618626
<input
619627
autoComplete="off"
620628
role="spinbutton"
@@ -648,6 +656,7 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r
648656
addonAfter,
649657
className,
650658
classNames,
659+
styles,
651660
...rest
652661
} = props;
653662

@@ -667,37 +676,41 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r
667676
nativeElement: holderRef.current.nativeElement || inputNumberDomRef.current,
668677
}),
669678
);
670-
679+
const memoizedValue = React.useMemo(() => ({ classNames, styles }), [classNames, styles]);
671680
return (
672-
<BaseInput
673-
className={className}
674-
triggerFocus={focus}
675-
prefixCls={prefixCls}
676-
value={value}
677-
disabled={disabled}
678-
style={style}
679-
prefix={prefix}
680-
suffix={suffix}
681-
addonAfter={addonAfter}
682-
addonBefore={addonBefore}
683-
classNames={classNames}
684-
components={{
685-
affixWrapper: 'div',
686-
groupWrapper: 'div',
687-
wrapper: 'div',
688-
groupAddon: 'div',
689-
}}
690-
ref={holderRef}
691-
>
692-
<InternalInputNumber
681+
<SemanticContext.Provider value={memoizedValue}>
682+
<BaseInput
683+
className={className}
684+
triggerFocus={focus}
693685
prefixCls={prefixCls}
686+
value={value}
694687
disabled={disabled}
695-
ref={inputFocusRef}
696-
domRef={inputNumberDomRef}
697-
className={classNames?.input}
698-
{...rest}
699-
/>
700-
</BaseInput>
688+
style={style}
689+
prefix={prefix}
690+
suffix={suffix}
691+
addonAfter={addonAfter}
692+
addonBefore={addonBefore}
693+
classNames={classNames}
694+
styles={styles}
695+
components={{
696+
affixWrapper: 'div',
697+
groupWrapper: 'div',
698+
wrapper: 'div',
699+
groupAddon: 'div',
700+
}}
701+
ref={holderRef}
702+
>
703+
<InternalInputNumber
704+
prefixCls={prefixCls}
705+
disabled={disabled}
706+
ref={inputFocusRef}
707+
domRef={inputNumberDomRef}
708+
className={classNames?.input}
709+
style={styles?.input}
710+
{...rest}
711+
/>
712+
</BaseInput>
713+
</SemanticContext.Provider>
701714
);
702715
}) as (<T extends ValueType = ValueType>(
703716
props: React.PropsWithChildren<InputNumberProps<T>> & {

src/SemanticContext.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
import { InputNumberProps } from './InputNumber';
3+
4+
interface SemanticContextProps {
5+
classNames?: InputNumberProps['classNames'];
6+
styles?: InputNumberProps['styles'];
7+
}
8+
9+
const SemanticContext = React.createContext<SemanticContextProps | undefined>(undefined);
10+
11+
export default SemanticContext;

src/StepHandler.tsx

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/* eslint-disable react/no-unknown-property */
22
import * as React from 'react';
3-
import classNames from 'classnames';
3+
import cls from 'classnames';
44
import useMobile from '@rc-component/util/lib/hooks/useMobile';
55
import raf from '@rc-component/util/lib/raf';
6+
import SemanticContext from './SemanticContext';
67

78
/**
89
* When click and hold on a button - the speed of auto changing the value.
@@ -22,7 +23,6 @@ export interface StepHandlerProps {
2223
downDisabled?: boolean;
2324
onStep: (up: boolean, emitter: 'handler' | 'keyboard' | 'wheel') => void;
2425
}
25-
2626
export default function StepHandler({
2727
prefixCls,
2828
upNode,
@@ -38,11 +38,12 @@ export default function StepHandler({
3838
const onStepRef = React.useRef<StepHandlerProps['onStep']>();
3939
onStepRef.current = onStep;
4040

41+
const { classNames, styles } = React.useContext(SemanticContext) || {};
42+
4143
const onStopStep = () => {
4244
clearTimeout(stepTimeoutRef.current);
4345
};
4446

45-
4647
// We will interval update step when hold mouse down
4748
const onStepMouseDown = (e: React.MouseEvent, up: boolean) => {
4849
e.preventDefault();
@@ -61,10 +62,13 @@ export default function StepHandler({
6162
stepTimeoutRef.current = setTimeout(loopStep, STEP_DELAY);
6263
};
6364

64-
React.useEffect(() => () => {
65-
onStopStep();
66-
frameIds.current.forEach(id => raf.cancel(id));
67-
}, []);
65+
React.useEffect(
66+
() => () => {
67+
onStopStep();
68+
frameIds.current.forEach((id) => raf.cancel(id));
69+
},
70+
[],
71+
);
6872

6973
// ======================= Render =======================
7074
const isMobile = useMobile();
@@ -74,16 +78,16 @@ export default function StepHandler({
7478

7579
const handlerClassName = `${prefixCls}-handler`;
7680

77-
const upClassName = classNames(handlerClassName, `${handlerClassName}-up`, {
81+
const upClassName = cls(handlerClassName, `${handlerClassName}-up`, {
7882
[`${handlerClassName}-up-disabled`]: upDisabled,
7983
});
80-
const downClassName = classNames(handlerClassName, `${handlerClassName}-down`, {
84+
const downClassName = cls(handlerClassName, `${handlerClassName}-down`, {
8185
[`${handlerClassName}-down-disabled`]: downDisabled,
8286
});
8387

8488
// fix: https://github.com/ant-design/ant-design/issues/43088
85-
// In Safari, When we fire onmousedown and onmouseup events in quick succession,
86-
// there may be a problem that the onmouseup events are executed first,
89+
// In Safari, When we fire onmousedown and onmouseup events in quick succession,
90+
// there may be a problem that the onmouseup events are executed first,
8791
// resulting in a disordered program execution.
8892
// So, we need to use requestAnimationFrame to ensure that the onmouseup event is executed after the onmousedown event.
8993
const safeOnStopStep = () => frameIds.current.push(raf(onStopStep));
@@ -96,7 +100,7 @@ export default function StepHandler({
96100
};
97101

98102
return (
99-
<div className={`${handlerClassName}-wrap`}>
103+
<div className={cls(`${handlerClassName}-wrap`, classNames?.actions)} style={styles?.actions}>
100104
<span
101105
{...sharedHandlerProps}
102106
onMouseDown={(e) => {

tests/semantic.test.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { render } from '@testing-library/react';
2+
import InputNumber from '../src';
3+
import React from 'react';
4+
5+
describe('InputNumber.Semantic', () => {
6+
it('support classNames and styles', () => {
7+
const testClassNames = {
8+
prefix: 'test-prefix',
9+
input: 'test-input',
10+
suffix: 'test-suffix',
11+
actions: 'test-handle',
12+
};
13+
const testStyles = {
14+
prefix: { color: 'red' },
15+
input: { color: 'blue' },
16+
suffix: { color: 'green' },
17+
actions: { color: 'yellow' },
18+
};
19+
const { container } = render(
20+
<InputNumber
21+
prefixCls="rc-input-number"
22+
prefix="prefix"
23+
suffix={<div>suffix</div>}
24+
styles={testStyles}
25+
classNames={testClassNames}
26+
/>,
27+
);
28+
const input = container.querySelector('.rc-input-number')!;
29+
const prefix = container.querySelector('.rc-input-number-prefix')!;
30+
const suffix = container.querySelector('.rc-input-number-suffix')!;
31+
const actions = container.querySelector('.rc-input-number-input-wrap')!;
32+
expect(input.className).toContain(testClassNames.input);
33+
expect(prefix.className).toContain(testClassNames.prefix);
34+
expect(suffix.className).toContain(testClassNames.suffix);
35+
expect(actions.className).toContain(testClassNames.actions);
36+
expect(prefix).toHaveStyle(testStyles.prefix);
37+
expect(input).toHaveStyle(testStyles.input);
38+
expect(suffix).toHaveStyle(testStyles.suffix);
39+
expect(actions).toHaveStyle(testStyles.actions);
40+
});
41+
});

0 commit comments

Comments
 (0)