@@ -7,12 +7,21 @@ import React, {
77 useRef ,
88 useState ,
99} from 'react'
10- import { KeyboardTypeOptions , StyleProp , TextInput , TextInputProps , TextStyle , ViewStyle } from 'react-native'
10+ import {
11+ KeyboardTypeOptions ,
12+ StyleProp ,
13+ TextInput ,
14+ TextInputProps ,
15+ TextProps ,
16+ TextStyle ,
17+ ViewStyle ,
18+ } from 'react-native'
1119import styled from 'styled-components/native'
1220import { useTheme } from '../../hooks'
1321import { metrics } from '../../helpers'
1422import { Cursor } from './Cursor'
1523import { Text } from '../Text/Text'
24+ import { ErrorText , HelperText } from './components'
1625
1726// Types
1827type CodeInputValue = string
@@ -102,9 +111,64 @@ interface CodeInputProps extends Omit<TextInputProps, 'value' | 'onChangeText' |
102111
103112 /** Test ID for the component */
104113 testID ?: string
105- }
106114
107- // Default constants moved to theme configuration
115+ /** Style for outer container */
116+ containerStyle ?: StyleProp < ViewStyle >
117+
118+ /** Label text displayed above the code input */
119+ label ?: string
120+
121+ /** Custom label component to replace default label text */
122+ labelComponent ?: ReactNode
123+
124+ /** Styling for the label */
125+ labelStyle ?: StyleProp < TextStyle >
126+
127+ /** Props to be passed to the label Text component */
128+ labelProps ?: TextProps
129+
130+ /** Show asterisk beside label for required fields */
131+ isRequire ?: boolean
132+
133+ /** Helper text displayed below the code input */
134+ helperText ?: string
135+
136+ /** Custom helper component to replace default helper text */
137+ helperComponent ?: ReactNode
138+
139+ /** Props to be passed to the helper text component */
140+ helperTextProps ?: TextProps
141+
142+ /** Error text displayed below the code input */
143+ errorText ?: string
144+
145+ /** Props to be passed to the error text component */
146+ errorProps ?: TextProps
147+
148+ /** Enable error state styling */
149+ error ?: boolean
150+
151+ /** Style for cell in error state */
152+ errorCellStyle ?: StyleProp < ViewStyle >
153+
154+ /** Enable success state styling */
155+ success ?: boolean
156+
157+ /** Style for cell in success state */
158+ successCellStyle ?: StyleProp < ViewStyle >
159+
160+ /** Style for cell in disabled state */
161+ disabledCellStyle ?: StyleProp < ViewStyle >
162+
163+ /** Style for cell in active state */
164+ activeCellStyle ?: StyleProp < ViewStyle >
165+
166+ /** React node to be rendered on the left side of the code input */
167+ leftComponent ?: ReactNode
168+
169+ /** React node to be rendered on the right side of the code input */
170+ rightComponent ?: ReactNode
171+ }
108172
109173// Main component
110174export const CodeInput = forwardRef < CodeInputRef , CodeInputProps > (
@@ -135,6 +199,25 @@ export const CodeInput = forwardRef<CodeInputRef, CodeInputProps>(
135199 autoFocus,
136200 disabled,
137201 testID = 'code-input' ,
202+ containerStyle,
203+ label,
204+ labelComponent,
205+ labelStyle,
206+ labelProps,
207+ isRequire,
208+ helperText,
209+ helperComponent,
210+ helperTextProps,
211+ errorText,
212+ errorProps,
213+ error,
214+ errorCellStyle,
215+ success,
216+ successCellStyle,
217+ disabledCellStyle,
218+ activeCellStyle,
219+ leftComponent,
220+ rightComponent,
138221 ...textInputProps
139222 } ,
140223 ref ,
@@ -145,7 +228,7 @@ export const CodeInput = forwardRef<CodeInputRef, CodeInputProps>(
145228 const [ isFocused , setIsFocused ] = useState ( false )
146229
147230 // Use theme defaults with props override
148- const actualLength = length ?? CodeInputTheme . length
231+ const actualLength = length ?? 6 // Default length is 6
149232
150233 // Use controlled or uncontrolled value
151234 const code = controlledValue !== undefined ? controlledValue : internalValue
@@ -295,7 +378,15 @@ export const CodeInput = forwardRef<CodeInputRef, CodeInputProps>(
295378 const isCellFocused = isFocused && cellIndex === focusedCellIndex
296379 const hasCellValue = Boolean ( cellValue )
297380
298- const cellStyles = [ cellStyle , hasCellValue && filledCellStyle , isCellFocused && focusCellStyle ]
381+ // Apply styles in priority order: disabled > error > success > focused > filled > default
382+ const cellStyles = [
383+ cellStyle ,
384+ hasCellValue && filledCellStyle ,
385+ isCellFocused && ( activeCellStyle ?? focusCellStyle ) ,
386+ success && ( successCellStyle ?? CodeInputTheme . successCellStyle ) ,
387+ error && ( errorCellStyle ?? CodeInputTheme . errorCellStyle ) ,
388+ disabled && ( disabledCellStyle ?? CodeInputTheme . disabledCellStyle ) ,
389+ ]
299390
300391 const wrapperStyles = [ cellWrapperStyle , isCellFocused && focusCellWrapperStyle ]
301392
@@ -311,7 +402,11 @@ export const CodeInput = forwardRef<CodeInputRef, CodeInputProps>(
311402 accessibilityLabel = { `Code input cell ${ cellIndex + 1 } of ${ length } ${
312403 cellValue ? `, contains ${ cellValue } ` : ', empty'
313404 } `}
314- accessibilityHint = { `Tap to ${ cellValue ? 'clear and ' : '' } enter code digit` } >
405+ accessibilityHint = { `Tap to ${ cellValue ? 'clear and ' : '' } enter code digit` }
406+ accessibilityState = { {
407+ disabled : ! ! disabled ,
408+ selected : isCellFocused ,
409+ } } >
315410 { renderCellContent ( cellIndex , cellValue ) }
316411 </ Cell >
317412 </ CellWrapperStyled >
@@ -324,13 +419,20 @@ export const CodeInput = forwardRef<CodeInputRef, CodeInputProps>(
324419 cellStyle ,
325420 filledCellStyle ,
326421 focusCellStyle ,
422+ activeCellStyle ,
327423 cellWrapperStyle ,
328424 focusCellWrapperStyle ,
329425 handleCellPress ,
330426 disabled ,
427+ error ,
428+ success ,
429+ errorCellStyle ,
430+ successCellStyle ,
431+ disabledCellStyle ,
331432 length ,
332433 testID ,
333434 renderCellContent ,
435+ CodeInputTheme ,
334436 ] ,
335437 )
336438
@@ -349,34 +451,74 @@ export const CodeInput = forwardRef<CodeInputRef, CodeInputProps>(
349451 }
350452
351453 return (
352- < Container testID = { testID } >
353- < HiddenTextInput
354- testID = { `${ testID } -hidden-input` }
355- ref = { textInputRef }
356- value = { code }
357- onChangeText = { handleValueChange }
358- onFocus = { handleFocus }
359- onBlur = { handleBlur }
360- maxLength = { actualLength }
361- keyboardType = { keyboardType ?? CodeInputTheme . keyboardType }
362- textContentType = "oneTimeCode"
363- autoComplete = "sms-otp"
364- autoFocus = { autoFocus ?? CodeInputTheme . autoFocus }
365- editable = { ! ( disabled ?? CodeInputTheme . disabled ) }
366- accessible = { true }
367- accessibilityLabel = { `Code input with ${ actualLength } digits` }
368- accessibilityHint = { `Enter ${ actualLength } digit code` }
369- accessibilityValue = { {
370- text : `${ code . length } of ${ actualLength } digits entered` ,
371- } }
372- { ...textInputProps }
373- />
374- < CellContainer
375- style = { cellContainerStyle }
376- accessible = { true }
377- accessibilityLabel = { `Code input cells, ${ code . length } of ${ actualLength } filled` } >
378- { cells }
379- </ CellContainer >
454+ < Container testID = { testID } style = { containerStyle } >
455+ { labelComponent ? (
456+ < LabelContainer testID = { `${ testID } -label-container` } >
457+ { labelComponent }
458+ { isRequire && < RequiredStar testID = { `${ testID } -required` } > *</ RequiredStar > }
459+ </ LabelContainer >
460+ ) : (
461+ ! ! label && (
462+ < LabelText
463+ testID = { `${ testID } -label` }
464+ style = { labelStyle ?? CodeInputTheme . labelStyle }
465+ { ...labelProps } >
466+ { label }
467+ { isRequire && < RequiredStar testID = { `${ testID } -required` } > *</ RequiredStar > }
468+ </ LabelText >
469+ )
470+ ) }
471+ < InputWrapper >
472+ < HiddenTextInput
473+ testID = { `${ testID } -hidden-input` }
474+ ref = { textInputRef }
475+ value = { code }
476+ onChangeText = { handleValueChange }
477+ onFocus = { handleFocus }
478+ onBlur = { handleBlur }
479+ maxLength = { actualLength }
480+ keyboardType = { keyboardType ?? 'number-pad' }
481+ textContentType = "oneTimeCode"
482+ autoComplete = "sms-otp"
483+ autoFocus = { autoFocus }
484+ editable = { ! disabled }
485+ accessible = { true }
486+ accessibilityLabel = { `Code input with ${ actualLength } digits${ error ? ', error' : '' } ${
487+ success ? ', success' : ''
488+ } ${ disabled ? ', disabled' : '' } `}
489+ accessibilityHint = { `Enter ${ actualLength } digit code` }
490+ accessibilityValue = { {
491+ text : `${ code . length } of ${ actualLength } digits entered` ,
492+ } }
493+ accessibilityState = { {
494+ disabled : ! ! disabled ,
495+ } }
496+ { ...textInputProps }
497+ />
498+ < ComponentRow >
499+ { ! ! leftComponent && leftComponent }
500+ < CellContainer
501+ style = { cellContainerStyle }
502+ accessible = { true }
503+ accessibilityLabel = { `Code input cells, ${ code . length } of ${ actualLength } filled${
504+ error ? ', error' : ''
505+ } ${ success ? ', success' : '' } ${ disabled ? ', disabled' : '' } `}
506+ accessibilityState = { {
507+ disabled : ! ! disabled ,
508+ } } >
509+ { cells }
510+ </ CellContainer >
511+ { ! ! rightComponent && rightComponent }
512+ </ ComponentRow >
513+ </ InputWrapper >
514+ { ! ! errorText && < ErrorText errorText = { errorText } errorProps = { errorProps } /> }
515+ { ! errorText && ( ! ! helperText || ! ! helperComponent ) && (
516+ < HelperText
517+ helperText = { helperText }
518+ helperComponent = { helperComponent }
519+ helperTextProps = { helperTextProps }
520+ />
521+ ) }
380522 </ Container >
381523 )
382524 } ,
@@ -385,10 +527,33 @@ export const CodeInput = forwardRef<CodeInputRef, CodeInputProps>(
385527CodeInput . displayName = 'CodeInput'
386528
387529// Styled components
388- const Container = styled . View ( {
530+ const Container = styled . View ( { } )
531+
532+ const InputWrapper = styled . View ( {
389533 position : 'relative' ,
390534} )
391535
536+ const LabelContainer = styled . View ( ( { theme} ) => ( {
537+ marginBottom : theme ?. spacing ?. tiny || 8 ,
538+ flexDirection : 'row' ,
539+ alignItems : 'center' ,
540+ } ) )
541+
542+ const LabelText = styled . Text ( ( { theme} ) => ( {
543+ fontSize : theme ?. fontSizes ?. sm || 14 ,
544+ color : theme ?. colors ?. darkText || '#333' ,
545+ marginBottom : theme ?. spacing ?. tiny || 8 ,
546+ } ) )
547+
548+ const RequiredStar = styled . Text ( ( { theme} ) => ( {
549+ color : theme ?. colors ?. errorText || '#ff0000' ,
550+ } ) )
551+
552+ const ComponentRow = styled . View ( {
553+ flexDirection : 'row' ,
554+ alignItems : 'center' ,
555+ } )
556+
392557const CellWrapperStyled = styled . View ( { } )
393558
394559const Cell = styled . Pressable < { disabled ?: boolean } > ( ( { theme, disabled} ) => ( {
0 commit comments