mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
add KeyValueInput
This commit is contained in:
137
src/components/KeyValueInput.tsx
Normal file
137
src/components/KeyValueInput.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
FormGroup,
|
||||
Stack,
|
||||
TextField,
|
||||
ButtonGroup,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import RemoveIcon from "@mui/icons-material/Remove";
|
||||
|
||||
export interface IKeyValueInputProps {
|
||||
value: Record<string, string>;
|
||||
onChange: (value: Record<string, string>) => void;
|
||||
label?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function KeyValueInput({
|
||||
value: valueProp,
|
||||
onChange,
|
||||
label,
|
||||
}: IKeyValueInputProps) {
|
||||
const [value, setValue] = useState(
|
||||
Object.keys(valueProp).length > 0
|
||||
? Object.keys(valueProp)
|
||||
.sort()
|
||||
.map((key) => [key, valueProp[key]])
|
||||
: [["", ""]]
|
||||
);
|
||||
|
||||
const saveValue = (v: typeof value) => {
|
||||
onChange(
|
||||
v.reduce((acc, [key, value]) => {
|
||||
if (key.length > 0) acc[key] = value;
|
||||
return acc;
|
||||
}, {} as Record<string, string>)
|
||||
);
|
||||
};
|
||||
|
||||
const handleAdd = (i: number) => () =>
|
||||
setValue((v) => {
|
||||
const newValue = [...v];
|
||||
newValue.splice(i + 1, 0, ["", ""]);
|
||||
setTimeout(() =>
|
||||
document.getElementById(`keyValue-${i + 1}-key`)?.focus()
|
||||
);
|
||||
return newValue;
|
||||
});
|
||||
const handleRemove = (i: number) => () =>
|
||||
setValue((v) => {
|
||||
const newValue = [...v];
|
||||
newValue.splice(i, 1);
|
||||
saveValue(newValue);
|
||||
return newValue;
|
||||
});
|
||||
|
||||
const handleChange =
|
||||
(i: number, j: number) => (e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setValue((v) => {
|
||||
const newValue = [...v];
|
||||
newValue[i][j] = e.target.value;
|
||||
saveValue(newValue);
|
||||
return newValue;
|
||||
});
|
||||
|
||||
return (
|
||||
<FormControl variant="filled">
|
||||
<FormLabel
|
||||
component="legend"
|
||||
sx={{ typography: "button", color: "text.primary", mb: 0.25, ml: 0.25 }}
|
||||
>
|
||||
{label}
|
||||
</FormLabel>
|
||||
<FormGroup>
|
||||
{value.map(([propKey, propValue], i) => (
|
||||
<Stack
|
||||
key={i}
|
||||
direction="row"
|
||||
alignItems="flex-start"
|
||||
sx={{ "& + &": { mt: 1 } }}
|
||||
>
|
||||
<TextField
|
||||
id={`keyValue-${i}-key`}
|
||||
aria-label="Key"
|
||||
placeholder="Key"
|
||||
value={propKey}
|
||||
sx={{
|
||||
"& .MuiInputBase-root": {
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
}}
|
||||
onChange={handleChange(i, 0)}
|
||||
error={propKey.length === 0}
|
||||
helperText={propKey.length === 0 ? "Required" : ""}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
id={`keyValue-${i}-value`}
|
||||
aria-label="Value"
|
||||
placeholder="Value"
|
||||
value={propValue}
|
||||
sx={{
|
||||
ml: "-1px",
|
||||
"& .MuiInputBase-root": {
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
},
|
||||
}}
|
||||
onChange={handleChange(i, 1)}
|
||||
/>
|
||||
|
||||
<ButtonGroup color="secondary" sx={{ ml: 1 }}>
|
||||
<Button
|
||||
onClick={handleAdd(i)}
|
||||
aria-label="Add row"
|
||||
style={{ minWidth: 32, paddingLeft: 0, paddingRight: 0 }}
|
||||
>
|
||||
<AddIcon style={{ fontSize: 18 }} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleRemove(i)}
|
||||
aria-label="Remove row"
|
||||
style={{ minWidth: 32, paddingLeft: 0, paddingRight: 0 }}
|
||||
>
|
||||
<RemoveIcon style={{ fontSize: 18 }} />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
))}
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import Navigation from "@src/components/Navigation";
|
||||
import CodeEditor from "@src/components/CodeEditor";
|
||||
import ReactJson from "react-json-view";
|
||||
import KeyValueInput from "@src/components/KeyValueInput";
|
||||
|
||||
// import { useConfirmation } from "@src/components/ConfirmationDialog";
|
||||
import { useProjectContext } from "@src/contexts/ProjectContext";
|
||||
@@ -44,6 +45,11 @@ export default function TestView() {
|
||||
const [method, setMethod] = useState<"GET" | "POST" | "DELETE">("GET");
|
||||
const [path, setPath] = useState<string>("/");
|
||||
const [codeValid, setCodeValid] = useState(true);
|
||||
const [headers, setHeaders] = useState<Record<string, string>>({
|
||||
x: "1",
|
||||
y: "2",
|
||||
z: "3",
|
||||
});
|
||||
|
||||
const cachedBodyKey = path.replace("/", "");
|
||||
|
||||
@@ -212,6 +218,16 @@ export default function TestView() {
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
{/* TODO: Remove */}
|
||||
<KeyValueInput
|
||||
value={headers}
|
||||
onChange={(v) => setHeaders(v)}
|
||||
label="Headers"
|
||||
/>
|
||||
<code style={{ display: "block" }}>
|
||||
{JSON.stringify(headers, undefined, 2)}
|
||||
</code>
|
||||
|
||||
<InputLabel sx={{ mb: 1, mt: 4 }}>Response</InputLabel>
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<ReactJson
|
||||
|
||||
Reference in New Issue
Block a user