Skip to content

Commit e86bfa5

Browse files
committed
Refactor KeyValueEditor component for improved layout and styling; enhance user interaction with dynamic input placeholders and visibility
1 parent d0066c8 commit e86bfa5

2 files changed

Lines changed: 110 additions & 106 deletions

File tree

spa/src/components/api-tester/KeyValueEditor.tsx

Lines changed: 67 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

templates/doc.html.php

Lines changed: 43 additions & 43 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)