@valbuild/ui 0.26.0 → 0.28.0

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 (145) hide show
  1. package/dist/valbuild-ui.cjs.js +41 -15
  2. package/dist/valbuild-ui.esm.js +41 -15
  3. package/package.json +7 -3
  4. package/server/.tmp/assets/index-18cfa26c.css +1 -0
  5. package/server/.tmp/assets/index-513f7a9c.js +197 -0
  6. package/{index.html → server/.tmp/index.html} +3 -1
  7. package/server/dist/style.css +0 -3
  8. package/server/dist/valbuild-ui-main.cjs.js +60 -34
  9. package/server/dist/valbuild-ui-main.esm.js +60 -34
  10. package/server/dist/valbuild-ui-server.cjs.js +1 -1
  11. package/server/dist/valbuild-ui-server.esm.js +1 -1
  12. package/.babelrc.json +0 -10
  13. package/.storybook/main.js +0 -25
  14. package/.storybook/preview-head.html +0 -6
  15. package/.storybook/preview.js +0 -33
  16. package/.storybook/theme.css +0 -34
  17. package/CHANGELOG.md +0 -0
  18. package/components.json +0 -16
  19. package/fix-server-hack.js +0 -54
  20. package/fullscreen.vite.config.ts +0 -9
  21. package/jest.config.js +0 -4
  22. package/postcss.config.js +0 -6
  23. package/rollup.config.js +0 -23
  24. package/server.vite.config.ts +0 -31
  25. package/src/App.tsx +0 -73
  26. package/src/assets/icons/Bold.tsx +0 -23
  27. package/src/assets/icons/Chevron.tsx +0 -28
  28. package/src/assets/icons/FontColor.tsx +0 -30
  29. package/src/assets/icons/ImageIcon.tsx +0 -29
  30. package/src/assets/icons/Italic.tsx +0 -24
  31. package/src/assets/icons/Logo.tsx +0 -103
  32. package/src/assets/icons/Section.tsx +0 -41
  33. package/src/assets/icons/Strikethrough.tsx +0 -22
  34. package/src/assets/icons/TextIcon.tsx +0 -20
  35. package/src/assets/icons/Underline.tsx +0 -22
  36. package/src/assets/icons/Undo.tsx +0 -20
  37. package/src/components/Button.tsx +0 -68
  38. package/src/components/Checkbox.tsx +0 -51
  39. package/src/components/DraggableList.stories.tsx +0 -20
  40. package/src/components/DraggableList.tsx +0 -95
  41. package/src/components/Dropdown.tsx +0 -101
  42. package/src/components/EditButton.tsx +0 -10
  43. package/src/components/ErrorText.tsx +0 -3
  44. package/src/components/ExpandLogo.tsx +0 -72
  45. package/src/components/Grid.stories.tsx +0 -43
  46. package/src/components/Grid.tsx +0 -139
  47. package/src/components/RichTextEditor/ContentEditable.tsx +0 -117
  48. package/src/components/RichTextEditor/Nodes/ImageNode.tsx +0 -100
  49. package/src/components/RichTextEditor/Plugins/AutoFocus.tsx +0 -12
  50. package/src/components/RichTextEditor/Plugins/ImagePlugin.tsx +0 -45
  51. package/src/components/RichTextEditor/Plugins/LinkEditorPlugin.tsx +0 -58
  52. package/src/components/RichTextEditor/Plugins/Toolbar.tsx +0 -412
  53. package/src/components/RichTextEditor/RichTextEditor.tsx +0 -105
  54. package/src/components/UploadModal.tsx +0 -109
  55. package/src/components/User.tsx +0 -17
  56. package/src/components/ValFormField.tsx +0 -574
  57. package/src/components/ValFullscreen.tsx +0 -1278
  58. package/src/components/ValMenu.tsx +0 -92
  59. package/src/components/ValOverlay.tsx +0 -488
  60. package/src/components/ValOverlayContext.tsx +0 -80
  61. package/src/components/ValWindow.stories.tsx +0 -146
  62. package/src/components/ValWindow.tsx +0 -220
  63. package/src/components/dashboard/DashboardButton.tsx +0 -25
  64. package/src/components/dashboard/DashboardDropdown.tsx +0 -59
  65. package/src/components/dashboard/Dropdown.stories.tsx +0 -11
  66. package/src/components/dashboard/Dropdown.tsx +0 -70
  67. package/src/components/dashboard/FormGroup.stories.tsx +0 -37
  68. package/src/components/dashboard/FormGroup.tsx +0 -42
  69. package/src/components/dashboard/Grid2.stories.tsx +0 -56
  70. package/src/components/dashboard/Grid2.tsx +0 -72
  71. package/src/components/dashboard/Tree.stories.tsx +0 -91
  72. package/src/components/dashboard/Tree.tsx +0 -72
  73. package/src/components/dashboard/ValDashboardEditor.tsx +0 -269
  74. package/src/components/dashboard/ValDashboardGrid.tsx +0 -142
  75. package/src/components/dashboard/ValTreeNavigator.tsx +0 -253
  76. package/src/components/forms/Form.tsx +0 -126
  77. package/src/components/forms/FormContainer.tsx +0 -24
  78. package/src/components/forms/ImageForm.tsx +0 -195
  79. package/src/components/forms/TextArea.tsx +0 -24
  80. package/src/components/ui/accordion.tsx +0 -58
  81. package/src/components/ui/alert-dialog.tsx +0 -139
  82. package/src/components/ui/avatar.tsx +0 -48
  83. package/src/components/ui/button.tsx +0 -56
  84. package/src/components/ui/calendar.tsx +0 -62
  85. package/src/components/ui/card.tsx +0 -86
  86. package/src/components/ui/checkbox.tsx +0 -28
  87. package/src/components/ui/command.tsx +0 -153
  88. package/src/components/ui/dialog.tsx +0 -120
  89. package/src/components/ui/dropdown-menu.tsx +0 -198
  90. package/src/components/ui/form.tsx +0 -177
  91. package/src/components/ui/input.tsx +0 -24
  92. package/src/components/ui/label.tsx +0 -24
  93. package/src/components/ui/popover.tsx +0 -29
  94. package/src/components/ui/progress.tsx +0 -26
  95. package/src/components/ui/radio-group.tsx +0 -42
  96. package/src/components/ui/scroll-area.tsx +0 -51
  97. package/src/components/ui/select.tsx +0 -119
  98. package/src/components/ui/switch.tsx +0 -27
  99. package/src/components/ui/tabs.tsx +0 -53
  100. package/src/components/ui/toggle.tsx +0 -43
  101. package/src/components/ui/tooltip.tsx +0 -28
  102. package/src/components/usePatch.ts +0 -86
  103. package/src/components/useTheme.ts +0 -45
  104. package/src/dto/SerializedSchema.ts +0 -69
  105. package/src/dto/Session.ts +0 -12
  106. package/src/dto/SessionMode.ts +0 -5
  107. package/src/dto/Tree.ts +0 -18
  108. package/src/exports.ts +0 -6
  109. package/src/index.css +0 -115
  110. package/src/index.tsx +0 -14
  111. package/src/lib/IValStore.ts +0 -6
  112. package/src/lib/utils.ts +0 -6
  113. package/src/main.jsx +0 -10
  114. package/src/richtext/conversion/conversion.test.ts +0 -146
  115. package/src/richtext/conversion/lexicalToRichTextSource.test.ts +0 -89
  116. package/src/richtext/conversion/lexicalToRichTextSource.ts +0 -285
  117. package/src/richtext/conversion/parseRichTextSource.test.ts +0 -469
  118. package/src/richtext/conversion/parseRichTextSource.ts +0 -233
  119. package/src/richtext/conversion/richTextSourceToLexical.test.ts +0 -381
  120. package/src/richtext/conversion/richTextSourceToLexical.ts +0 -293
  121. package/src/richtext/shadowRootPolyFill.js +0 -115
  122. package/src/server.ts +0 -70
  123. package/src/stories/Button.stories.tsx +0 -20
  124. package/src/stories/Checkbox.stories.tsx +0 -14
  125. package/src/stories/Dropdown.stories.tsx +0 -23
  126. package/src/stories/Introduction.mdx +0 -221
  127. package/src/stories/RichTextEditor.stories.tsx +0 -24
  128. package/src/stories/assets/code-brackets.svg +0 -1
  129. package/src/stories/assets/colors.svg +0 -1
  130. package/src/stories/assets/comments.svg +0 -1
  131. package/src/stories/assets/direction.svg +0 -1
  132. package/src/stories/assets/flow.svg +0 -1
  133. package/src/stories/assets/plugin.svg +0 -1
  134. package/src/stories/assets/repo.svg +0 -1
  135. package/src/stories/assets/stackalt.svg +0 -1
  136. package/src/utils/Remote.ts +0 -15
  137. package/src/utils/imageMimeType.ts +0 -23
  138. package/src/utils/readImage.ts +0 -54
  139. package/src/utils/resolvePath.ts +0 -32
  140. package/src/vite-env.d.ts +0 -1
  141. package/src/vite-index.tsx +0 -7
  142. package/src/vite-server.ts +0 -42
  143. package/tailwind.config.js +0 -83
  144. package/tsconfig.json +0 -19
  145. package/vite.config.ts +0 -43
@@ -1,574 +0,0 @@
1
- import {
2
- AnyRichTextOptions,
3
- FileSource,
4
- ImageMetadata,
5
- Internal,
6
- Json,
7
- RichTextSource,
8
- SerializedSchema,
9
- SourcePath,
10
- VAL_EXTENSION,
11
- } from "@valbuild/core";
12
- import type { PatchJSON } from "@valbuild/core/patch";
13
- import { LexicalEditor } from "lexical";
14
- import { useState, useEffect, useRef } from "react";
15
- import { RichTextEditor } from "../exports";
16
- import { lexicalToRichTextSource } from "../richtext/conversion/lexicalToRichTextSource";
17
- import { LexicalRootNode } from "../richtext/conversion/richTextSourceToLexical";
18
- import { readImage } from "../utils/readImage";
19
- import { Button } from "./ui/button";
20
- import { Input } from "./ui/input";
21
- import {
22
- Select,
23
- SelectContent,
24
- SelectItem,
25
- SelectTrigger,
26
- SelectValue,
27
- } from "./ui/select";
28
- import { PatchCallback } from "./usePatch";
29
- import { useValModuleFromPath } from "./ValFullscreen";
30
-
31
- type ImageSource = FileSource<ImageMetadata>;
32
- export type OnSubmit = (callback: PatchCallback) => Promise<void>;
33
-
34
- export function ValFormField({
35
- path,
36
- disabled,
37
- source: source,
38
- schema: schema,
39
- registerPatchCallback,
40
- onSubmit,
41
- }: {
42
- path: string;
43
- disabled: boolean;
44
- source: Json;
45
- schema: SerializedSchema;
46
- onSubmit?: OnSubmit;
47
- registerPatchCallback?: (callback: PatchCallback) => void;
48
- }) {
49
- if (
50
- (typeof source === "string" || source === null) &&
51
- schema?.type === "string"
52
- ) {
53
- return (
54
- <StringField
55
- defaultValue={source}
56
- disabled={disabled}
57
- registerPatchCallback={registerPatchCallback}
58
- onSubmit={onSubmit}
59
- />
60
- );
61
- }
62
- if (
63
- (typeof source === "number" || source === null) &&
64
- schema?.type === "number"
65
- ) {
66
- return (
67
- <NumberField
68
- defaultValue={source}
69
- disabled={disabled}
70
- registerPatchCallback={registerPatchCallback}
71
- onSubmit={onSubmit}
72
- />
73
- );
74
- }
75
- if (
76
- (typeof source === "number" ||
77
- typeof source === "string" ||
78
- source === null) &&
79
- schema?.type === "keyOf"
80
- ) {
81
- return (
82
- <KeyOfField
83
- defaultValue={source}
84
- disabled={disabled}
85
- registerPatchCallback={registerPatchCallback}
86
- onSubmit={onSubmit}
87
- selector={schema.selector}
88
- />
89
- );
90
- }
91
- if (
92
- (typeof source === "object" || source === null) &&
93
- schema?.type === "richtext"
94
- ) {
95
- return (
96
- <RichTextField
97
- registerPatchCallback={registerPatchCallback}
98
- onSubmit={onSubmit}
99
- defaultValue={source as RichTextSource<AnyRichTextOptions>}
100
- />
101
- );
102
- }
103
- if (
104
- (typeof source === "object" || source === null) &&
105
- schema?.type === "image"
106
- ) {
107
- return (
108
- <ImageField
109
- path={path}
110
- registerPatchCallback={registerPatchCallback}
111
- onSubmit={onSubmit}
112
- defaultValue={source as ImageSource}
113
- />
114
- );
115
- }
116
- return <div>Unsupported schema: {schema.type}</div>;
117
- }
118
-
119
- async function createImagePatch(
120
- path: string,
121
- data: string | null,
122
- metadata: ImageMetadata,
123
- defaultValue?: ImageSource
124
- ): Promise<PatchJSON> {
125
- const pathParts = path.split("/");
126
- if (!data || !metadata) {
127
- return [];
128
- }
129
- return [
130
- {
131
- value: {
132
- ...defaultValue,
133
- metadata,
134
- },
135
- op: "replace",
136
- path,
137
- },
138
- // update the contents of the file:
139
- {
140
- value: data,
141
- op: "replace",
142
- path: `${pathParts.slice(0, -1).join("/")}/$${
143
- pathParts[pathParts.length - 1]
144
- }`,
145
- },
146
- ];
147
- }
148
-
149
- function ImageField({
150
- path,
151
- defaultValue,
152
- onSubmit,
153
- registerPatchCallback,
154
- }: {
155
- path: string;
156
- onSubmit?: OnSubmit;
157
- registerPatchCallback?: (callback: PatchCallback) => void;
158
- defaultValue?: ImageSource;
159
- }) {
160
- const [data, setData] = useState<string | null>(null);
161
- const [loading, setLoading] = useState(false);
162
- const [metadata, setMetadata] = useState<ImageMetadata>();
163
- const [url, setUrl] = useState<string>();
164
- useEffect(() => {
165
- setUrl(defaultValue && Internal.convertFileSource(defaultValue).url);
166
- }, [defaultValue]);
167
-
168
- useEffect(() => {
169
- if (registerPatchCallback) {
170
- registerPatchCallback(async (path) => {
171
- return createImagePatch(path, data, metadata, defaultValue);
172
- });
173
- }
174
- }, [data, defaultValue]);
175
-
176
- return (
177
- <div className="max-w-4xl p-4" key={path}>
178
- <label htmlFor={`img_input:${path}`} className="">
179
- {data || url ? <img src={data || url} /> : <div>Empty</div>}
180
- <input
181
- id={`img_input:${path}`}
182
- type="file"
183
- hidden
184
- onChange={(ev) => {
185
- readImage(ev)
186
- .then((res) => {
187
- setData(res.src);
188
- if (res.width && res.height) {
189
- setMetadata({
190
- sha256: res.sha256,
191
- width: res.width,
192
- height: res.height,
193
- });
194
- } else {
195
- setMetadata(undefined);
196
- }
197
- })
198
- .catch((err) => {
199
- console.error(err.message);
200
- setData(null);
201
- setMetadata(undefined);
202
- });
203
- }}
204
- />
205
- </label>
206
- {onSubmit && (
207
- <div>
208
- {data && (
209
- <Button
210
- disabled={loading}
211
- onClick={() => {
212
- setLoading(true);
213
- onSubmit((path) =>
214
- createImagePatch(path, data, metadata, defaultValue)
215
- ).finally(() => {
216
- setLoading(false);
217
- setData(null);
218
- setMetadata(undefined);
219
- });
220
- }}
221
- >
222
- {loading ? "Saving..." : "Submit"}
223
- </Button>
224
- )}
225
- </div>
226
- )}
227
- </div>
228
- );
229
- }
230
-
231
- async function createRichTextPatch(path: string, editor: LexicalEditor) {
232
- const { templateStrings, exprs, files } = editor
233
- ? await lexicalToRichTextSource(
234
- editor.getEditorState().toJSON().root as LexicalRootNode
235
- )
236
- : ({
237
- [VAL_EXTENSION]: "richtext",
238
- templateStrings: [""],
239
- exprs: [],
240
- files: {},
241
- } as RichTextSource<AnyRichTextOptions> & {
242
- files: Record<string, string>;
243
- });
244
- return [
245
- {
246
- op: "replace" as const,
247
- path,
248
- value: {
249
- templateStrings,
250
- exprs,
251
- [VAL_EXTENSION]: "richtext",
252
- },
253
- },
254
- ...Object.entries(files).map(([filePath, value]) => {
255
- return {
256
- op: "file" as const,
257
- path,
258
- filePath,
259
- value,
260
- };
261
- }),
262
- ];
263
- }
264
- function RichTextField({
265
- defaultValue,
266
- onSubmit,
267
- registerPatchCallback,
268
- }: {
269
- onSubmit?: OnSubmit;
270
- registerPatchCallback?: (callback: PatchCallback) => void;
271
- defaultValue?: RichTextSource<AnyRichTextOptions>;
272
- }) {
273
- const [editor, setEditor] = useState<LexicalEditor | null>(null);
274
- const [didChange, setDidChange] = useState(false);
275
- const [loading, setLoading] = useState(false);
276
- useEffect(() => {
277
- if (editor) {
278
- setDidChange(false);
279
- editor.registerTextContentListener(() => {
280
- setDidChange(true);
281
- });
282
- editor.registerDecoratorListener(() => {
283
- setDidChange(true);
284
- });
285
- }
286
- }, [editor]);
287
- useEffect(() => {
288
- if (editor && registerPatchCallback) {
289
- registerPatchCallback((path) => createRichTextPatch(path, editor));
290
- }
291
- }, [editor]);
292
- return (
293
- <div className="p-4 border rounded border-card">
294
- <RichTextEditor
295
- onEditor={(editor) => {
296
- setEditor(editor);
297
- }}
298
- richtext={
299
- defaultValue ||
300
- ({
301
- children: [],
302
- [VAL_EXTENSION]: "root",
303
- } as unknown as RichTextSource<AnyRichTextOptions>)
304
- }
305
- />
306
- {onSubmit && (
307
- <div>
308
- {didChange && (
309
- <Button
310
- disabled={loading || !editor}
311
- onClick={() => {
312
- if (editor) {
313
- setLoading(true);
314
- onSubmit((path) => createRichTextPatch(path, editor)).finally(
315
- () => {
316
- setLoading(false);
317
- setDidChange(false);
318
- }
319
- );
320
- }
321
- }}
322
- >
323
- {loading ? "Saving..." : "Submit"}
324
- </Button>
325
- )}
326
- </div>
327
- )}
328
- </div>
329
- );
330
- }
331
-
332
- function KeyOfField({
333
- disabled,
334
- defaultValue,
335
- registerPatchCallback,
336
- onSubmit,
337
- selector,
338
- }: {
339
- registerPatchCallback?: (callback: PatchCallback) => void;
340
- onSubmit?: OnSubmit;
341
- disabled: boolean;
342
- defaultValue?: string | number | null;
343
- selector: SourcePath;
344
- }) {
345
- const valModule = useValModuleFromPath(selector);
346
- const getValuesFromModule = (module: typeof valModule) => {
347
- if (Array.isArray(module.moduleSource)) {
348
- return {
349
- type: "number",
350
- values: Object.keys(module.moduleSource).map((key) => parseInt(key)),
351
- };
352
- }
353
- return {
354
- type: "string",
355
- values: Object.keys(module.moduleSource ?? ["ERROR fetching source"]),
356
- };
357
- };
358
- const typeAndValues = getValuesFromModule(valModule);
359
- const [value, setValue] = useState(defaultValue ?? typeAndValues.values[0]);
360
- const [loading, setLoading] = useState(false);
361
- useEffect(() => {
362
- setLoading(disabled);
363
- }, [disabled]);
364
-
365
- const parse = (value: string) => {
366
- if (typeAndValues.type === "number") {
367
- if (value === "") {
368
- throw new Error("Value cannot be empty");
369
- }
370
- if (Number.isNaN(Number(value))) {
371
- throw new Error("Value was not a number: " + JSON.stringify(value));
372
- }
373
- return Number(value);
374
- }
375
- return value;
376
- };
377
-
378
- useEffect(() => {
379
- if (registerPatchCallback) {
380
- registerPatchCallback(async (path) => {
381
- return [
382
- {
383
- op: "replace",
384
- path,
385
- value: value,
386
- },
387
- ];
388
- });
389
- }
390
- }, [value]);
391
- return (
392
- <div className="flex flex-col justify-between h-full gap-y-4">
393
- <Select
394
- defaultValue={value.toString()}
395
- disabled={loading}
396
- onValueChange={(value) => {
397
- setValue(parse(value));
398
- }}
399
- >
400
- <SelectTrigger>
401
- <SelectValue placeholder="Select a value" />
402
- </SelectTrigger>
403
- <SelectContent>
404
- {typeAndValues.values.map((value) => (
405
- <SelectItem key={value} value={value.toString()}>
406
- {value.toString()}
407
- </SelectItem>
408
- ))}
409
- </SelectContent>
410
- </Select>
411
- {onSubmit && (
412
- <div>
413
- {defaultValue !== value && (
414
- <Button
415
- disabled={loading}
416
- onClick={() => {
417
- setLoading(true);
418
- onSubmit(async (path) => [
419
- {
420
- op: "replace",
421
- path,
422
- value: value,
423
- },
424
- ]).finally(() => {
425
- setLoading(false);
426
- });
427
- }}
428
- >
429
- {loading ? "Saving..." : "Submit"}
430
- </Button>
431
- )}
432
- </div>
433
- )}
434
- </div>
435
- );
436
- }
437
- function NumberField({
438
- disabled,
439
- defaultValue,
440
- registerPatchCallback,
441
- onSubmit,
442
- }: {
443
- registerPatchCallback?: (callback: PatchCallback) => void;
444
- onSubmit?: OnSubmit;
445
- disabled: boolean;
446
- defaultValue?: number | null;
447
- }) {
448
- const [value, setValue] = useState(defaultValue || 0);
449
- const [loading, setLoading] = useState(false);
450
- useEffect(() => {
451
- setLoading(disabled);
452
- }, [disabled]);
453
-
454
- // ref is used to get the value of the textarea without closing over the value field
455
- // to avoid registering a new callback every time the value changes
456
- const ref = useRef<HTMLInputElement>(null);
457
- useEffect(() => {
458
- if (registerPatchCallback) {
459
- registerPatchCallback(async (path) => {
460
- return [
461
- {
462
- op: "replace",
463
- path,
464
- value: Number(ref.current?.value) || 0,
465
- },
466
- ];
467
- });
468
- }
469
- }, []);
470
-
471
- return (
472
- <div className="flex flex-col justify-between h-full gap-y-4">
473
- <Input
474
- ref={ref}
475
- disabled={loading}
476
- defaultValue={value ?? 0}
477
- onChange={(e) => setValue(Number(e.target.value))}
478
- type="number"
479
- />
480
- {onSubmit && (
481
- <div>
482
- {defaultValue !== value && (
483
- <Button
484
- disabled={loading}
485
- onClick={() => {
486
- setLoading(true);
487
- onSubmit(async (path) => [
488
- {
489
- op: "replace",
490
- path,
491
- value: Number(ref.current?.value) || 0,
492
- },
493
- ]).finally(() => {
494
- setLoading(false);
495
- });
496
- }}
497
- >
498
- {loading ? "Saving..." : "Submit"}
499
- </Button>
500
- )}
501
- </div>
502
- )}
503
- </div>
504
- );
505
- }
506
-
507
- function StringField({
508
- disabled,
509
- defaultValue,
510
- registerPatchCallback,
511
- onSubmit,
512
- }: {
513
- registerPatchCallback?: (callback: PatchCallback) => void;
514
- onSubmit?: OnSubmit;
515
- disabled: boolean;
516
- defaultValue?: string | null;
517
- }) {
518
- const [value, setValue] = useState(defaultValue || "");
519
- const [loading, setLoading] = useState(false);
520
- useEffect(() => {
521
- setLoading(disabled);
522
- }, [disabled]);
523
-
524
- // ref is used to get the value of the textarea without closing over the value field
525
- // to avoid registering a new callback every time the value changes
526
- const ref = useRef<HTMLInputElement>(null);
527
- useEffect(() => {
528
- if (registerPatchCallback) {
529
- registerPatchCallback(async (path) => {
530
- return [
531
- {
532
- op: "replace",
533
- path,
534
- value: ref.current?.value || "",
535
- },
536
- ];
537
- });
538
- }
539
- }, []);
540
-
541
- return (
542
- <div className="flex flex-col justify-between h-full gap-y-4">
543
- <Input
544
- ref={ref}
545
- disabled={loading}
546
- defaultValue={value ?? ""}
547
- onChange={(e) => setValue(e.target.value)}
548
- />
549
- {onSubmit && (
550
- <div>
551
- {defaultValue !== value && (
552
- <Button
553
- disabled={loading}
554
- onClick={() => {
555
- setLoading(true);
556
- onSubmit(async (path) => [
557
- {
558
- op: "replace",
559
- path,
560
- value: ref.current?.value || "",
561
- },
562
- ]).finally(() => {
563
- setLoading(false);
564
- });
565
- }}
566
- >
567
- {loading ? "Saving..." : "Submit"}
568
- </Button>
569
- )}
570
- </div>
571
- )}
572
- </div>
573
- );
574
- }