1+ import { useState , useEffect , type CSSProperties } from "react" ;
2+ import { useFormFieldContext } from "../hooks/form-field-context" ;
3+ import { useCustomize } from "../hooks/customization-context" ;
4+
5+ export function ControlArray ( ) {
6+ const formFieldContextProps = useFormFieldContext ( ) ;
7+ const {
8+ id, onChange, prop, value,
9+ } = formFieldContextProps ;
10+ const {
11+ getProps, theme,
12+ } = useCustomize ( ) ;
13+
14+ // Initialize values from the current value
15+ const initializeValues = ( ) : string [ ] => {
16+ if ( ! value || ! Array . isArray ( value ) ) {
17+ return [ "" ] ;
18+ }
19+
20+ const stringValues = value . map ( v => typeof v === "string" ? v : JSON . stringify ( v ) ) ;
21+ return stringValues . length > 0 ? stringValues : [ "" ] ;
22+ } ;
23+
24+ const [ values , setValues ] = useState < string [ ] > ( initializeValues ) ;
25+
26+ // Update values when value changes externally
27+ useEffect ( ( ) => {
28+ setValues ( initializeValues ( ) ) ;
29+ } , [ value ] ) ;
30+
31+ const updateArray = ( newValues : string [ ] ) => {
32+ // Filter out empty values
33+ const validValues = newValues . filter ( v => v . trim ( ) !== "" ) ;
34+
35+ if ( validValues . length === 0 ) {
36+ onChange ( undefined ) ;
37+ return ;
38+ }
39+
40+ onChange ( validValues ) ;
41+ } ;
42+
43+ const handleValueChange = ( index : number , newValue : string ) => {
44+ const newValues = [ ...values ] ;
45+ newValues [ index ] = newValue ;
46+ setValues ( newValues ) ;
47+ updateArray ( newValues ) ;
48+ } ;
49+
50+ const addValue = ( ) => {
51+ const newValues = [ ...values , "" ] ;
52+ setValues ( newValues ) ;
53+ } ;
54+
55+ const removeValue = ( index : number ) => {
56+ const newValues = values . filter ( ( _ , i ) => i !== index ) ;
57+ setValues ( newValues . length > 0 ? newValues : [ "" ] ) ;
58+ updateArray ( newValues ) ;
59+ } ;
60+
61+ const containerStyles : CSSProperties = {
62+ gridArea : "control" ,
63+ display : "flex" ,
64+ flexDirection : "column" ,
65+ gap : "0.5rem" ,
66+ } ;
67+
68+ const itemStyles : CSSProperties = {
69+ display : "flex" ,
70+ gap : "0.5rem" ,
71+ alignItems : "center" ,
72+ } ;
73+
74+ const inputStyles : CSSProperties = {
75+ color : theme . colors . neutral60 ,
76+ border : "1px solid" ,
77+ borderColor : theme . colors . neutral20 ,
78+ padding : 6 ,
79+ borderRadius : theme . borderRadius ,
80+ boxShadow : theme . boxShadow . input ,
81+ flex : 1 ,
82+ } ;
83+
84+ const buttonStyles : CSSProperties = {
85+ color : theme . colors . neutral60 ,
86+ display : "inline-flex" ,
87+ alignItems : "center" ,
88+ padding : `${ theme . spacing . baseUnit } px ${ theme . spacing . baseUnit * 1.5 } px ${
89+ theme . spacing . baseUnit
90+ } px ${ theme . spacing . baseUnit * 2.5 } px`,
91+ border : `1px solid ${ theme . colors . neutral30 } ` ,
92+ borderRadius : theme . borderRadius ,
93+ cursor : "pointer" ,
94+ fontSize : "0.8125rem" ,
95+ fontWeight : 450 ,
96+ gap : theme . spacing . baseUnit * 2 ,
97+ textWrap : "nowrap" ,
98+ backgroundColor : "white" ,
99+ } ;
100+
101+ const removeButtonStyles : CSSProperties = {
102+ ...buttonStyles ,
103+ flex : "0 0 auto" ,
104+ padding : "6px 8px" ,
105+ } ;
106+
107+ return (
108+ < div { ...getProps ( "controlArray" , containerStyles , formFieldContextProps ) } >
109+ { values . map ( ( value , index ) => (
110+ < div key = { index } style = { itemStyles } >
111+ < input
112+ type = "text"
113+ value = { value }
114+ onChange = { ( e ) => handleValueChange ( index , e . target . value ) }
115+ placeholder = ""
116+ style = { inputStyles }
117+ required = { ! prop . optional && index === 0 }
118+ />
119+ { values . length > 1 && (
120+ < button
121+ type = "button"
122+ onClick = { ( ) => removeValue ( index ) }
123+ style = { removeButtonStyles }
124+ aria-label = "Remove value"
125+ >
126+ ×
127+ </ button >
128+ ) }
129+ </ div >
130+ ) ) }
131+ { ( values [ values . length - 1 ] ?. trim ( ) || values . length > 1 ) && (
132+ < button
133+ type = "button"
134+ onClick = { addValue }
135+ style = { {
136+ ...buttonStyles ,
137+ alignSelf : "flex-start" ,
138+ paddingRight : `${ theme . spacing . baseUnit * 2 } px` ,
139+ } }
140+ >
141+ < span > +</ span >
142+ < span > Add more</ span >
143+ </ button >
144+ ) }
145+ </ div >
146+ ) ;
147+ }
0 commit comments