@@ -37,69 +37,73 @@ export function KeyValueEditor({ pairs, onChange, readOnlyKeys }: KeyValueEditor
3737 }
3838
3939 return (
40- < div className = "border border-slate-200 dark:border-slate-700/50 rounded-lg overflow-hidden" >
41- < table className = "w-full text-sm" >
42- < thead >
43- < tr className = "bg-slate-50 dark:bg-slate-800/60 text-left" >
44- < th className = "w-8 px-2 py-1.5" > </ th >
45- < th className = "px-2 py-1.5 text-xs font-medium text-slate-500 dark:text-slate-400" > Key</ th >
46- < th className = "px-2 py-1.5 text-xs font-medium text-slate-500 dark:text-slate-400" > Value</ th >
47- < th className = "w-8 px-2 py-1.5" > </ th >
48- </ tr >
49- </ thead >
50- < tbody >
51- { rows . map ( ( pair ) => {
52- const isReadOnly = readOnlyKeys ?. has ( pair . key ) ;
53- return (
54- < tr
55- key = { pair . id }
56- className = { `border-t border-slate-100 dark:border-slate-700/50 ${ ! pair . enabled ? 'opacity-40' : '' } ` }
57- >
58- < td className = "px-2 py-1" >
59- < input
60- type = "checkbox"
61- checked = { pair . enabled }
62- onChange = { e => update ( pair . id , 'enabled' , e . target . checked ) }
63- className = "w-3.5 h-3.5 rounded border-slate-300 dark:border-slate-600 text-primary-600 focus:ring-primary-500"
64- />
65- </ td >
66- < td className = "px-2 py-1" >
67- < input
68- type = "text"
69- value = { pair . key }
70- onChange = { e => update ( pair . id , 'key' , e . target . value ) }
71- readOnly = { isReadOnly }
72- placeholder = "key"
73- className = { `w-full bg-transparent text-sm font-mono text-slate-700 dark:text-slate-300 placeholder:text-slate-300 dark:placeholder:text-slate-600 outline-none ${ isReadOnly ? 'cursor-default text-slate-400 dark:text-slate-500' : '' } ` }
74- />
75- </ td >
76- < td className = "px-2 py-1" >
77- < input
78- type = "text"
79- value = { pair . value }
80- onChange = { e => update ( pair . id , 'value' , e . target . value ) }
81- placeholder = "value"
82- className = "w-full bg-transparent text-sm font-mono text-slate-700 dark:text-slate-300 placeholder:text-slate-300 dark:placeholder:text-slate-600 outline-none"
83- />
84- </ td >
85- < td className = "px-2 py-1" >
86- { ( pair . key || pair . value ) && ! isReadOnly && (
87- < button
88- type = "button"
89- onClick = { ( ) => remove ( pair . id ) }
90- className = "text-slate-400 hover:text-rose-500 dark:hover:text-rose-400 transition-colors"
91- >
92- < svg className = "w-3.5 h-3.5" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" strokeWidth = { 2 } >
93- < path strokeLinecap = "round" strokeLinejoin = "round" d = "M6 18L18 6M6 6l12 12" />
94- </ svg >
95- </ button >
96- ) }
97- </ td >
98- </ tr >
99- ) ;
100- } ) }
101- </ tbody >
102- </ table >
40+ < div className = "space-y-2" >
41+ { rows . map ( ( pair , index ) => {
42+ const isReadOnly = readOnlyKeys ?. has ( pair . key ) ;
43+ const isEmpty = ! pair . key && ! pair . value ;
44+ const isLast = index === rows . length - 1 && isEmpty ;
45+
46+ return (
47+ < div
48+ key = { pair . id }
49+ className = { `flex items-center gap-2 ${ ! pair . enabled && ! isLast ? 'opacity-40' : '' } ` }
50+ >
51+ { /* Checkbox — hidden on the trailing empty row */ }
52+ < div className = "shrink-0 w-5 flex items-center justify-center" >
53+ { ! isLast && (
54+ < input
55+ type = "checkbox"
56+ checked = { pair . enabled }
57+ onChange = { e => update ( pair . id , 'enabled' , e . target . checked ) }
58+ className = "w-4 h-4 rounded border-slate-300 dark:border-slate-600 text-primary-600 focus:ring-primary-500 cursor-pointer"
59+ />
60+ ) }
61+ </ div >
62+
63+ { /* Key input */ }
64+ < input
65+ type = "text"
66+ value = { pair . key }
67+ onChange = { e => update ( pair . id , 'key' , e . target . value ) }
68+ readOnly = { isReadOnly }
69+ placeholder = { isLast ? 'param_name' : 'key' }
70+ className = { `flex-1 min-w-0 px-3 py-2 text-sm font-mono rounded-md outline-none transition-colors ${
71+ isLast
72+ ? 'border border-dashed border-slate-300 dark:border-slate-600 bg-transparent text-slate-400 dark:text-slate-500 placeholder:text-slate-300 dark:placeholder:text-slate-600'
73+ : 'border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/60 text-slate-700 dark:text-slate-300 placeholder:text-slate-300 dark:placeholder:text-slate-600 focus:border-primary-400 dark:focus:border-primary-500'
74+ } ${ isReadOnly ? 'cursor-default text-slate-400 dark:text-slate-500' : '' } `}
75+ />
76+
77+ { /* Value input */ }
78+ < input
79+ type = "text"
80+ value = { pair . value }
81+ onChange = { e => update ( pair . id , 'value' , e . target . value ) }
82+ placeholder = { isLast ? 'Value' : 'value' }
83+ className = { `flex-1 min-w-0 px-3 py-2 text-sm font-mono rounded-md outline-none transition-colors ${
84+ isLast
85+ ? 'border border-dashed border-slate-300 dark:border-slate-600 bg-transparent text-slate-400 dark:text-slate-500 placeholder:text-slate-300 dark:placeholder:text-slate-600'
86+ : 'border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/60 text-slate-700 dark:text-slate-300 placeholder:text-slate-300 dark:placeholder:text-slate-600 focus:border-primary-400 dark:focus:border-primary-500'
87+ } `}
88+ />
89+
90+ { /* Delete / chevron */ }
91+ < div className = "shrink-0 w-5 flex items-center justify-center" >
92+ { ! isEmpty && ! isReadOnly ? (
93+ < button
94+ type = "button"
95+ onClick = { ( ) => remove ( pair . id ) }
96+ className = "text-slate-300 dark:text-slate-600 hover:text-rose-500 dark:hover:text-rose-400 transition-colors"
97+ >
98+ < svg className = "w-4 h-4" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" strokeWidth = { 2 } >
99+ < path strokeLinecap = "round" strokeLinejoin = "round" d = "M6 18L18 6M6 6l12 12" />
100+ </ svg >
101+ </ button >
102+ ) : null }
103+ </ div >
104+ </ div >
105+ ) ;
106+ } ) }
103107 </ div >
104108 ) ;
105109}
0 commit comments