diff --git a/packages/ui-toolkit/src/components/atoms/DataRowInput/DataRowInput.tsx b/packages/ui-toolkit/src/components/atoms/DataRowInput/DataRowInput.tsx index caa9c003..e58e4772 100644 --- a/packages/ui-toolkit/src/components/atoms/DataRowInput/DataRowInput.tsx +++ b/packages/ui-toolkit/src/components/atoms/DataRowInput/DataRowInput.tsx @@ -1,4 +1,9 @@ -import React, { useState } from 'react'; +import React, { + useState, + forwardRef, + useCallback, + memo +} from 'react'; import cn from 'classnames'; import type { ReactIconComponentType } from '@groww-tech/icon-store'; import { ContentMintTokens } from '../../../types/mint-token-types/content-mint-tokens'; @@ -6,6 +11,10 @@ import { BackgroundMintTokens } from '../../../types/mint-token-types/background import { BorderMintTokens } from '../../../types/mint-token-types/border-mint-tokens'; import './styles/index.css'; +// Allow navigation/control keys (backspace, delete, arrows, etc.) +const allowedKeys = [ 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Tab', 'Enter', 'Home', 'End' ]; + + export type DataRowInputProps = { placeholder?: string; value: string; @@ -14,7 +23,6 @@ export type DataRowInputProps = { width?: string; PrefixIcon?: ReactIconComponentType; prefixLabel?: string; - ref?: React.RefObject; disabled?: boolean; error?: boolean; warning?: boolean; @@ -32,10 +40,54 @@ export type DataRowInputProps = { backgroundColor?: BackgroundMintTokens; disableCopyPaste?: boolean; onEnterPress?: (e: React.KeyboardEvent) => void; + onFocus?: (e: React.FocusEvent) => void; + onBlur?: (e: React.FocusEvent) => void; } +// Memoized prefix component to prevent unnecessary re-renders +const PrefixComponent = memo(({ + PrefixIcon, + prefixLabel, + prefixIconColor, + perfixTextColor, + dataTestId +}: { + PrefixIcon?: ReactIconComponentType; + prefixLabel?: string; + prefixIconColor: ContentMintTokens; + perfixTextColor: ContentMintTokens; + dataTestId?: string; +}) => ( +
+ { + PrefixIcon && ( +
+ +
+ ) + } + { + prefixLabel && ( +
+ {prefixLabel} +
+ ) + } +
+)); + +PrefixComponent.displayName = 'PrefixComponent'; -const DataRowInput: React.FC = ({ +const DataRowInput = forwardRef(({ placeholder, value, onChange, @@ -43,7 +95,6 @@ const DataRowInput: React.FC = ({ width = '128px', PrefixIcon, prefixLabel, - ref, disabled = false, error = false, warning = false, @@ -60,98 +111,101 @@ const DataRowInput: React.FC = ({ backgroundColor = 'backgroundPrimary', borderColor = 'borderPrimary', disableCopyPaste = false, - onEnterPress -}) => { + onEnterPress, + onFocus, + onBlur +}, ref) => { const [ isFocused, setIsFocused ] = useState(false); - const inputClasses = cn('datarow-input', textAlign); - const inputWrapperClasses = cn('datarow-inputWrapper'); + const hasPrefix = Boolean(PrefixIcon || prefixLabel); - - const handleWheel = (e: React.WheelEvent) => { + // Memoize event handlers to prevent unnecessary re-renders + const handleWheel = useCallback((e: React.WheelEvent) => { e.currentTarget.blur(); - }; - + }, []); - const handleKeyDown = (e: React.KeyboardEvent) => { + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { + // Only block the period when disableDecimal is true if (disableDecimal && e.key === '.') { e.preventDefault(); + return; + } + + + // If it's not a digit, not a period, and not in the allowed keys list, block it + if (!/^[0-9]$/.test(e.key) && e.key !== '.' && !allowedKeys.includes(e.key)) { + e.preventDefault(); + return; } if (e.key === 'Enter' && onEnterPress) { onEnterPress(e); } - onKeyDown && onKeyDown(e); - }; + onKeyDown?.(e); + }, [ disableDecimal, onEnterPress, onKeyDown ]); - - const handleCopyPaste = (e: React.ClipboardEvent) => { + const handleCopyPaste = useCallback((e: React.ClipboardEvent) => { if (disableCopyPaste) { e.preventDefault(); } - }; + }, [ disableCopyPaste ]); + + const handleFocus = useCallback((e: React.FocusEvent) => { + setIsFocused(true); + onFocus?.(e); + }, [ onFocus ]); + + const handleBlur = useCallback((e: React.FocusEvent) => { + setIsFocused(false); + onBlur?.(e); + }, [ onBlur ]); + // Memoize class names to avoid recalculation on each render const inputContentClasses = cn( `datarow-inputContent ${textColor} ${borderColor}`, { [backgroundColor]: !disabled, 'datarow-inputBorderNegative': error, 'datarow-inputBorderWarning': warning, - 'datarow-inputPrefix': PrefixIcon || prefixLabel, + 'datarow-inputPrefix': hasPrefix, 'datarow-inputFocused': isFocused && !disabled && !error, 'backgroundSecondary contentSecondary': disabled } ); + const inputClasses = `datarow-input ${textAlign} ${textStyle} ${textColor} datarow-contentPrimary`; + + return (
{ - (PrefixIcon || prefixLabel) && ( -
- { - PrefixIcon && ( -
- {/* Hardcoding size to 20 to maintain consistency across different icons and elements */} - -
- ) - } - { - prefixLabel && ( -
- {prefixLabel} -
- ) - } -
+ hasPrefix && ( + ) } setIsFocused(true)} - onBlur={() => setIsFocused(false)} + onFocus={handleFocus} + onBlur={handleBlur} disabled={disabled} data-test-id={dataTestId} ref={ref} @@ -168,6 +222,6 @@ const DataRowInput: React.FC = ({
); -}; +}); -export default DataRowInput; +export default memo(DataRowInput); diff --git a/packages/ui-toolkit/src/components/atoms/FreeFormInput/FreeFormInput.tsx b/packages/ui-toolkit/src/components/atoms/FreeFormInput/FreeFormInput.tsx index 37dfb9e4..75361ea4 100644 --- a/packages/ui-toolkit/src/components/atoms/FreeFormInput/FreeFormInput.tsx +++ b/packages/ui-toolkit/src/components/atoms/FreeFormInput/FreeFormInput.tsx @@ -1,4 +1,10 @@ -import React, { useState, useEffect } from 'react'; +import React, { + useState, + useEffect, + forwardRef, + useCallback, + memo +} from 'react'; import cn from 'classnames'; import { MdsIcCancelCircle, @@ -11,6 +17,158 @@ import type { ReactIconComponentType } from '@groww-tech/icon-store'; import { ContentMintTokens } from '../../../types/mint-token-types/content-mint-tokens'; import './styles/index.css'; +const allowedKeys = [ 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Enter', 'Home', 'End', '.' ]; + + +type PrefixSectionProps = { + PrefixIcon?: ReactIconComponentType; + prefixLabel?: string; + prefixIconColor: ContentMintTokens; + prefixTextColor: ContentMintTokens; + prefixTextStyle: 'bodyBase' | 'bodyBaseHeavy'; + dataTestId?: string; +}; + +const PrefixSection = memo(({ + PrefixIcon, + prefixLabel, + prefixIconColor, + prefixTextColor, + prefixTextStyle, + dataTestId +}) => { + if (!PrefixIcon && !prefixLabel) return null; + + return ( +
+ { + PrefixIcon && ( +
+ +
+ ) + } + { + prefixLabel && ( +
+ {prefixLabel} +
+ ) + } +
+ ); +}); + + +type SuffixSectionProps = { + clearable: boolean; + showClearIcon: boolean; + variant: 'text' | 'password' | 'number'; + showPassword: boolean; + setShowPassword: React.Dispatch>; + SuffixIcon?: ReactIconComponentType; + suffixIconButton?: SuffixIconButtonProps; + handleClear: () => void; + clearIconColor: ContentMintTokens; + passwordToggleIconColor: ContentMintTokens; + suffixIconColor: ContentMintTokens; + suffixIconButtonColor: ContentMintTokens; + disabled?: boolean; + dataTestId?: string; +}; + +const SuffixSection = memo(({ + clearable, + showClearIcon, + variant, + showPassword, + setShowPassword, + SuffixIcon, + suffixIconButton, + handleClear, + clearIconColor, + passwordToggleIconColor, + suffixIconColor, + suffixIconButtonColor, + disabled, + dataTestId +}) => { + const showSuffixContainer = (clearable && showClearIcon) || variant === 'password' || !!SuffixIcon || !!suffixIconButton; + + if (!showSuffixContainer) return null; + + return ( +
+ { + clearable && showClearIcon && ( +
+ +
+ ) + } + + { + variant === 'password' && ( +
+ setShowPassword(!showPassword)} + Icon={showPassword ? MdsIcHideEye : MdsIcShowEye} + size="medium" + data-test-id={`${dataTestId}-password-toggle-button`} + iconColor={passwordToggleIconColor} + /> +
+ ) + } + + { + SuffixIcon && ( +
+ +
+ ) + } + + { + suffixIconButton && ( +
+ +
+ ) + } +
+ ); +}); + type SuffixIconButtonProps = { icon: ReactIconComponentType; @@ -34,14 +192,13 @@ export type FreeFormInputProps = { error?: boolean; errorMessage?: string; clearable?: boolean; - ref?: React.RefObject; helperText?: string; helperTextColor?: ContentMintTokens; variant?: 'text' | 'password' | 'number'; onKeyDown?: (e: React.KeyboardEvent) => void; autoComplete?: string; onKeyUp?: (e: React.KeyboardEvent) => void; - perfixTextColor?: ContentMintTokens; + prefixTextColor?: ContentMintTokens; prefixTextStyle?: 'bodyBase' | 'bodyBaseHeavy'; onEnterPress?: (e: React.KeyboardEvent) => void; disableCopyPaste?: boolean; @@ -51,11 +208,11 @@ export type FreeFormInputProps = { suffixIconButtonColor?: ContentMintTokens; clearIconColor?: ContentMintTokens; passwordToggleIconColor?: ContentMintTokens; - + onFocus?: (e: React.FocusEvent) => void; + onBlur?: (e: React.FocusEvent) => void; }; - -const FreeFormInput: React.FC = ({ +const FreeFormInput = forwardRef(({ placeholder, value, label, @@ -72,14 +229,13 @@ const FreeFormInput: React.FC = ({ error = false, errorMessage = '', clearable = false, - ref, helperText, helperTextColor = 'contentSecondary', variant = 'text', onKeyDown, autoComplete, onKeyUp, - perfixTextColor = 'contentSecondary', + prefixTextColor = 'contentSecondary', prefixTextStyle = 'bodyBase', onEnterPress, disableCopyPaste = false, @@ -88,19 +244,21 @@ const FreeFormInput: React.FC = ({ suffixIconColor = 'contentSecondary', suffixIconButtonColor = 'contentSecondary', clearIconColor = 'contentSecondary', - passwordToggleIconColor = 'contentSecondary' -}) => { + passwordToggleIconColor = 'contentSecondary', + onFocus, + onBlur +}, ref) => { const [ showClearIcon, setShowClearIcon ] = useState(false); const [ isFocused, setIsFocused ] = useState(false); const [ showPassword, setShowPassword ] = useState(false); + // Update clear icon visibility when value changes useEffect(() => { - setShowClearIcon(!!clearable && value.length > 0); + setShowClearIcon(clearable && value.length > 0); }, [ clearable, value ]); - const inputClasses = cn('freeform-input'); - const inputWrapperClasses = cn('freeform-inputWrapper flex width100'); - const inputContentClasses = cn('freeform-inputContent contentPrimary borderPrimary', { + // Memoize class computation to prevent recalculation on every render + const inputContentClasses = React.useMemo(() => cn('freeform-inputContent contentPrimary borderPrimary', { 'backgroundPrimary': !disabled, 'freeform-inputBorderNegative': error, 'freeform-inputClearable': clearable, @@ -108,17 +266,9 @@ const FreeFormInput: React.FC = ({ 'freeform-inputSuffix': SuffixIcon || (clearable && showClearIcon) || variant === 'password', 'freeform-inputFocused': isFocused && !disabled && !error, 'backgroundSecondary contentSecondary': disabled - }); - + }), [ disabled, error, clearable, PrefixIcon, prefixLabel, SuffixIcon, showClearIcon, variant, isFocused ]); - const handleWheel = (e: React.WheelEvent) => { - if (variant === 'number') { - e.currentTarget.blur(); - } - }; - - - const handleClear = () => { + const handleClear = useCallback(() => { if (onChange) { const event = { target: { value: '' }, @@ -129,91 +279,93 @@ const FreeFormInput: React.FC = ({ onChange(event); } - }; + }, [ onChange ]); + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { + onKeyDown?.(e); - const handleCopyPaste = (e: React.ClipboardEvent) => { - if (disableCopyPaste) { + if (e.key === 'Enter' && onEnterPress) { + onEnterPress(e); + } + + // Only block decimal point when disableDecimal is true + if (disableDecimal && e.key === '.') { e.preventDefault(); + return; } - }; + if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { + e.preventDefault(); + } - const togglePasswordVisibility = () => { + // For number variant, only prevent non-numeric input with exceptions for navigation keys + if (variant === 'number') { - setShowPassword(!showPassword); - }; + if (!/^[0-9]$/.test(e.key) && !allowedKeys.includes(e.key)) { + e.preventDefault(); + } + } + }, [ onKeyDown, onEnterPress, disableDecimal, variant ]); + const handleFocus = useCallback((e: React.FocusEvent) => { + setIsFocused(true); + onFocus?.(e); + }, [ onFocus ]); - const handleKeyDown = (e: React.KeyboardEvent) => { - if (onKeyDown) onKeyDown(e); - if (e.key === 'Enter' && onEnterPress) { - onEnterPress(e); - } + const handleBlur = useCallback((e: React.FocusEvent) => { + setIsFocused(false); + onBlur?.(e); + }, [ onBlur ]); - if (disableDecimal && (e.key === '.')) { + const handleCopyPaste = useCallback((e: React.ClipboardEvent) => { + if (disableCopyPaste) { e.preventDefault(); } - }; + }, [ disableCopyPaste ]); + + const handleWheel = useCallback((e: React.WheelEvent) => { + if (variant === 'number') { + e.currentTarget.blur(); + } + }, [ variant ]); return (
{ label && (
{label}
) } -
- { - (PrefixIcon || prefixLabel) && ( -
- { - PrefixIcon && ( -
- {/* Hardcoding size to 20 to maintain consistency across different icons and elements */} - -
- ) - } - { - prefixLabel && ( -
- {prefixLabel} -
- ) - } -
- ) - } + + setIsFocused(true)} - onBlur={() => setIsFocused(false)} + onFocus={handleFocus} + onBlur={handleBlur} disabled={disabled} data-test-id={dataTestId} maxLength={maxLength} @@ -226,75 +378,25 @@ const FreeFormInput: React.FC = ({ onCut={handleCopyPaste} onPaste={handleCopyPaste} /> - { - (clearable && showClearIcon) || variant === 'password' || SuffixIcon || suffixIconButton ? ( -
- { - clearable && showClearIcon && ( -
- -
- ) - } - { - variant === 'password' && ( -
- -
- ) - } - { - SuffixIcon && ( -
- {/* Hardcoding size to 20 to maintain consistency across different icons and elements */} - -
- ) - } - { - suffixIconButton && ( -
- -
- ) - } -
- ) : null - } + +
+ { helperText && (
= ({
) } + { error && errorMessage && (
@@ -318,6 +421,6 @@ const FreeFormInput: React.FC = ({ }
); -}; +}); -export default FreeFormInput; +export default memo(FreeFormInput); diff --git a/packages/ui-toolkit/src/components/atoms/FreeFormInput/styles/index.css b/packages/ui-toolkit/src/components/atoms/FreeFormInput/styles/index.css index ee4f9f81..cdcdb277 100644 --- a/packages/ui-toolkit/src/components/atoms/FreeFormInput/styles/index.css +++ b/packages/ui-toolkit/src/components/atoms/FreeFormInput/styles/index.css @@ -92,4 +92,7 @@ input::placeholder { .freeform-helperText { padding-left: 1px; +} +.freeform-label{ + padding-left: 1px; } \ No newline at end of file diff --git a/packages/ui-toolkit/src/components/atoms/InputStepper/InputStepper.tsx b/packages/ui-toolkit/src/components/atoms/InputStepper/InputStepper.tsx index df66b5b2..f0cc9e54 100644 --- a/packages/ui-toolkit/src/components/atoms/InputStepper/InputStepper.tsx +++ b/packages/ui-toolkit/src/components/atoms/InputStepper/InputStepper.tsx @@ -1,4 +1,10 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { + useState, + useEffect, + forwardRef, + useCallback, + memo +} from 'react'; import cn from 'classnames'; import type { ReactIconComponentType } from '@groww-tech/icon-store'; import { MdsIcRemoveMinus, MdsIcAddPlus } from '@groww-tech/icon-store/mint-icons'; @@ -7,6 +13,8 @@ import { ContentMintTokens } from '../../../types/mint-token-types/content-mint- import { BackgroundMintTokens } from '../../../types/mint-token-types/background-mint-tokens'; import './styles/index.css'; + // Allow navigation/control keys +const allowedKeys = [ 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Tab', 'Enter', 'Home', 'End' ]; export type InputStepperProps = { placeholder?: string; @@ -14,9 +22,6 @@ export type InputStepperProps = { onChange: (value: number) => void; dataTestId?: string; width?: string; - prefixIcon?: ReactIconComponentType; - prefixLabel?: string; - ref?: React.RefObject; error?: boolean; warning?: boolean; disabled?: boolean; @@ -33,18 +38,44 @@ export type InputStepperProps = { disableCopyPaste?: boolean; onEnterPress?: (e: React.KeyboardEvent) => void; disableDecimal?: boolean; -} - + onFocus?: (e: React.FocusEvent) => void; + onBlur?: (e: React.FocusEvent) => void; +}; -const InputStepper: React.FC = ({ +// Memoized stepper button component +const StepperButton = memo(({ + onClick, + Icon, + disabled, + dataTestId +}: { + onClick: () => void; + Icon: ReactIconComponentType; + disabled: boolean; + dataTestId: string; +}) => ( +
+ +
+)); + +StepperButton.displayName = 'StepperButton'; + +const InputStepper = forwardRef(({ placeholder, value, onChange, dataTestId, width = '128px', - prefixIcon, - prefixLabel, - ref, error = false, warning = false, disabled = false, @@ -60,73 +91,44 @@ const InputStepper: React.FC = ({ shouldFocusOnMount = false, disableCopyPaste = false, onEnterPress, - disableDecimal = false -}) => { + disableDecimal = false, + onFocus, + onBlur +}, ref) => { const [ isFocused, setIsFocused ] = useState(false); const [ inputValue, setInputValue ] = useState(value.toString()); - const internalRef = useRef(null); - const inputRef = ref || internalRef; - + // Update inputValue when value prop changes useEffect(() => { setInputValue(value.toString()); }, [ value ]); + // Focus on mount if needed useEffect(() => { - if (shouldFocusOnMount && inputRef.current) { - inputRef.current.focus(); + if (shouldFocusOnMount && ref && typeof ref !== 'function' && ref.current) { + ref.current.focus(); } - }, [ inputRef, shouldFocusOnMount ]); + }, [ shouldFocusOnMount, ref ]); - const inputClasses = cn('inputStepper-input width100 center-align', { - contentDisabled: disabled - }); - - const inputWrapperClasses = cn('inputStepper-inputWrapper'); - - const inputContentClasses = cn( - `inputStepper-inputContent pos-rel flex ${textStyle} ${textColor} borderPrimary`, - { - [backgroundColor]: !disabled, - 'inputStepper-inputBorderNegative': error, - 'inputStepper-inputBorderWarning': warning, - 'inputStepper-inputPrefix': prefixIcon || prefixLabel, - 'inputStepper-inputFocused': isFocused && !disabled && !error, - 'backgroundSecondary contentSecondary': disabled, - contentDisabled: disabled - } - ); - - - const handleWheel = (e: React.WheelEvent) => { + // Memoized event handlers + const handleWheel = useCallback((e: React.WheelEvent) => { e.currentTarget.blur(); - }; - - - const handleCopyPaste = (e: React.ClipboardEvent) => { - if (disableCopyPaste) { - e.preventDefault(); - } - }; - + }, []); - const handleMinus = () => { + const handleMinus = useCallback(() => { if (value > min && !disabled) { onChange(value - step); } - }; + }, [ value, min, disabled, onChange, step ]); - - const handlePlus = () => { + const handlePlus = useCallback(() => { if (value < max && !disabled) { onChange(value + step); } - }; - + }, [ value, max, disabled, onChange, step ]); - const handleChange = (e: React.ChangeEvent) => { + const handleChange = useCallback((e: React.ChangeEvent) => { if (!typeable) return; - const newValue = e.target.value; if (newValue === '') { @@ -141,11 +143,11 @@ const InputStepper: React.FC = ({ onChange(numValue); } } - }; - + }, [ typeable, min, max, onChange ]); - const handleBlur = () => { + const handleBlur = useCallback((e: React.FocusEvent) => { setIsFocused(false); + if (inputValue === '') { setInputValue('0'); onChange(0); @@ -156,113 +158,124 @@ const InputStepper: React.FC = ({ setInputValue(numValue.toString()); onChange(numValue); } - }; + onBlur?.(e); + }, [ inputValue, onChange, onBlur ]); + + const handleFocus = useCallback((e: React.FocusEvent) => { + setIsFocused(true); + onFocus?.(e); + }, [ onFocus ]); + + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { + // Only block decimal point when disableDecimal is true + if (disableDecimal && e.key === '.') { + e.preventDefault(); + return; + } + + if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { + e.preventDefault(); + } + + + // Block keys that aren't digits, a period, or in the allowed keys list + if (!/^[0-9]$/.test(e.key) && e.key !== '.' && !allowedKeys.includes(e.key)) { + e.preventDefault(); + return; + } - const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && onEnterPress) { onEnterPress(e); } - if (disableDecimal && e.key === '.') { + onKeyDown?.(e); + }, [ onEnterPress, disableDecimal, onKeyDown ]); + + const handleKeyUp = useCallback((e: React.KeyboardEvent) => { + if (e.key === 'ArrowUp') { + if (value < max && !disabled) { + onChange(value + step); + } + + } else if (e.key === 'ArrowDown') { + if (value > min && !disabled) { + onChange(value - step); + } + } + + onKeyUp?.(e); + }, [ value, max, min, disabled, onChange, step, onKeyUp ]); + const handleCopyPaste = useCallback((e: React.ClipboardEvent) => { + if (disableCopyPaste) { e.preventDefault(); } + }, [ disableCopyPaste ]); - if (onKeyDown) { - onKeyDown(e); + // Memoized class names + const inputClasses = cn('inputStepper-input width100 center-align', { + contentDisabled: disabled + }); + + const inputContentClasses = cn( + `inputStepper-inputContent pos-rel flex ${textStyle} ${textColor} borderPrimary`, + { + [backgroundColor]: !disabled, + 'inputStepper-inputBorderNegative': error, + 'inputStepper-inputBorderWarning': warning, + 'inputStepper-inputFocused': isFocused && !disabled && !error, + 'backgroundSecondary contentSecondary': disabled, + contentDisabled: disabled } - }; + ); return (
-
- -
- - { - (prefixIcon || prefixLabel) && ( -
- { - prefixIcon && ( -
- {prefixIcon} -
- ) - } - { - prefixLabel && ( -
- {prefixLabel} -
- ) - } -
- ) - } + setIsFocused(true)} + onFocus={handleFocus} onBlur={handleBlur} disabled={disabled} data-test-id={dataTestId} - ref={inputRef} + ref={ref} onWheel={handleWheel} readOnly={!typeable} onKeyDown={handleKeyDown} - onKeyUp={onKeyUp} + onKeyUp={handleKeyUp} onCopy={handleCopyPaste} onCut={handleCopyPaste} onPaste={handleCopyPaste} /> -
- = max} - size="small" - dataTestId={`${dataTestId}-plus-button`} - /> -
+ = max} + dataTestId={`${dataTestId}-plus-container`} + />
); -}; +}); -export default InputStepper; +export default memo(InputStepper); diff --git a/packages/ui-toolkit/stories/FreeFormInput.stories.tsx b/packages/ui-toolkit/stories/FreeFormInput.stories.tsx index 43338b20..5693ed77 100644 --- a/packages/ui-toolkit/stories/FreeFormInput.stories.tsx +++ b/packages/ui-toolkit/stories/FreeFormInput.stories.tsx @@ -36,7 +36,9 @@ Default.args = { errorMessage: '', disabled: false, clearable: false, - helperText: 'Helper text here' + helperText: 'Helper text here', + onFocus: () => console.log('Focused'), + onBlur: () => console.log('Blurred'), }; export const WithError = Template.bind({}); diff --git a/packages/ui-toolkit/stories/InputStepper.stories.tsx b/packages/ui-toolkit/stories/InputStepper.stories.tsx index cd73e360..2132acf9 100644 --- a/packages/ui-toolkit/stories/InputStepper.stories.tsx +++ b/packages/ui-toolkit/stories/InputStepper.stories.tsx @@ -36,6 +36,13 @@ Default.args = { value: 0, width: '128px' }; +export const FocusOnMount = Template.bind({}); +FocusOnMount.args = { + placeholder: '0', + value: 0, + width: '128px', + shouldFocusOnMount: true +}; export const WithError = Template.bind({}); WithError.args = {