index.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import React, { CSSProperties, ReactElement } from 'react';
  2. import PropTypes from 'prop-types';
  3. import cls from 'classnames';
  4. import PinCodeFoundation, {
  5. PinCodeAdapter,
  6. PinCodeBaseProps,
  7. PinCodeBaseState,
  8. } from '@douyinfe/semi-foundation/pincode/foundation';
  9. import { cssClasses } from '@douyinfe/semi-foundation/pincode/constants';
  10. import BaseComponent from '../_base/baseComponent';
  11. import { getDefaultPropsFromGlobalConfig } from '../_utils';
  12. import Input, { InputProps } from '../input';
  13. import "@douyinfe/semi-foundation/pincode/pincode.scss";
  14. export interface PinCodeProps extends PinCodeBaseProps {
  15. className?: string;
  16. style?: CSSProperties;
  17. size?: InputProps['size']
  18. }
  19. export interface PinCodeState extends PinCodeBaseState {
  20. }
  21. class PinCode extends BaseComponent<PinCodeProps, PinCodeState> {
  22. static __SemiComponentName__ = "PinCode";
  23. static propTypes = {
  24. value: PropTypes.string,
  25. format: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.func]),
  26. onChange: PropTypes.func,
  27. defaultValue: PropTypes.string,
  28. count: PropTypes.number,
  29. className: PropTypes.string,
  30. style: PropTypes.object,
  31. autoFocus: PropTypes.bool,
  32. onComplete: PropTypes.func,
  33. };
  34. static defaultProps = getDefaultPropsFromGlobalConfig(PinCode.__SemiComponentName__, {
  35. count: 6,
  36. format: "number",
  37. autoFocus: true,
  38. })
  39. inputDOMList: HTMLInputElement[] = []
  40. foundation: PinCodeFoundation
  41. constructor(props: PinCodeProps) {
  42. super(props);
  43. this.foundation = new PinCodeFoundation(this.adapter);
  44. this.state = {
  45. valueList: (this.props.value || this.props.defaultValue) && (this.props.value || this.props.defaultValue).split("") || [],
  46. currentActiveIndex: 0
  47. };
  48. }
  49. componentDidUpdate(prevProps: Readonly<PinCodeProps>, prevState: Readonly<PinCodeState>, snapshot?: any) {
  50. if (prevProps.value !== this.props.value) {
  51. this.foundation.updateValueList(this.props.value.split(""));
  52. }
  53. }
  54. get adapter(): PinCodeAdapter<PinCodeProps, PinCodeState> {
  55. return {
  56. ...super.adapter,
  57. onCurrentActiveIndexChange: async (i) => {
  58. await this.setStateAsync({ currentActiveIndex: i });
  59. },
  60. notifyValueChange: (values: string[]) => {
  61. this.props.onChange?.(values.join(""));
  62. },
  63. changeSpecificInputFocusState: (index, state) => {
  64. if (state === "focus") {
  65. this.inputDOMList[index]?.focus?.();
  66. } else if (state === "blur") {
  67. this.inputDOMList[index]?.blur?.();
  68. }
  69. },
  70. updateValueList: async (valueList: PinCodeState['valueList']) => {
  71. await this.setStateAsync({ valueList });
  72. }
  73. };
  74. }
  75. focus = (index: number) => {
  76. const inputDOM = this.inputDOMList[index];
  77. inputDOM?.focus();
  78. inputDOM?.setSelectionRange(1, 1);
  79. }
  80. blur = (index: number) => {
  81. this.inputDOMList[index]?.blur();
  82. }
  83. renderSingleInput = (index: number) => {
  84. return <Input
  85. ref={dom => this.inputDOMList[index] = dom}
  86. key={`input-${index}`}
  87. autoFocus={this.props.autoFocus && index === 0}
  88. value={this.state.valueList[index]}
  89. size={this.props.size}
  90. disabled={this.props.disabled}
  91. onBlur={()=>this.foundation.handleCurrentActiveIndexChange(index, "blur")}
  92. onFocus={()=>this.foundation.handleCurrentActiveIndexChange(index, "focus")}
  93. onPaste={e=>this.foundation.handlePaste(e.nativeEvent, index)}
  94. onKeyDown={e=>{
  95. this.foundation.handleKeyDownOnSingleInput(e.nativeEvent, index);
  96. }}
  97. onChange={v => {
  98. const userInputChar = v[v.length - 1];
  99. if (this.foundation.validateValue(userInputChar)) {
  100. this.foundation.completeSingleInput(index, userInputChar);
  101. }
  102. }}/>;
  103. }
  104. render() {
  105. const inputElements: ReactElement[] = [];
  106. for (let i = 0; i < this.props.count; i++) {
  107. inputElements.push(this.renderSingleInput(i));
  108. }
  109. return <div className={cls(`${cssClasses.PREFIX}-wrapper`, this.props.className)} style={this.props.style}>
  110. {inputElements}
  111. </div>;
  112. }
  113. }
  114. export default PinCode;