react-input-pin-code
Version:
Pin input built with React component and styled-components
1 lines • 5.38 kB
Source Map (JSON)
{"version":3,"file":"index.d.ts","sources":["../../../src/components/pin-input-field/types.ts","../../../src/components/pin-input-field/component.tsx"],"sourcesContent":["import type { FC } from 'react';\r\nimport type { Props as PinInputProps } from '../pin-input'\r\nimport type { ModifierClassName } from '../../types';\r\n\r\nexport type Size = 'xs' | 'sm' | 'md' | 'lg';\r\n\r\nexport type SizeConfigMap = Record<\r\n Size,\r\n {\r\n className: ModifierClassName;\r\n }\r\n>;\r\n\r\nexport type Props = PinInputProps & {\r\n index: number;\r\n value: string;\r\n completed: boolean;\r\n};\r\n\r\nexport type Component = FC<Props>;\r\n","import { useEffect, useMemo, useRef } from \"react\";\r\nimport { Component } from \"./types\";\r\nimport cx from \"clsx\";\r\n\r\nimport styles from './styles.module.scss';\r\n\r\nimport { normalizeNewValue } from \"./utils\";\r\nimport { sizeConfigMap } from \"./configs\";\r\nimport { INPUT_KEY_BACKSPACE, SIZE_MEDIUM } from \"../../constants\";\r\nimport { validateToPattern } from \"../../utils/string\";\r\n\r\nexport const PinInputField: Component = ({\r\n index,\r\n value,\r\n values,\r\n completed,\r\n type = 'number',\r\n mask,\r\n size = SIZE_MEDIUM,\r\n validate,\r\n format,\r\n showState = true,\r\n autoFocus = false,\r\n autoTab = true,\r\n 'aria-describedby': ariaDescribedby,\r\n 'aria-label': ariaLabel = 'Please enter pin code',\r\n 'aria-labelledby': ariaLabelledby,\r\n autoComplete = 'off',\r\n disabled,\r\n inputMode,\r\n id,\r\n name,\r\n placeholder = 'o',\r\n required,\r\n inputClassName,\r\n inputStyle = {},\r\n onChange,\r\n onBlur,\r\n onFocus,\r\n onKeyDown,\r\n}) => {\r\n const inputRef = useRef<HTMLInputElement | null>(null);\r\n\r\n const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {\r\n const currentValue = values[index];\r\n const eventValue = e.target.value;\r\n const newValues = [...values];\r\n const rawValue: string[] = normalizeNewValue(\r\n currentValue,\r\n eventValue\r\n ).slice(0, newValues.length - index);\r\n const regex = type === 'number' ? /(^$)|(\\d+)/ : /.*/;\r\n const shouldFireChange: boolean = rawValue.every((val) => regex.test(val));\r\n\r\n if (!onChange) {\r\n return;\r\n }\r\n\r\n // apply formatter to transform\r\n const newValue = format ? rawValue.map((val) => format(val)) : rawValue;\r\n\r\n if (newValue.length) {\r\n newValue.forEach((val, idx) => (newValues[index + idx] = val));\r\n } else {\r\n newValues[index] = '';\r\n }\r\n\r\n if (!shouldFireChange) {\r\n return;\r\n }\r\n\r\n onChange(newValue, index, newValues);\r\n\r\n // auto-tab to the specified pin input\r\n let inputEl: Element | null = inputRef.current;\r\n for (let i = 0; i < newValue.length; i++) {\r\n if (inputEl) {\r\n inputEl = inputEl.nextElementSibling;\r\n }\r\n }\r\n\r\n if (newValue && autoTab && inputEl instanceof HTMLInputElement) {\r\n inputEl.focus();\r\n }\r\n };\r\n const handleKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (e) => {\r\n if (inputRef.current\r\n && e.key === INPUT_KEY_BACKSPACE\r\n && autoTab\r\n && values[index] === ''\r\n && index > 0\r\n ) {\r\n const prevInput = inputRef.current.previousElementSibling;\r\n\r\n if (prevInput instanceof HTMLInputElement) {\r\n prevInput.focus();\r\n }\r\n }\r\n if (onKeyDown) {\r\n onKeyDown(e);\r\n }\r\n };\r\n const handleInputFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {\r\n e.target.placeholder = '';\r\n if (onFocus) {\r\n onFocus(e);\r\n }\r\n };\r\n const handleInputBlur: React.FocusEventHandler<HTMLInputElement> = (e) => {\r\n e.target.placeholder = placeholder;\r\n if (onBlur) {\r\n onBlur(e);\r\n }\r\n };\r\n\r\n const pattern = useMemo(() => validateToPattern(validate), [validate]);\r\n\r\n // auto-focus on mount\r\n useEffect(() => {\r\n if (inputRef.current && autoFocus && index === 0) {\r\n inputRef.current.focus();\r\n }\r\n }, [autoFocus, index]);\r\n\r\n const {\r\n [size]: {\r\n className: sizeClassName,\r\n },\r\n } = sizeConfigMap;\r\n\r\n return (\r\n <input\r\n ref={inputRef}\r\n type={mask ? 'password' : 'text'}\r\n aria-describedby={ariaDescribedby}\r\n aria-disabled={disabled}\r\n aria-label={ariaLabel}\r\n aria-labelledby={ariaLabelledby}\r\n aria-required={required}\r\n autoComplete={autoComplete}\r\n disabled={disabled}\r\n name={name}\r\n id={id && `${id}-${index}`}\r\n className={cx(\r\n styles.inputWrapper,\r\n styles[sizeClassName],\r\n completed && styles.hasCompleted,\r\n showState && styles.showState,\r\n inputClassName,\r\n )}\r\n inputMode={inputMode || (type === 'number' ? 'numeric' : 'text')}\r\n required={required}\r\n placeholder={placeholder}\r\n pattern={pattern}\r\n value={value}\r\n onChange={handleInputChange}\r\n onKeyDown={handleKeyDown}\r\n onFocus={handleInputFocus}\r\n onBlur={handleInputBlur}\r\n style={inputStyle}\r\n data-index={index}\r\n />\r\n );\r\n};\r\n"],"names":[],"mappings":";;;AAOO;AACP;AACA;AACA;AACA;AACO;;ACXA;;;"}