@valbuild/ui 0.26.0 → 0.27.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 (138) hide show
  1. package/package.json +7 -3
  2. package/server/.tmp/assets/index-082e6676.css +1 -0
  3. package/server/.tmp/assets/index-3108ab2a.js +197 -0
  4. package/{index.html → server/.tmp/index.html} +3 -1
  5. package/.babelrc.json +0 -10
  6. package/.storybook/main.js +0 -25
  7. package/.storybook/preview-head.html +0 -6
  8. package/.storybook/preview.js +0 -33
  9. package/.storybook/theme.css +0 -34
  10. package/CHANGELOG.md +0 -0
  11. package/components.json +0 -16
  12. package/fix-server-hack.js +0 -54
  13. package/fullscreen.vite.config.ts +0 -9
  14. package/jest.config.js +0 -4
  15. package/postcss.config.js +0 -6
  16. package/rollup.config.js +0 -23
  17. package/server.vite.config.ts +0 -31
  18. package/src/App.tsx +0 -73
  19. package/src/assets/icons/Bold.tsx +0 -23
  20. package/src/assets/icons/Chevron.tsx +0 -28
  21. package/src/assets/icons/FontColor.tsx +0 -30
  22. package/src/assets/icons/ImageIcon.tsx +0 -29
  23. package/src/assets/icons/Italic.tsx +0 -24
  24. package/src/assets/icons/Logo.tsx +0 -103
  25. package/src/assets/icons/Section.tsx +0 -41
  26. package/src/assets/icons/Strikethrough.tsx +0 -22
  27. package/src/assets/icons/TextIcon.tsx +0 -20
  28. package/src/assets/icons/Underline.tsx +0 -22
  29. package/src/assets/icons/Undo.tsx +0 -20
  30. package/src/components/Button.tsx +0 -68
  31. package/src/components/Checkbox.tsx +0 -51
  32. package/src/components/DraggableList.stories.tsx +0 -20
  33. package/src/components/DraggableList.tsx +0 -95
  34. package/src/components/Dropdown.tsx +0 -101
  35. package/src/components/EditButton.tsx +0 -10
  36. package/src/components/ErrorText.tsx +0 -3
  37. package/src/components/ExpandLogo.tsx +0 -72
  38. package/src/components/Grid.stories.tsx +0 -43
  39. package/src/components/Grid.tsx +0 -139
  40. package/src/components/RichTextEditor/ContentEditable.tsx +0 -117
  41. package/src/components/RichTextEditor/Nodes/ImageNode.tsx +0 -100
  42. package/src/components/RichTextEditor/Plugins/AutoFocus.tsx +0 -12
  43. package/src/components/RichTextEditor/Plugins/ImagePlugin.tsx +0 -45
  44. package/src/components/RichTextEditor/Plugins/LinkEditorPlugin.tsx +0 -58
  45. package/src/components/RichTextEditor/Plugins/Toolbar.tsx +0 -412
  46. package/src/components/RichTextEditor/RichTextEditor.tsx +0 -105
  47. package/src/components/UploadModal.tsx +0 -109
  48. package/src/components/User.tsx +0 -17
  49. package/src/components/ValFormField.tsx +0 -574
  50. package/src/components/ValFullscreen.tsx +0 -1278
  51. package/src/components/ValMenu.tsx +0 -92
  52. package/src/components/ValOverlay.tsx +0 -488
  53. package/src/components/ValOverlayContext.tsx +0 -80
  54. package/src/components/ValWindow.stories.tsx +0 -146
  55. package/src/components/ValWindow.tsx +0 -220
  56. package/src/components/dashboard/DashboardButton.tsx +0 -25
  57. package/src/components/dashboard/DashboardDropdown.tsx +0 -59
  58. package/src/components/dashboard/Dropdown.stories.tsx +0 -11
  59. package/src/components/dashboard/Dropdown.tsx +0 -70
  60. package/src/components/dashboard/FormGroup.stories.tsx +0 -37
  61. package/src/components/dashboard/FormGroup.tsx +0 -42
  62. package/src/components/dashboard/Grid2.stories.tsx +0 -56
  63. package/src/components/dashboard/Grid2.tsx +0 -72
  64. package/src/components/dashboard/Tree.stories.tsx +0 -91
  65. package/src/components/dashboard/Tree.tsx +0 -72
  66. package/src/components/dashboard/ValDashboardEditor.tsx +0 -269
  67. package/src/components/dashboard/ValDashboardGrid.tsx +0 -142
  68. package/src/components/dashboard/ValTreeNavigator.tsx +0 -253
  69. package/src/components/forms/Form.tsx +0 -126
  70. package/src/components/forms/FormContainer.tsx +0 -24
  71. package/src/components/forms/ImageForm.tsx +0 -195
  72. package/src/components/forms/TextArea.tsx +0 -24
  73. package/src/components/ui/accordion.tsx +0 -58
  74. package/src/components/ui/alert-dialog.tsx +0 -139
  75. package/src/components/ui/avatar.tsx +0 -48
  76. package/src/components/ui/button.tsx +0 -56
  77. package/src/components/ui/calendar.tsx +0 -62
  78. package/src/components/ui/card.tsx +0 -86
  79. package/src/components/ui/checkbox.tsx +0 -28
  80. package/src/components/ui/command.tsx +0 -153
  81. package/src/components/ui/dialog.tsx +0 -120
  82. package/src/components/ui/dropdown-menu.tsx +0 -198
  83. package/src/components/ui/form.tsx +0 -177
  84. package/src/components/ui/input.tsx +0 -24
  85. package/src/components/ui/label.tsx +0 -24
  86. package/src/components/ui/popover.tsx +0 -29
  87. package/src/components/ui/progress.tsx +0 -26
  88. package/src/components/ui/radio-group.tsx +0 -42
  89. package/src/components/ui/scroll-area.tsx +0 -51
  90. package/src/components/ui/select.tsx +0 -119
  91. package/src/components/ui/switch.tsx +0 -27
  92. package/src/components/ui/tabs.tsx +0 -53
  93. package/src/components/ui/toggle.tsx +0 -43
  94. package/src/components/ui/tooltip.tsx +0 -28
  95. package/src/components/usePatch.ts +0 -86
  96. package/src/components/useTheme.ts +0 -45
  97. package/src/dto/SerializedSchema.ts +0 -69
  98. package/src/dto/Session.ts +0 -12
  99. package/src/dto/SessionMode.ts +0 -5
  100. package/src/dto/Tree.ts +0 -18
  101. package/src/exports.ts +0 -6
  102. package/src/index.css +0 -115
  103. package/src/index.tsx +0 -14
  104. package/src/lib/IValStore.ts +0 -6
  105. package/src/lib/utils.ts +0 -6
  106. package/src/main.jsx +0 -10
  107. package/src/richtext/conversion/conversion.test.ts +0 -146
  108. package/src/richtext/conversion/lexicalToRichTextSource.test.ts +0 -89
  109. package/src/richtext/conversion/lexicalToRichTextSource.ts +0 -285
  110. package/src/richtext/conversion/parseRichTextSource.test.ts +0 -469
  111. package/src/richtext/conversion/parseRichTextSource.ts +0 -233
  112. package/src/richtext/conversion/richTextSourceToLexical.test.ts +0 -381
  113. package/src/richtext/conversion/richTextSourceToLexical.ts +0 -293
  114. package/src/richtext/shadowRootPolyFill.js +0 -115
  115. package/src/server.ts +0 -70
  116. package/src/stories/Button.stories.tsx +0 -20
  117. package/src/stories/Checkbox.stories.tsx +0 -14
  118. package/src/stories/Dropdown.stories.tsx +0 -23
  119. package/src/stories/Introduction.mdx +0 -221
  120. package/src/stories/RichTextEditor.stories.tsx +0 -24
  121. package/src/stories/assets/code-brackets.svg +0 -1
  122. package/src/stories/assets/colors.svg +0 -1
  123. package/src/stories/assets/comments.svg +0 -1
  124. package/src/stories/assets/direction.svg +0 -1
  125. package/src/stories/assets/flow.svg +0 -1
  126. package/src/stories/assets/plugin.svg +0 -1
  127. package/src/stories/assets/repo.svg +0 -1
  128. package/src/stories/assets/stackalt.svg +0 -1
  129. package/src/utils/Remote.ts +0 -15
  130. package/src/utils/imageMimeType.ts +0 -23
  131. package/src/utils/readImage.ts +0 -54
  132. package/src/utils/resolvePath.ts +0 -32
  133. package/src/vite-env.d.ts +0 -1
  134. package/src/vite-index.tsx +0 -7
  135. package/src/vite-server.ts +0 -42
  136. package/tailwind.config.js +0 -83
  137. package/tsconfig.json +0 -19
  138. 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
- }