• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sangupta / bedrock / 4360887097

08 Mar 2023 03:34AM UTC coverage: 80.687% (+14.5%) from 66.224%
4360887097

push

github

Sandeep Gupta
export all prop interfaces

121 of 129 branches covered (93.8%)

Branch coverage included in aggregate %.

5185 of 6447 relevant lines covered (80.43%)

1.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

40.72
/src/components/form/TokenInput.tsx
1
/**
1✔
2
 * bedrock - UI Component Library
1✔
3
 * https://github.com/sangupta/bedrock
1✔
4
 *
1✔
5
 * MIT License.
1✔
6
 * Copyright (c) 2022, Sandeep Gupta.
1✔
7
 *
1✔
8
 * Use of this source code is governed by a MIT style license
1✔
9
 * that can be found in LICENSE file in the code repository.
1✔
10
 */
1✔
11

1✔
12
import React from 'react';
1✔
13
import { BaseProps } from '../../types';
1✔
14
import { buildCss, getUniqueString, isDigit } from '../../Utils';
1✔
15
import HBox from '../layout/HBox';
1✔
16

1✔
17
export interface TokenInputProps extends BaseProps {
1✔
18
    /**
1✔
19
     * The number of digits the token will contain
1✔
20
     */
1✔
21
    numChars: number;
1✔
22

1✔
23
    /**
1✔
24
     * Do we need to mask input?
1✔
25
     */
1✔
26
    maskInput?: boolean;
1✔
27

1✔
28
    /**
1✔
29
     * The masking character to be used. Only useful when
1✔
30
     * `maskInput` is set to `true`.
1✔
31
     */
1✔
32
    maskChar?: string;
1✔
33

1✔
34
    allowCharacters?: boolean;
1✔
35

1✔
36
    onSubmit: (token: string) => void;
1✔
37

1✔
38
    onChange?: (token: string, valid: boolean) => void;
1✔
39

1✔
40
}
1✔
41

1✔
42
interface TokenInputState {
1✔
43
    value: Array<string>;
1✔
44
    currentIndex: number;
1✔
45
}
1✔
46

1✔
47
export default class TokenInput extends React.PureComponent<TokenInputProps, TokenInputState> {
1✔
48

1✔
49
    static defaultProps = {
1✔
50
        allowCharacters: false
8✔
51
    }
8✔
52

1✔
53
    boxIds: Array<string>;
1✔
54

×
55
    constructor(props: TokenInputProps) {
×
56
        super(props);
×
57

×
58
        this.boxIds = [];
×
59
        for (let index = 0; index < props.numChars; index++) {
×
60
            this.boxIds.push(getUniqueString('id-'));
×
61
        }
×
62

×
63
        this.state = {
×
64
            value: new Array(props.numChars),
×
65
            currentIndex: -1
×
66
        }
×
67
    }
×
68

×
69
    handleDigitUpdate = (index: number, digit: string | number): void => {
×
70
        const { value } = this.state;
×
71
        value[index] = ('' + digit).trim();
×
72
        this.setState({ value: value, currentIndex: index });
×
73

×
74
        // set next box in focus
×
75
        this.setFocus(index);
×
76

×
77
        // check if all indexes have a value
×
78
        for (let index = 0; index < this.props.numChars; index++) {
×
79
            if (value[index] === '') {
×
80
                // not every index has a value
×
81
                return;
×
82
            }
×
83
        }
×
84

×
85
        // everything has a value
×
86
        if (this.props.onChange) {
×
87
            const finalValue = value.join('');
×
88
            this.props.onChange(finalValue, finalValue.length === this.props.numChars);
×
89
        }
×
90
    }
×
91

×
92
    setFocus = (index: number): void => {
×
93
        if (index <= 0) {
×
94
            index = 0;
×
95
        }
×
96

×
97
        if ((index + 1) < this.props.numChars) {
×
98
            document.getElementById(this.boxIds[index + 1])?.focus();
×
99
        }
×
100
    }
×
101

1✔
102
    render(): React.ReactNode {
1✔
103
        const { numChars, allowCharacters, maskInput } = this.props;
×
104
        if (numChars <= 0) {
×
105
            return null;
×
106
        }
×
107

×
108
        const { value, currentIndex } = this.state;
×
109

×
110
        const boxes = [];
×
111
        for (let index = 0; index < numChars; index++) {
×
112
            const digit = (value[index] || '').trim();
×
113
            const hasValue = digit !== '';
×
114

×
115
            boxes.push(<DigitBox key={'box' + index}
×
116
                id={this.boxIds[index]}
×
117
                focus={index === (currentIndex + 1)}
×
118
                index={index} allowChars={allowCharacters}
×
119
                onUpdate={this.handleDigitUpdate}
×
120
                value={maskInput ? (hasValue ? '*' : '') : digit} />)
×
121
        }
×
122

×
123
        const css = buildCss('token-input-container', this.props.className);
×
124

×
125
        return <HBox className={css}>
×
126
            {boxes}
×
127
        </HBox>
×
128
    }
×
129
}
1✔
130

1✔
131
interface DigitBoxProps {
1✔
132
    id: string;
1✔
133
    index: number;
1✔
134
    onUpdate: (index: number, digit: string) => void;
1✔
135
    allowChars: boolean;
1✔
136
    value: string;
1✔
137
    focus?: boolean;
1✔
138
}
1✔
139

1✔
140
class DigitBox extends React.PureComponent<DigitBoxProps> {
1✔
141

1✔
142
    static defaultProps = {
1✔
143
        focus: false
8✔
144
    }
8✔
145

1✔
146
    handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
1✔
147
        const value = e.target.value;
×
148
        if (value === '') {
×
149
            // delete the value
×
150
            this.props.onUpdate(this.props.index, '');
×
151
            return;
×
152
        }
×
153

×
154
        // value exists, parse and update
×
155
        const digit: string = this.props.allowChars ? value : (isDigit(value) ? value : '');
×
156
        if (digit === '') {
×
157
            // invalid value, behave as if nothing happened
×
158
            e.preventDefault();
×
159
            e.stopPropagation();
×
160
            return;
×
161
        }
×
162

×
163
        this.props.onUpdate(this.props.index, digit);
×
164
    }
×
165

×
166
    handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
×
167
        if (e.code !== 'Backspace') {
×
168
            // do nothing
×
169
            return;
×
170
        }
×
171

×
172
        // see if we have a value, if we have something delete it
×
173
        if (this.props.value === '') {
×
174
            console.log('delete previous box');
×
175
            return;
×
176
        }
×
177
    }
×
178

1✔
179
    render(): React.ReactNode {
1✔
180
        return <input type='text'
×
181
            className='token-input-box'
×
182
            id={this.props.id}
×
183
            onChange={this.handleChange}
×
184
            onKeyDown={this.handleKeyDown}
×
185
            autoComplete='false'
×
186
            autoCapitalize='false'
×
187
            value={this.props.value}
×
188
            maxLength={1}
×
189
            spellCheck={false} />
×
190
    }
×
191

1✔
192
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc