diff --git a/src/InputNumber.tsx b/src/InputNumber.tsx index 969e97e2..44795bb8 100644 --- a/src/InputNumber.tsx +++ b/src/InputNumber.tsx @@ -15,6 +15,7 @@ import * as React from 'react'; import useCursor from './hooks/useCursor'; 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'; @@ -53,6 +54,7 @@ const getDecimalIfValidate = (value: ValueType) => { return decimal.isInvalidate() ? null : decimal; }; +type SemanticName = 'actions' | 'input'; export interface InputNumberProps extends Omit< React.InputHTMLAttributes, @@ -76,9 +78,8 @@ export interface InputNumberProps suffix?: React.ReactNode; addonBefore?: React.ReactNode; addonAfter?: React.ReactNode; - classNames?: BaseInputProps['classNames'] & { - input?: string; - }; + classNames?: BaseInputProps['classNames'] & Partial>; + styles?: BaseInputProps['styles'] & Partial>; // Customize handler node upHandler?: React.ReactNode; @@ -99,7 +100,10 @@ export interface InputNumberProps onChange?: (value: T | null) => void; onPressEnter?: React.KeyboardEventHandler; - onStep?: (value: T, info: { offset: ValueType; type: 'up' | 'down', emitter: 'handler' | 'keyboard' | 'wheel' }) => void; + onStep?: ( + value: T, + info: { offset: ValueType; type: 'up' | 'down'; emitter: 'handler' | 'keyboard' | 'wheel' }, + ) => void; /** * Trigger change onBlur event. @@ -131,7 +135,6 @@ const InternalInputNumber = React.forwardRef( changeOnWheel = false, controls = true, - classNames, stringMode, parser, @@ -151,6 +154,8 @@ const InternalInputNumber = React.forwardRef( ...inputProps } = props; + const { classNames, styles } = React.useContext(SemanticContext) || {}; + const inputClassName = `${prefixCls}-input`; const inputRef = React.useRef(null); @@ -614,7 +619,10 @@ const InternalInputNumber = React.forwardRef( onStep={onInternalStep} /> )} -
+
((props, r addonAfter, className, classNames, + styles, ...rest } = props; @@ -667,37 +676,41 @@ const InputNumber = React.forwardRef((props, r nativeElement: holderRef.current.nativeElement || inputNumberDomRef.current, }), ); - + const memoizedValue = React.useMemo(() => ({ classNames, styles }), [classNames, styles]); return ( - - + - + style={style} + prefix={prefix} + suffix={suffix} + addonAfter={addonAfter} + addonBefore={addonBefore} + classNames={classNames} + styles={styles} + components={{ + affixWrapper: 'div', + groupWrapper: 'div', + wrapper: 'div', + groupAddon: 'div', + }} + ref={holderRef} + > + + + ); }) as (( props: React.PropsWithChildren> & { diff --git a/src/SemanticContext.ts b/src/SemanticContext.ts new file mode 100644 index 00000000..d8d932a8 --- /dev/null +++ b/src/SemanticContext.ts @@ -0,0 +1,11 @@ +import React from 'react'; +import { InputNumberProps } from './InputNumber'; + +interface SemanticContextProps { + classNames?: InputNumberProps['classNames']; + styles?: InputNumberProps['styles']; +} + +const SemanticContext = React.createContext(undefined); + +export default SemanticContext; diff --git a/src/StepHandler.tsx b/src/StepHandler.tsx index 4051d189..63394ad7 100644 --- a/src/StepHandler.tsx +++ b/src/StepHandler.tsx @@ -1,8 +1,9 @@ /* eslint-disable react/no-unknown-property */ import * as React from 'react'; -import classNames from 'classnames'; +import cls from 'classnames'; import useMobile from '@rc-component/util/lib/hooks/useMobile'; import raf from '@rc-component/util/lib/raf'; +import SemanticContext from './SemanticContext'; /** * When click and hold on a button - the speed of auto changing the value. @@ -22,7 +23,6 @@ export interface StepHandlerProps { downDisabled?: boolean; onStep: (up: boolean, emitter: 'handler' | 'keyboard' | 'wheel') => void; } - export default function StepHandler({ prefixCls, upNode, @@ -38,11 +38,12 @@ export default function StepHandler({ const onStepRef = React.useRef(); onStepRef.current = onStep; + const { classNames, styles } = React.useContext(SemanticContext) || {}; + const onStopStep = () => { clearTimeout(stepTimeoutRef.current); }; - // We will interval update step when hold mouse down const onStepMouseDown = (e: React.MouseEvent, up: boolean) => { e.preventDefault(); @@ -61,10 +62,13 @@ export default function StepHandler({ stepTimeoutRef.current = setTimeout(loopStep, STEP_DELAY); }; - React.useEffect(() => () => { - onStopStep(); - frameIds.current.forEach(id => raf.cancel(id)); - }, []); + React.useEffect( + () => () => { + onStopStep(); + frameIds.current.forEach((id) => raf.cancel(id)); + }, + [], + ); // ======================= Render ======================= const isMobile = useMobile(); @@ -74,16 +78,16 @@ export default function StepHandler({ const handlerClassName = `${prefixCls}-handler`; - const upClassName = classNames(handlerClassName, `${handlerClassName}-up`, { + const upClassName = cls(handlerClassName, `${handlerClassName}-up`, { [`${handlerClassName}-up-disabled`]: upDisabled, }); - const downClassName = classNames(handlerClassName, `${handlerClassName}-down`, { + const downClassName = cls(handlerClassName, `${handlerClassName}-down`, { [`${handlerClassName}-down-disabled`]: downDisabled, }); // fix: https://github.com/ant-design/ant-design/issues/43088 - // In Safari, When we fire onmousedown and onmouseup events in quick succession, - // there may be a problem that the onmouseup events are executed first, + // In Safari, When we fire onmousedown and onmouseup events in quick succession, + // there may be a problem that the onmouseup events are executed first, // resulting in a disordered program execution. // So, we need to use requestAnimationFrame to ensure that the onmouseup event is executed after the onmousedown event. const safeOnStopStep = () => frameIds.current.push(raf(onStopStep)); @@ -96,7 +100,7 @@ export default function StepHandler({ }; return ( -
+
{ diff --git a/tests/semantic.test.tsx b/tests/semantic.test.tsx new file mode 100644 index 00000000..9a74cb81 --- /dev/null +++ b/tests/semantic.test.tsx @@ -0,0 +1,41 @@ +import { render } from '@testing-library/react'; +import InputNumber from '../src'; +import React from 'react'; + +describe('InputNumber.Semantic', () => { + it('support classNames and styles', () => { + const testClassNames = { + prefix: 'test-prefix', + input: 'test-input', + suffix: 'test-suffix', + actions: 'test-handle', + }; + const testStyles = { + prefix: { color: 'red' }, + input: { color: 'blue' }, + suffix: { color: 'green' }, + actions: { color: 'yellow' }, + }; + const { container } = render( + suffix
} + styles={testStyles} + classNames={testClassNames} + />, + ); + const input = container.querySelector('.rc-input-number')!; + const prefix = container.querySelector('.rc-input-number-prefix')!; + const suffix = container.querySelector('.rc-input-number-suffix')!; + const actions = container.querySelector('.rc-input-number-input-wrap')!; + expect(input.className).toContain(testClassNames.input); + expect(prefix.className).toContain(testClassNames.prefix); + expect(suffix.className).toContain(testClassNames.suffix); + expect(actions.className).toContain(testClassNames.actions); + expect(prefix).toHaveStyle(testStyles.prefix); + expect(input).toHaveStyle(testStyles.input); + expect(suffix).toHaveStyle(testStyles.suffix); + expect(actions).toHaveStyle(testStyles.actions); + }); +});