Skip to content

Commit 8da66fd

Browse files
authored
feat: edit panel state change and panel styling (#9)
2 parents f05b2fa + d817a36 commit 8da66fd

20 files changed

+706
-56
lines changed

example/src/mocks/handlers.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
1-
import { http, HttpResponse } from "msw"
1+
import { HttpResponse } from "msw"
2+
import { http } from "msw-devtools"
23

34
export const handlers = [
4-
http.get("https://api.example.com/user", () => {
5-
return HttpResponse.json({
6-
firstName: "John",
7-
lastName: "Maverick",
5+
http
6+
.get("https://api.example.com/user", () => {
7+
return HttpResponse.json({
8+
firstName: "John",
9+
lastName: "Maverick",
10+
})
811
})
9-
}),
12+
.presets([
13+
{
14+
status: 200,
15+
description: "Success",
16+
response: {
17+
firstName: "John",
18+
lastName: "Maverick",
19+
},
20+
},
21+
]),
1022
http.post("https://api.example.com/user", () => {
1123
return HttpResponse.json({ success: true })
1224
}),
1325
http.put("https://api.example.com/user/:id", () => {
1426
return HttpResponse.json({ success: true })
1527
}),
28+
1629
http.delete("https://api.example.com/user/:id", () => {
1730
return HttpResponse.json({ success: true })
1831
}),

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
"./styles": "./dist/index.css"
3333
},
3434
"scripts": {
35-
"dev": "tsup src/index.ts --format cjs,esm --dts --watch ",
36-
"build": "tsup src/index.ts --format cjs,esm --dts --minify ",
35+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
36+
"build": "tsup src/index.ts --format cjs,esm --dts --minify",
3737
"example": "pnpm build && cd example && pnpm i && pnpm dev"
3838
},
3939
"dependencies": {
@@ -70,7 +70,7 @@
7070
"tailwind-merge": "2.3.0",
7171
"tailwindcss-animate": "1.0.7",
7272
"ts-pattern": "^5.1.2",
73-
"typescript": "5.1.6"
73+
"typescript": "5.4.5"
7474
},
7575
"devDependencies": {
7676
"autoprefixer": "^10.4.19",

src/features/DevtoolsHandlerList.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import { match } from "ts-pattern"
44

55
import { cn } from "@/shared/lib/cn"
6-
import { Checkbox } from "@/shared/ui/checkbox"
76
import { InlineCode, P } from "@/shared/ui/typography"
87
import { HttpMethods } from "msw"
98
import {
@@ -12,32 +11,42 @@ import {
1211
} from "@/providers/useMswDevtoolsContext"
1312
import { HandlerSelect } from "./HandlerSelect"
1413
import { Button } from "@/shared/ui/button"
14+
import { InputContainer } from "@/shared/ui/input-container"
15+
import { Label } from "@/shared/ui/label"
16+
import { Switch } from "@/shared/ui/switch"
1517

1618
export const DevtoolsHandlerList = () => {
1719
const { routes, onToggleHandler } = useRoute()
1820
const { onOpenEditPanel } = useEditorRouteState()
1921

2022
return (
21-
<ul className=" list-none overflow-y-auto h-[250px] scrollbar-hide bg-secondary rounded-[4px]">
23+
<ul className="list-none overflow-y-auto h-[250px] scrollbar-hide rounded-[4px] bg-transparent">
24+
<li className="p-[12px] flex items-center">
25+
<P className="font-semibold">Skip</P>
26+
<P className="font-semibold">Actions</P>
27+
<P className="font-semibold">Options</P>
28+
</li>
2229
{routes.map((route) => (
2330
<li key={route.id} className="p-[12px] flex items-center">
24-
<Checkbox
25-
id={route.id}
31+
<Switch
2632
checked={route.isSkip}
2733
onCheckedChange={(checked) => {
28-
onToggleHandler(route.id)
34+
onToggleHandler(route.id, checked)
2935
}}
3036
/>
31-
<label
37+
<Label
3238
htmlFor={route.id}
3339
className="pl-[12px] flex items-center w-full"
3440
>
3541
<MethodTag method={route.method} />
3642
<span className="font-semibold">{route.url}</span>
3743
<div className="ml-auto flex items-center">
44+
<InputContainer label="delay" />
3845
<HandlerSelect
39-
options={route.handlers}
40-
defaultValue={route.handlers[0].id}
46+
options={route.handlers ?? []}
47+
defaultValue={
48+
route.handlers?.[route.selectedHandlerIndex]?.id ?? undefined
49+
}
4150
/>
4251
<Button
4352
className="ml-[12px]"
@@ -49,7 +58,7 @@ export const DevtoolsHandlerList = () => {
4958
Edit
5059
</Button>
5160
</div>
52-
</label>
61+
</Label>
5362
</li>
5463
))}
5564
</ul>

src/features/HandlerSelect/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ export const HandlerSelect = ({ options, ...rest }: HandlerSelectProps) => {
1616
return (
1717
<Select {...rest}>
1818
<SelectTrigger className="w-[280px]">
19-
<SelectValue placeholder="Select a timezone" />
19+
<SelectValue placeholder="test" />
2020
</SelectTrigger>
2121
<SelectContent>
22-
{options.map(({ id, description }) => (
22+
{options?.map(({ id, description, status }) => (
2323
<SelectItem key={id} value={id}>
24-
{description}
24+
{status} - {description}
2525
</SelectItem>
2626
))}
2727
</SelectContent>
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { Button } from "@/shared/ui/button"
2+
import { Input } from "@/shared/ui/input"
3+
import { H4, P } from "@/shared/ui/typography"
4+
import {
5+
Select,
6+
SelectTrigger,
7+
SelectValue,
8+
SelectContent,
9+
SelectItem,
10+
} from "@/shared/ui/select"
11+
import { format } from "prettier"
12+
import { ResponseJsonEditor } from "./ResponseJsonEditor"
13+
import { StatusSelect } from "./StatusSelect"
14+
import babel from "prettier/plugins/babel"
15+
import prettierPluginEstree from "prettier/plugins/estree"
16+
import { Separator } from "@/shared/ui/separator"
17+
import { Controller, useForm } from "react-hook-form"
18+
import { HttpMethods } from "msw"
19+
import { ROUTE_METHODS } from "@/constants"
20+
21+
type CreateEditFormValues = {
22+
delay?: string
23+
description?: string
24+
method: HttpMethods
25+
url: string
26+
status: string
27+
response: string
28+
}
29+
30+
export const CreateEditForm = () => {
31+
const {
32+
register,
33+
control,
34+
handleSubmit,
35+
setError,
36+
formState: { errors },
37+
} = useForm<CreateEditFormValues>({
38+
defaultValues: {
39+
method: HttpMethods.GET,
40+
url: "",
41+
status: "200",
42+
response: "{}",
43+
},
44+
})
45+
46+
return (
47+
<form
48+
onSubmit={handleSubmit((value) => {
49+
console.log(value)
50+
})}
51+
>
52+
<div className="flex py-[12px] px-[16px]">
53+
<Controller
54+
control={control}
55+
name="method"
56+
render={({ field }) => (
57+
<Select defaultValue={field.value} onValueChange={field.onChange}>
58+
<SelectTrigger className="w-[180px]">
59+
<SelectValue placeholder="Theme" />
60+
</SelectTrigger>
61+
<SelectContent>
62+
{ROUTE_METHODS.map((method) => (
63+
<SelectItem key={method} value={method}>
64+
{method}
65+
</SelectItem>
66+
))}
67+
</SelectContent>
68+
</Select>
69+
)}
70+
/>
71+
<Input
72+
placeholder="Route URL https://example.com/..."
73+
{...register("url")}
74+
/>
75+
<Button type="submit">Save</Button>
76+
</div>
77+
<Separator />
78+
<div className="flex py-[12px] px-[16px]">
79+
<StatusSelect defaultValue={"200"} />
80+
<Input placeholder="Description" {...register("description")} />
81+
<Input placeholder="delay" {...register("delay")} />
82+
</div>
83+
<Separator />
84+
<div className="flex justify-between w-full text-left py-[12px] px-[16px]">
85+
<H4>Response Body</H4>
86+
{errors.response && (
87+
<P className="text-red-500">{errors.response.message}</P>
88+
)}
89+
</div>
90+
<div className="py-[12px] px-[16px] text-left">
91+
<Controller
92+
control={control}
93+
name="response"
94+
render={({ field }) => (
95+
<ResponseJsonEditor
96+
value={field.value}
97+
onChange={(value) => {
98+
console.log(value)
99+
field.onChange(value)
100+
}}
101+
onSave={async () => {
102+
try {
103+
const formattedResponse = await format(field.value, {
104+
tabWidth: 2,
105+
printWidth: 100,
106+
parser: "json5",
107+
plugins: [babel, prettierPluginEstree],
108+
})
109+
// LINK: https://github.com/prettier/prettier/issues/6360
110+
field.onChange(formattedResponse.replace(/[\r\n]+$/, ""))
111+
} catch (error) {
112+
const formattedError =
113+
error instanceof Error ? error.message : "Unknown Error"
114+
setError("response", {
115+
message: formattedError,
116+
})
117+
}
118+
}}
119+
/>
120+
)}
121+
/>
122+
</div>
123+
</form>
124+
)
125+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import CodeMirror, {
2+
KeyBinding,
3+
ReactCodeMirrorProps,
4+
hoverTooltip,
5+
keymap,
6+
} from "@uiw/react-codemirror"
7+
import { githubDark } from "@uiw/codemirror-theme-github"
8+
import { linter } from "@codemirror/lint"
9+
import { jsonAutoComplete } from "./utils/jsonAutoComplete"
10+
import { autocompletion, closeBracketsKeymap } from "@codemirror/autocomplete"
11+
import { defaultKeymap, historyKeymap } from "@codemirror/commands"
12+
import { json5, json5ParseLinter } from "codemirror-json5"
13+
14+
export type ResponseJsonEditorProps = ReactCodeMirrorProps & {
15+
onSave?: VoidFunction
16+
}
17+
18+
export const ResponseJsonEditor = ({
19+
onSave,
20+
...rest
21+
}: ResponseJsonEditorProps) => {
22+
const savedKeymap: KeyBinding = {
23+
key: "Ctrl-s",
24+
preventDefault: true,
25+
run: (editor) => {
26+
onSave?.()
27+
return editor.hasFocus
28+
},
29+
}
30+
31+
return (
32+
<CodeMirror
33+
style={{ height: "100%" }}
34+
extensions={[
35+
json5(),
36+
linter(json5ParseLinter(), {
37+
delay: 300,
38+
}),
39+
autocompletion({
40+
defaultKeymap: true,
41+
icons: true,
42+
aboveCursor: true,
43+
activateOnTyping: true,
44+
}),
45+
jsonAutoComplete(),
46+
keymap.of([
47+
...closeBracketsKeymap,
48+
...defaultKeymap,
49+
...historyKeymap,
50+
savedKeymap,
51+
]),
52+
]}
53+
theme={githubDark}
54+
basicSetup={{
55+
lineNumbers: true,
56+
highlightActiveLine: true,
57+
highlightActiveLineGutter: true,
58+
indentOnInput: true,
59+
history: true,
60+
bracketMatching: true,
61+
}}
62+
{...rest}
63+
/>
64+
)
65+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {
2+
Select,
3+
SelectContent,
4+
SelectItem,
5+
SelectTrigger,
6+
SelectValue,
7+
} from "@/shared/ui/select"
8+
import { STATUS_CODES } from "./constants"
9+
import { SelectProps } from "@radix-ui/react-select"
10+
11+
export const StatusSelect = ({ ...rest }: SelectProps) => {
12+
return (
13+
<Select {...rest}>
14+
<SelectTrigger className="w-[280px]">
15+
<SelectValue placeholder="Select HTTP Status" />
16+
</SelectTrigger>
17+
<SelectContent>
18+
{STATUS_CODES.map(({ label, value }) => {
19+
return (
20+
<SelectItem key={value} value={String(value)}>
21+
{label}
22+
</SelectItem>
23+
)
24+
})}
25+
</SelectContent>
26+
</Select>
27+
)
28+
}

0 commit comments

Comments
 (0)