github.com/pelicanplatform/pelican@v1.0.5/web_ui/frontend/app/(login)/components/CodeInput.tsx (about)

     1  /***************************************************************
     2   *
     3   * Copyright (C) 2023, Pelican Project, Morgridge Institute for Research
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License"); you
     6   * may not use this file except in compliance with the License.  You may
     7   * obtain a copy of the License at
     8   *
     9   *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   ***************************************************************/
    18  
    19  import {ChangeEvent, ClipboardEvent, KeyboardEvent, useRef} from "react";
    20  import {Grid, TextField} from "@mui/material";
    21  import {AppProps} from "next/app";
    22  
    23  interface CodeInputProps {
    24      length: number;
    25      setCode: Function;
    26      submitFunction?: () => void;
    27  }
    28  
    29  export default function CodeInput({length, setCode, submitFunction}: CodeInputProps) {
    30  
    31      const inputRefs = useRef<HTMLInputElement[] | null[] | never[]>([])
    32  
    33      /**
    34       * Set the code in the input blocks
    35       * @param code: Array of digits to set
    36       * @param offset: Offset to start setting the code at, if code is exact length this is set to 0
    37       */
    38      function setInputs(code: number[], offset: number) {
    39  
    40          if(code.length == length){
    41              offset = 0
    42          }
    43  
    44          Array.from(Array(code.length).keys()).forEach(index => {
    45              if(index + offset < inputRefs.current.length) {
    46                  inputRefs.current[index + offset]!.value = code[index].toString()
    47              }
    48          })
    49  
    50          // Set the code
    51          setCode(getValue())
    52      }
    53  
    54      /**
    55       * Get the value of the input blocks
    56       */
    57      function getValue() {
    58          return Number(inputRefs.current.map(input => input!.value).join(""))
    59      }
    60  
    61      /**
    62       * Handle change in one of the input blocks
    63       * - Checks that the input is an integer
    64       * @param e
    65       * @param index
    66       */
    67      const onChange = (e: ChangeEvent, index: number) => {
    68  
    69          // Check that the input was a legal one
    70          const currentInput = inputRefs.current[index]
    71          if (!Number.isInteger(Number(currentInput!.value))) {
    72              currentInput!.value = ""
    73              return
    74          }
    75  
    76          // Set the code
    77          setCode(getValue())
    78  
    79          // If the current input is not the last advance focus
    80          if(index != inputRefs.current.length - 1) {
    81              const nextInput = inputRefs.current[index + 1]
    82              nextInput!.focus()
    83          }
    84      }
    85  
    86      /**
    87       * Handle pasting into one of the input blocks
    88       *
    89       * Maps a ClipboardEvent to setCode function
    90       *
    91       * @param e
    92       * @param index: Index of the input block event was captured in
    93       */
    94      const onPaste = (e: ClipboardEvent, index: number) => {
    95          let code = e.clipboardData.getData('Text').split("").map(x => Number(x))
    96  
    97          setInputs(code, index)
    98      }
    99  
   100      /**
   101       * Handle backspace in one of the input blocks
   102       * @param e
   103       * @param index: Index of the input block event was captured in
   104       */
   105      const onKeyDown = (e: KeyboardEvent, index: number) => {
   106          if(["Backspace"].includes(e.code)) {
   107              const currentInput = inputRefs.current[index]
   108  
   109              // If the current input is the first one, clear it
   110              if(index == 0) {
   111                  currentInput!.value = ""
   112  
   113              } else {
   114  
   115                  const previousInput = inputRefs.current[index - 1]
   116  
   117                  // If the current input is empty, focus the previous one
   118                  if(currentInput!.value == "") {
   119                      previousInput!.focus()
   120  
   121                  // Empty the current input
   122                  } else {
   123                      currentInput!.value = ""
   124                  }
   125              }
   126              e.preventDefault()
   127          }
   128      }
   129  
   130      return (
   131          <Grid container spacing={1}>
   132              {
   133                  Array.from(Array(length).keys()).map((index) => {
   134  
   135                      return (
   136                          <Grid item key={index} textAlign={"center"}>
   137                              <TextField
   138                                  inputProps={{
   139                                      sx: {
   140                                          width: "50px",
   141                                          borderWidth: "3px",
   142                                          fontSize: "3rem",
   143                                          textAlign: "center",
   144                                          padding: ".5rem",
   145                                          backgroundColor: "secondary.main",
   146                                      },
   147                                      maxLength: 1,
   148                                      ref: (el : HTMLInputElement) => inputRefs.current[index] = el
   149                                  }}
   150                                  variant="outlined"
   151                                  onKeyDown={(e) => onKeyDown(e, index)}
   152                                  onChange={(e) => onChange(e, index)}
   153                                  onPaste={(e) => onPaste(e, index)}
   154                              />
   155                          </Grid>
   156                      )
   157                  })
   158              }
   159          </Grid>
   160      )
   161  
   162  }