zudoku 0.1.1-dev.29 → 0.1.1-dev.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/lib/components/Dialog.js +1 -1
  2. package/dist/lib/components/Dialog.js.map +1 -1
  3. package/dist/lib/components/SyntaxHighlight.d.ts +1 -1
  4. package/dist/lib/components/SyntaxHighlight.js +3 -0
  5. package/dist/lib/components/SyntaxHighlight.js.map +1 -1
  6. package/dist/lib/plugins/openapi/ColorizedParam.d.ts +1 -2
  7. package/dist/lib/plugins/openapi/ColorizedParam.js +1 -1
  8. package/dist/lib/plugins/openapi/ColorizedParam.js.map +1 -1
  9. package/dist/lib/plugins/openapi/MakeRequest.js +1 -1
  10. package/dist/lib/plugins/openapi/MakeRequest.js.map +1 -1
  11. package/dist/lib/plugins/openapi/ParameterListItem.js +1 -1
  12. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  13. package/dist/lib/plugins/openapi/playground/Editor.d.ts +1 -0
  14. package/dist/lib/plugins/openapi/playground/Editor.js +5 -0
  15. package/dist/lib/plugins/openapi/playground/Editor.js.map +1 -0
  16. package/dist/lib/plugins/openapi/playground/Headers.d.ts +7 -0
  17. package/dist/lib/plugins/openapi/playground/Headers.js +26 -0
  18. package/dist/lib/plugins/openapi/playground/Headers.js.map +1 -0
  19. package/dist/lib/plugins/openapi/playground/InlineInput.d.ts +3 -0
  20. package/dist/lib/plugins/openapi/playground/InlineInput.js +3 -0
  21. package/dist/lib/plugins/openapi/playground/InlineInput.js.map +1 -0
  22. package/dist/lib/plugins/openapi/playground/Playground.d.ts +18 -1
  23. package/dist/lib/plugins/openapi/playground/Playground.js +60 -71
  24. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  25. package/dist/lib/plugins/openapi/playground/QueryParams.d.ts +7 -0
  26. package/dist/lib/plugins/openapi/playground/QueryParams.js +30 -0
  27. package/dist/lib/plugins/openapi/playground/QueryParams.js.map +1 -0
  28. package/dist/lib/plugins/openapi/playground/UrlParts.d.ts +6 -0
  29. package/dist/lib/plugins/openapi/playground/UrlParts.js +21 -0
  30. package/dist/lib/plugins/openapi/playground/UrlParts.js.map +1 -0
  31. package/dist/lib/util/createVariantComponent.d.ts +3 -10
  32. package/dist/lib/util/createVariantComponent.js +3 -2
  33. package/dist/lib/util/createVariantComponent.js.map +1 -1
  34. package/lib/{DevPortal-ChiyAyW7.js → DevPortal-COMmOqxP.js} +4082 -4379
  35. package/lib/assets/{worker-CnXQsqxH.js → worker-W78u54MC.js} +2840 -2878
  36. package/lib/zudoku.components.js +1 -1
  37. package/lib/zudoku.openapi-worker.js +5 -5
  38. package/lib/zudoku.plugins.js +8197 -6903
  39. package/package.json +10 -1
  40. package/src/lib/components/Dialog.tsx +32 -32
  41. package/src/lib/components/SyntaxHighlight.tsx +4 -0
  42. package/src/lib/plugins/openapi/ColorizedParam.tsx +0 -2
  43. package/src/lib/plugins/openapi/MakeRequest.tsx +1 -1
  44. package/src/lib/plugins/openapi/ParameterListItem.tsx +4 -1
  45. package/src/lib/plugins/openapi/playground/Editor.tsx +6 -0
  46. package/src/lib/plugins/openapi/playground/Headers.tsx +66 -0
  47. package/src/lib/plugins/openapi/playground/InlineInput.tsx +6 -0
  48. package/src/lib/plugins/openapi/playground/Playground.tsx +191 -226
  49. package/src/lib/plugins/openapi/playground/QueryParams.tsx +79 -0
  50. package/src/lib/plugins/openapi/playground/UrlParts.tsx +67 -0
  51. package/src/lib/util/createVariantComponent.tsx +7 -5
@@ -9,45 +9,51 @@ import {
9
9
  import { Card, CardContent } from "../../../ui/Card.js";
10
10
  import { Button } from "../../../ui/Button.js";
11
11
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../../ui/Tabs.js";
12
- import { Header } from "har-format";
13
- import {
14
- Fragment,
15
- InputHTMLAttributes,
16
- useEffect,
17
- useRef,
18
- useState,
19
- } from "react";
20
- import { TrashIcon } from "lucide-react";
21
- import createVariantComponent from "../../../util/createVariantComponent.js";
12
+ import { Fragment } from "react";
22
13
  import { SyntaxHighlight } from "../../../components/SyntaxHighlight.js";
23
- import {
24
- ColorizedParam,
25
- DATA_ATTR,
26
- usePastellizedColor,
27
- } from "../ColorizedParam.js";
28
- import { cn } from "../../../util/cn.js";
14
+ import { ColorizedParam } from "../ColorizedParam.js";
29
15
  import { useMutation } from "@tanstack/react-query";
16
+ import { useForm } from "react-hook-form";
17
+ import { Headers } from "./Headers.js";
18
+ import { QueryParams } from "./QueryParams.js";
19
+ import { UrlParts } from "./UrlParts.js";
20
+ import { LoaderCircle } from "lucide-react";
30
21
 
31
- const InlineInput = createVariantComponent(
32
- "input",
33
- "px-2 bg-transparent h-8 font-mono text-xs m-2",
34
- );
22
+ function mimeTypeToLanguage(mimeType: string) {
23
+ const mimeTypeMapping = {
24
+ "application/json": "json",
25
+ "text/json": "json",
26
+ "text/html": "html",
27
+ "text/css": "css",
28
+ "text/javascript": "javascript",
29
+ "application/xml": "xml",
30
+ "application/xhtml+xml": "xhtml",
31
+ "text/plain": "plain",
32
+ } as const;
35
33
 
36
- const ParameterValue = ({
37
- part,
38
- className,
39
- ...props
40
- }: {
41
- part: string;
42
- } & InputHTMLAttributes<HTMLInputElement>) => {
43
- const color = usePastellizedColor(part);
44
- return (
45
- <InlineInput
46
- {...props}
47
- className={cn(className, "opacity-80 data-[active=true]:opacity-100")}
48
- style={{ color: `hsl(${color})` }}
49
- />
50
- );
34
+ return Object.entries(mimeTypeMapping).find(([mime]) =>
35
+ mimeType.includes(mime),
36
+ )?.[0][1];
37
+ }
38
+
39
+ export type Header = {
40
+ name: string;
41
+ value: string;
42
+ };
43
+ export type QueryParam = {
44
+ name: string;
45
+ value: string;
46
+ };
47
+ export type UrlPart = {
48
+ name: string;
49
+ value: string;
50
+ };
51
+
52
+ export type PlaygroundForm = {
53
+ body: string;
54
+ queryParams: QueryParam[];
55
+ urlParts: UrlPart[];
56
+ headers: Header[];
51
57
  };
52
58
 
53
59
  const Playground = ({
@@ -61,41 +67,38 @@ const Playground = ({
61
67
  method: string;
62
68
  defaultHeaders?: Header[];
63
69
  }) => {
64
- const [headers, setHeaders] = useState<Header[]>(
65
- defaultHeaders ?? [{ name: "", value: "" }],
66
- );
67
- const [urlParts, setUrlParts] = useState<Header[]>(
68
- url
69
- .split("/")
70
- .filter((part) => part.startsWith("{") && part.endsWith("}"))
71
- .map((part) => ({ name: part, value: "" })),
72
- );
70
+ const { register, control, handleSubmit, watch } = useForm<PlaygroundForm>({
71
+ defaultValues: {
72
+ body: "",
73
+ queryParams: [{ name: "", value: "" }],
74
+ headers: defaultHeaders ?? [{ name: "", value: "" }],
75
+ urlParts: url
76
+ .split("/")
77
+ .filter((part) => part.startsWith("{") && part.endsWith("}"))
78
+ .map((part) => ({ name: part.slice(1, -1), value: "" })),
79
+ },
80
+ });
73
81
 
74
- useEffect(() => {
75
- const lastHeader = headers.at(-1);
76
- if (lastHeader?.value !== "" || lastHeader?.name !== "") {
77
- setHeaders([...headers, { name: "", value: "" }]);
78
- }
79
- }, [headers]);
82
+ const formState = watch();
80
83
 
81
84
  const x = useMutation({
82
- mutationFn: async () => {
85
+ mutationFn: async (data: PlaygroundForm) => {
83
86
  const fullUrl =
84
87
  host +
85
88
  url
86
89
  .split("/")
87
- .map((v) => urlParts.find((part) => part.name === v)?.value ?? v)
90
+ .map((v) =>
91
+ v.startsWith("{") && v.endsWith("}")
92
+ ? data.urlParts.find((part) => part.name === v.slice(1, -1))
93
+ ?.value ?? v
94
+ : v,
95
+ )
88
96
  .join("/");
89
- console.log(
90
- fullUrl,
91
- Object.fromEntries(
92
- headers.map((header) => [header.name, header.value]),
93
- ),
94
- );
97
+
95
98
  const response = await fetch(fullUrl, {
96
- // method,
99
+ method: method.toUpperCase(),
97
100
  headers: Object.fromEntries(
98
- headers
101
+ data.urlParts
99
102
  .filter((h) => h.name)
100
103
  .map((header) => [header.name, header.value]),
101
104
  ),
@@ -103,6 +106,7 @@ const Playground = ({
103
106
 
104
107
  return {
105
108
  status: response.status,
109
+ headers: response.headers,
106
110
  body: await response.text(),
107
111
  };
108
112
  },
@@ -119,188 +123,149 @@ const Playground = ({
119
123
  }}
120
124
  slug={part.slice(1, -1)}
121
125
  >
122
- {urlParts.find((p) => p.name === part)?.value}
126
+ {
127
+ formState.urlParts.find((p) => {
128
+ console.log(
129
+ p.name,
130
+ part.slice(1, -1),
131
+ p.name === part.slice(1, -1),
132
+ p.value,
133
+ );
134
+ return p.name === part.slice(1, -1);
135
+ })?.value
136
+ }
123
137
  </ColorizedParam>
124
138
  ) : (
125
139
  part
126
140
  )}
127
- /
141
+ {"/"}
128
142
  <wbr />
129
143
  </Fragment>
130
144
  ));
131
145
 
146
+ const lang = mimeTypeToLanguage(x.data?.headers.get("Content-Type") ?? "");
147
+
132
148
  return (
133
149
  <Dialog>
134
150
  <DialogTrigger>Open</DialogTrigger>
135
- <DialogContent className="max-w-screen-xl">
136
- <DialogHeader>
137
- <DialogTitle>API Playground</DialogTitle>
138
- <DialogDescription>
139
- <div className="grid grid-cols-2 gap-2">
140
- <Card>
141
- <CardContent className="border-b border-border pt-4">
142
- <div>URL</div>
143
- <div className="flex gap-2">
144
- <div className="border rounded border-border p-1.5 w-full">
145
- {path}
151
+
152
+ <DialogContent className="max-w-screen-xl w-full h-5/6">
153
+ <form
154
+ onSubmit={handleSubmit((data) => {
155
+ x.mutateAsync(data);
156
+ })}
157
+ >
158
+ <DialogHeader>
159
+ <DialogTitle className="mb-4">API Playground</DialogTitle>
160
+ <DialogDescription>
161
+ <div className="grid grid-cols-2 gap-2">
162
+ <Card>
163
+ <CardContent className="border-b border-border pt-4">
164
+ <div className="font-bold mb-1">URL</div>
165
+ <div className="flex gap-2 items-center">
166
+ <div className="border rounded border-border flex justify-stretch w-full">
167
+ <div className="border-r border-border p-2 bg-muted font-mono">
168
+ {method.toUpperCase()}
169
+ </div>
170
+ <div className="p-2 whitespace-nowrap overflow-scroll flex-1">
171
+ {path}
172
+ </div>
173
+ </div>
174
+ <Button type="submit">Send</Button>
146
175
  </div>
147
- <Button onClick={() => x.mutateAsync()}>Send</Button>
148
- </div>
149
- </CardContent>
150
- <Tabs defaultValue="headers">
151
- <CardContent className="border-b border-border py-4">
152
- <TabsList>
153
- <TabsTrigger value="headers">
154
- Headers ({headers.length})
155
- </TabsTrigger>
156
- <TabsTrigger value="parameters">Parameters</TabsTrigger>
157
- <TabsTrigger value="auth">Auth</TabsTrigger>
158
- </TabsList>
159
176
  </CardContent>
177
+ <Tabs defaultValue="parameters">
178
+ <CardContent className="border-b border-border py-4">
179
+ <TabsList>
180
+ <TabsTrigger value="parameters">Parameters</TabsTrigger>
181
+ <TabsTrigger value="headers">
182
+ Headers ({formState.headers.length - 1})
183
+ </TabsTrigger>
184
+ <TabsTrigger
185
+ value="body"
186
+ disabled={["POST", "PUT", "PATCH", "DELETE"].includes(
187
+ method.toUpperCase(),
188
+ )}
189
+ >
190
+ Body
191
+ </TabsTrigger>
192
+ </TabsList>
193
+ </CardContent>
194
+ <CardContent className="overflow-auto h-full">
195
+ <TabsContent value="headers">
196
+ <Headers
197
+ control={control}
198
+ register={register}
199
+ headers={formState.headers}
200
+ />
201
+ </TabsContent>
202
+ <TabsContent value="parameters">
203
+ <UrlParts control={control} register={register} />
204
+ Query Parameters
205
+ <QueryParams
206
+ control={control}
207
+ queryParams={formState.queryParams}
208
+ register={register}
209
+ />
210
+ </TabsContent>
211
+ <TabsContent value="auth">
212
+ <CardContent>
213
+ {url
214
+ .split("/")
215
+ .map((part, i) =>
216
+ part.startsWith("{") && part.endsWith("}")
217
+ ? formState.urlParts[i]
218
+ : part,
219
+ )
220
+ .join("/")}
221
+ </CardContent>
222
+ Change your password here.
223
+ </TabsContent>
224
+ <TabsContent value="body">
225
+ <textarea
226
+ {...register("body")}
227
+ className="border border-border w-full rounded p-2 bg-muted h-40"
228
+ />
229
+ </TabsContent>
230
+ </CardContent>
231
+ </Tabs>
232
+ </Card>
233
+ <Card>
160
234
  <CardContent>
161
- <TabsContent value="headers">
162
- <div className="grid grid-cols-[1fr_1fr_auto]">
163
- {headers.map((header, i) => (
164
- <div
165
- key={i}
166
- className="grid-cols-subgrid col-span-full grid items-center gap-x-2 has-[:focus]:bg-muted hover:bg-muted rounded overflow-hidden group"
167
- >
168
- {/*<input type="checkbox" />*/}
169
- <InlineInput
170
- onChange={(e) =>
171
- setHeaders((headers) => {
172
- const newHeaders = [...headers];
173
- newHeaders[i] = {
174
- ...headers[i],
175
- name: e.target.value,
176
- };
177
- return newHeaders;
178
- })
179
- }
180
- value={header.name}
181
- placeholder={"Name"}
182
- className="peer"
183
- />
184
- <InlineInput
185
- onChange={(e) =>
186
- setHeaders((headers) => {
187
- const newHeaders = [...headers];
188
- newHeaders[i] = {
189
- name: "",
190
- ...headers.at(i),
191
- value: e.target.value,
192
- };
193
- return newHeaders;
194
- })
195
- }
196
- value={header.value}
197
- placeholder={"Value"}
198
- className="peer"
199
- />
200
- <button
201
- className="hover:bg-black/5 p-1 rounded mr-2 text-muted-foreground invisible group-hover:visible peer-focus:visible"
202
- onClick={() => {
203
- setHeaders((headers) =>
204
- [...headers].toSpliced(i, 1),
205
- );
206
- }}
207
- >
208
- <TrashIcon size={16} />
209
- </button>
210
- <div className="col-span-full border-b border-border"></div>
211
- </div>
212
- ))}
213
- </div>
214
- </TabsContent>
215
- <TabsContent value="parameters">
216
- Parameters
217
- <div className="grid grid-cols-[1fr_1fr_auto]">
218
- {urlParts.map(({ name, value }, i) => (
219
- <div
220
- key={i}
221
- className="grid-cols-subgrid col-span-full grid items-center gap-x-2 has-[:focus]:bg-muted hover:bg-muted rounded overflow-hidden group"
222
- >
223
- {/*<input type="checkbox" />*/}
224
- <InlineInput
225
- onChange={(e) =>
226
- setHeaders((headers) => {
227
- const newHeaders = [...headers];
228
- newHeaders[i] = {
229
- ...headers[i],
230
- name: e.target.value,
231
- };
232
- return newHeaders;
233
- })
234
- }
235
- value={name.slice(1, -1)}
236
- disabled
237
- placeholder={"Name"}
238
- className="peer"
239
- />
240
- <ParameterValue
241
- part={name.slice(1, -1)}
242
- onChange={(e) =>
243
- setUrlParts((parts) => {
244
- const newParts = [...parts];
245
- newParts[i] = {
246
- name: "",
247
- ...parts.at(i),
248
- value: e.target.value,
249
- };
250
- return newParts;
251
- })
252
- }
253
- {...{ [DATA_ATTR]: name.slice(1, -1) }}
254
- value={value}
255
- placeholder={"Value"}
256
- className="peer"
257
- />
235
+ <div className="mt-2 font-mono gap-2 flex">
236
+ {method.toUpperCase()} {x.data?.status}{" "}
237
+ {url
238
+ .split("/")
239
+ .map((v) =>
240
+ v.startsWith("{") && v.endsWith("}")
241
+ ? formState.urlParts.find((part) => part.name === v)
242
+ ?.value ?? v
243
+ : v,
244
+ )
245
+ .join("/")}
246
+ </div>
258
247
 
259
- <div className="col-span-full border-b border-border"></div>
260
- </div>
261
- ))}
262
- </div>
263
- </TabsContent>
264
- <TabsContent value="auth">
265
- <CardContent>
266
- {url
267
- .split("/")
268
- .map((part, i) =>
269
- part.startsWith("{") && part.endsWith("}")
270
- ? urlParts[i]
271
- : part,
272
- )
273
- .join("/")}
274
- </CardContent>
275
- Change your password here.
276
- </TabsContent>
248
+ <div className="w-full overflow-auto max-h-80 rounded-xl border border-border p-4 dark:!bg-foreground/10 dark:border-transparent">
249
+ {x.isPending && (
250
+ <>
251
+ <LoaderCircle size={18} className="animate-spins" /> a
252
+ request to see the response."
253
+ </>
254
+ )}
255
+ <SyntaxHighlight
256
+ language={lang ?? "json"}
257
+ noBackground
258
+ copyable={false}
259
+ className="overflow-x-auto "
260
+ code={x.data?.body ?? JSON.stringify("")}
261
+ />
262
+ </div>
277
263
  </CardContent>
278
- </Tabs>
279
- </Card>
280
- <Card>
281
- <CardContent>
282
- {method.toUpperCase()}
283
- {url
284
- .split("/")
285
- .map((v) =>
286
- v.startsWith("{") && v.endsWith("}")
287
- ? urlParts.find((part) => part.name === v)?.value ?? v
288
- : v,
289
- )
290
- .join("/")}
291
- <SyntaxHighlight
292
- language="json"
293
- noBackground
294
- copyable={false}
295
- className="text-xs"
296
- code={x.data?.body ?? JSON.stringify("")}
297
- />
298
- </CardContent>
299
- {x.data?.status}
300
- </Card>
301
- </div>
302
- </DialogDescription>
303
- </DialogHeader>
264
+ </Card>
265
+ </div>
266
+ </DialogDescription>
267
+ </DialogHeader>
268
+ </form>
304
269
  </DialogContent>
305
270
  </Dialog>
306
271
  );
@@ -0,0 +1,79 @@
1
+ import {
2
+ Control,
3
+ Controller,
4
+ useFieldArray,
5
+ UseFormRegister,
6
+ } from "react-hook-form";
7
+ import { TrashIcon } from "lucide-react";
8
+ import { InlineInput } from "./InlineInput.js";
9
+ import { PlaygroundForm, QueryParam } from "./Playground.js";
10
+ import { useEffect } from "react";
11
+
12
+ export const QueryParams = ({
13
+ control,
14
+ register,
15
+ queryParams,
16
+ }: {
17
+ register: UseFormRegister<PlaygroundForm>;
18
+ control: Control<PlaygroundForm>;
19
+ queryParams: QueryParam[];
20
+ }) => {
21
+ const { fields, append, remove } = useFieldArray<PlaygroundForm>({
22
+ control,
23
+ name: "queryParams",
24
+ });
25
+
26
+ const queryCount = queryParams?.length;
27
+ const queryLastName = queryParams.at(-1)?.name;
28
+ const queryLastValue = queryParams.at(-1)?.value;
29
+
30
+ useEffect(() => {
31
+ // if (queryCount === 0) {
32
+ // append({ name: "", value: "" });
33
+ // }
34
+ console.log(queryLastName, queryLastValue, queryCount);
35
+ if (queryLastName !== "" || queryLastValue !== "") {
36
+ console.log("appending");
37
+ append({ name: "", value: "" });
38
+ }
39
+ }, [append, queryLastValue, queryLastName, queryCount]);
40
+
41
+ return (
42
+ <div className="grid grid-cols-[1fr_1fr_auto]">
43
+ {fields.map((field, i) => (
44
+ <div
45
+ key={field.id}
46
+ className="grid-cols-subgrid col-span-full grid items-center gap-x-2 has-[:focus]:bg-muted hover:bg-muted rounded overflow-hidden group"
47
+ >
48
+ {field.id}
49
+ <Controller
50
+ control={control}
51
+ render={({ field }) => {
52
+ return (
53
+ <InlineInput {...field} placeholder="Name" className="peer" />
54
+ );
55
+ }}
56
+ name={`queryParams.${i}.name`}
57
+ />
58
+ <Controller
59
+ control={control}
60
+ render={({ field }) => {
61
+ return (
62
+ <InlineInput {...field} placeholder="Value" className="peer" />
63
+ );
64
+ }}
65
+ name={`queryParams.${i}.value`}
66
+ />
67
+
68
+ <button
69
+ className="hover:bg-black/5 p-1 rounded mr-2 text-muted-foreground invisible group-hover:visible peer-focus:visible"
70
+ onClick={() => remove(i)}
71
+ >
72
+ <TrashIcon size={16} />
73
+ </button>
74
+ <div className="col-span-full border-b border-border"></div>
75
+ </div>
76
+ ))}
77
+ </div>
78
+ );
79
+ };
@@ -0,0 +1,67 @@
1
+ import { Control, useFieldArray, UseFormRegister } from "react-hook-form";
2
+ import { InlineInput } from "./InlineInput.js";
3
+ import { PlaygroundForm } from "./Playground.js";
4
+ import { DATA_ATTR, usePastellizedColor } from "../ColorizedParam.js";
5
+ import { forwardRef, InputHTMLAttributes } from "react";
6
+ import { cn } from "../../../util/cn.js";
7
+
8
+ type ParameterValueProps = {
9
+ part: string;
10
+ } & InputHTMLAttributes<HTMLInputElement>;
11
+
12
+ const ParameterValue = forwardRef<HTMLInputElement, ParameterValueProps>(
13
+ ({ part, className, ...props }, ref) => {
14
+ const color = usePastellizedColor(part);
15
+ return (
16
+ <InlineInput
17
+ {...props}
18
+ ref={ref}
19
+ className={cn(className, "opacity-80 data-[active=true]:opacity-100")}
20
+ style={{
21
+ // color: `hsl(${color})`,
22
+ outlineColor: `hsl(${color})`,
23
+ }}
24
+ />
25
+ );
26
+ },
27
+ );
28
+
29
+ export const UrlParts = ({
30
+ control,
31
+ register,
32
+ }: {
33
+ register: UseFormRegister<PlaygroundForm>;
34
+ control: Control<PlaygroundForm>;
35
+ }) => {
36
+ const { fields } = useFieldArray<PlaygroundForm>({
37
+ control,
38
+ name: "urlParts",
39
+ });
40
+
41
+ return (
42
+ <div className="grid grid-cols-[1fr_1fr_auto]">
43
+ {fields.map((part, i) => (
44
+ <div
45
+ key={part.id}
46
+ className="grid-cols-subgrid col-span-full grid items-center gap-x-2 has-[:focus]:bg-muted hover:bg-muted rounded overflow-hidden group"
47
+ >
48
+ <InlineInput
49
+ {...register(`urlParts.${i}.name`)}
50
+ disabled
51
+ placeholder="Name"
52
+ className="peer"
53
+ />
54
+ <ParameterValue
55
+ {...register(`urlParts.${i}.value`)}
56
+ part={part.name}
57
+ {...{ [DATA_ATTR]: part.name }}
58
+ placeholder="Value"
59
+ className="peer"
60
+ />
61
+
62
+ <div className="col-span-full border-b border-border"></div>
63
+ </div>
64
+ ))}
65
+ </div>
66
+ );
67
+ };
@@ -12,15 +12,17 @@ const createVariantComponent = <
12
12
  cvx: ClassValue | C,
13
13
  // variantProps: Array<keyof VariantProps<C>> = [],
14
14
  ) => {
15
- const MyVariant = ({
16
- className,
17
- ...props
18
- }: JSX.IntrinsicElements[E] & { className?: ClassValue }) =>
15
+ const MyVariant = React.forwardRef<
16
+ HTMLElement,
17
+ JSX.IntrinsicElements[E] & { className?: ClassValue }
18
+ >(({ className, ...props }, ref) =>
19
19
  React.createElement(tag, {
20
20
  ...props,
21
+ ref,
21
22
  className:
22
23
  typeof cvx === "function" ? cvx({ className }) : cn(cvx, className),
23
- });
24
+ }),
25
+ );
24
26
 
25
27
  MyVariant.displayName = `VariantComponent(${tag})`;
26
28