@carlonicora/nextjs-jsonapi 1.59.0 → 1.60.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.
- package/dist/{BlockNoteEditor-LM45SVSD.js → BlockNoteEditor-56VMCPSG.js} +6 -6
- package/dist/{BlockNoteEditor-LM45SVSD.js.map → BlockNoteEditor-56VMCPSG.js.map} +1 -1
- package/dist/{BlockNoteEditor-V46DP6BW.mjs → BlockNoteEditor-H7QM6EVJ.mjs} +2 -2
- package/dist/billing/index.js +299 -299
- package/dist/billing/index.mjs +1 -1
- package/dist/{chunk-FDTDSTD6.mjs → chunk-67522EQN.mjs} +2150 -2062
- package/dist/chunk-67522EQN.mjs.map +1 -0
- package/dist/{chunk-4QXIOFK5.js → chunk-JRKIV2DF.js} +213 -125
- package/dist/chunk-JRKIV2DF.js.map +1 -0
- package/dist/client/index.js +2 -2
- package/dist/client/index.mjs +1 -1
- package/dist/components/index.d.mts +26 -1
- package/dist/components/index.d.ts +26 -1
- package/dist/components/index.js +6 -2
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +5 -1
- package/dist/contexts/index.js +2 -2
- package/dist/contexts/index.mjs +1 -1
- package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/editor.template.js +17 -39
- package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -1
- package/package.json +1 -1
- package/scripts/generate-web-module/templates/components/editor.template.ts +17 -39
- package/src/components/forms/CommonEditorDiscardDialog.tsx +40 -0
- package/src/components/forms/__tests__/CommonEditorDiscardDialog.test.tsx +43 -0
- package/src/components/forms/__tests__/useEditorDialog.test.ts +145 -0
- package/src/components/forms/index.ts +2 -0
- package/src/components/forms/useEditorDialog.ts +93 -0
- package/src/components/tables/ContentListTable.tsx +13 -14
- package/dist/chunk-4QXIOFK5.js.map +0 -1
- package/dist/chunk-FDTDSTD6.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-V46DP6BW.mjs.map → BlockNoteEditor-H7QM6EVJ.mjs.map} +0 -0
package/package.json
CHANGED
|
@@ -53,26 +53,9 @@ function ${names.pascalCase}EditorInternal({
|
|
|
53
53
|
}: ${names.pascalCase}EditorProps) {
|
|
54
54
|
const router = useRouter();
|
|
55
55
|
const generateUrl = usePageUrlGenerator();
|
|
56
|
-
const [open, setOpen] = useState<boolean>(false);
|
|
57
56
|
const t = useTranslations();
|
|
58
57
|
${hasAuthor ? ` const { currentUser } = useCurrentUserContext<UserInterface>();` : ""}
|
|
59
58
|
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (dialogOpen !== undefined) {
|
|
62
|
-
setOpen(dialogOpen);
|
|
63
|
-
}
|
|
64
|
-
}, [dialogOpen]);
|
|
65
|
-
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
if (typeof onDialogOpenChange === "function") {
|
|
68
|
-
onDialogOpenChange(open);
|
|
69
|
-
}
|
|
70
|
-
}, [open, onDialogOpenChange]);
|
|
71
|
-
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
if (forceShow) setOpen(true);
|
|
74
|
-
}, [forceShow]);
|
|
75
|
-
|
|
76
59
|
${formSchema}
|
|
77
60
|
|
|
78
61
|
${defaultValues}
|
|
@@ -82,10 +65,19 @@ ${defaultValues}
|
|
|
82
65
|
defaultValues: getDefaultValues(),
|
|
83
66
|
});
|
|
84
67
|
|
|
68
|
+
const { dirtyFields } = form.formState;
|
|
69
|
+
|
|
70
|
+
const isFormDirty = useCallback(() => {
|
|
71
|
+
return Object.keys(dirtyFields).length > 0;
|
|
72
|
+
}, [dirtyFields]);
|
|
73
|
+
|
|
74
|
+
const { open, setOpen, handleOpenChange, discardDialogProps } = useEditorDialog(isFormDirty, {
|
|
75
|
+
dialogOpen, onDialogOpenChange, forceShow, onClose,
|
|
76
|
+
});
|
|
77
|
+
|
|
85
78
|
useEffect(() => {
|
|
86
79
|
if (!open) {
|
|
87
80
|
form.reset(getDefaultValues());
|
|
88
|
-
if (onClose) onClose();
|
|
89
81
|
}
|
|
90
82
|
}, [open]);
|
|
91
83
|
|
|
@@ -101,25 +93,9 @@ ${
|
|
|
101
93
|
|
|
102
94
|
${onSubmit}
|
|
103
95
|
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
106
|
-
if (event.key === "Escape" && open) {
|
|
107
|
-
event.preventDefault();
|
|
108
|
-
event.stopPropagation();
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
if (open) {
|
|
113
|
-
document.addEventListener("keydown", handleKeyDown, true);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return () => {
|
|
117
|
-
document.removeEventListener("keydown", handleKeyDown, true);
|
|
118
|
-
};
|
|
119
|
-
}, [open]);
|
|
120
|
-
|
|
121
96
|
return (
|
|
122
|
-
|
|
97
|
+
<>
|
|
98
|
+
<Dialog open={open} onOpenChange={handleOpenChange}>
|
|
123
99
|
{dialogOpen === undefined && (trigger ? <DialogTrigger>{trigger}</DialogTrigger> : <CommonEditorTrigger isEdit={!!${names.camelCase}} />)}
|
|
124
100
|
<DialogContent
|
|
125
101
|
className="flex max-h-[70vh] max-w-3xl flex-col overflow-y-auto"
|
|
@@ -129,12 +105,14 @@ ${onSubmit}
|
|
|
129
105
|
<form onSubmit={form.handleSubmit(onSubmit)} className="flex w-full flex-col gap-y-4">
|
|
130
106
|
<div className="flex flex-col justify-between gap-x-4">
|
|
131
107
|
${formFields}
|
|
132
|
-
<CommonEditorButtons form={form} setOpen={
|
|
108
|
+
<CommonEditorButtons form={form} setOpen={handleOpenChange} isEdit={!!${names.camelCase}} />
|
|
133
109
|
</div>
|
|
134
110
|
</form>
|
|
135
111
|
</Form>
|
|
136
112
|
</DialogContent>
|
|
137
113
|
</Dialog>
|
|
114
|
+
<CommonEditorDiscardDialog {...discardDialogProps} />
|
|
115
|
+
</>
|
|
138
116
|
);
|
|
139
117
|
}
|
|
140
118
|
|
|
@@ -194,7 +172,7 @@ function generateImports(data: FrontendTemplateData): string {
|
|
|
194
172
|
imports.push(`import { revalidatePaths } from "@/utils/revalidation";`);
|
|
195
173
|
|
|
196
174
|
// Library component imports
|
|
197
|
-
const componentImports: string[] = ["CommonEditorButtons", "CommonEditorHeader", "CommonEditorTrigger", "errorToast"];
|
|
175
|
+
const componentImports: string[] = ["CommonEditorButtons", "CommonEditorDiscardDialog", "CommonEditorHeader", "CommonEditorTrigger", "errorToast", "useEditorDialog"];
|
|
198
176
|
|
|
199
177
|
// Check for field types that need specific components
|
|
200
178
|
const hasContentField = fields.some((f) => f.isContentField || f.name === "content");
|
|
@@ -251,7 +229,7 @@ function generateImports(data: FrontendTemplateData): string {
|
|
|
251
229
|
// Other imports
|
|
252
230
|
imports.push(`import { zodResolver } from "@hookform/resolvers/zod";`);
|
|
253
231
|
imports.push(`import { useTranslations } from "next-intl";`);
|
|
254
|
-
imports.push(`import { ReactNode,
|
|
232
|
+
imports.push(`import { ReactNode, useCallback, useEffect } from "react";`);
|
|
255
233
|
imports.push(`import { SubmitHandler, useForm } from "react-hook-form";`);
|
|
256
234
|
imports.push(`import { v4 } from "uuid";`);
|
|
257
235
|
imports.push(`import { z } from "zod";`);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
4
|
+
import {
|
|
5
|
+
AlertDialog,
|
|
6
|
+
AlertDialogAction,
|
|
7
|
+
AlertDialogCancel,
|
|
8
|
+
AlertDialogContent,
|
|
9
|
+
AlertDialogDescription,
|
|
10
|
+
AlertDialogFooter,
|
|
11
|
+
AlertDialogHeader,
|
|
12
|
+
AlertDialogTitle,
|
|
13
|
+
} from "../../shadcnui";
|
|
14
|
+
|
|
15
|
+
type CommonEditorDiscardDialogProps = {
|
|
16
|
+
open: boolean;
|
|
17
|
+
onOpenChange: (open: boolean) => void;
|
|
18
|
+
onDiscard: () => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function CommonEditorDiscardDialog({ open, onOpenChange, onDiscard }: CommonEditorDiscardDialogProps) {
|
|
22
|
+
const t = useTranslations();
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
|
26
|
+
<AlertDialogContent>
|
|
27
|
+
<AlertDialogHeader>
|
|
28
|
+
<AlertDialogTitle>{t(`ui.dialogs.unsaved_changes_title`)}</AlertDialogTitle>
|
|
29
|
+
<AlertDialogDescription>{t(`ui.dialogs.unsaved_changes_description`)}</AlertDialogDescription>
|
|
30
|
+
</AlertDialogHeader>
|
|
31
|
+
<AlertDialogFooter>
|
|
32
|
+
<AlertDialogCancel>{t(`ui.buttons.cancel`)}</AlertDialogCancel>
|
|
33
|
+
<AlertDialogAction variant="destructive" onClick={onDiscard}>
|
|
34
|
+
{t(`ui.dialogs.unsaved_changes_discard`)}
|
|
35
|
+
</AlertDialogAction>
|
|
36
|
+
</AlertDialogFooter>
|
|
37
|
+
</AlertDialogContent>
|
|
38
|
+
</AlertDialog>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
3
|
+
import { CommonEditorDiscardDialog } from "../CommonEditorDiscardDialog";
|
|
4
|
+
|
|
5
|
+
// Mock next-intl
|
|
6
|
+
vi.mock("next-intl", () => ({
|
|
7
|
+
useTranslations: () => (key: string) => key,
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
describe("CommonEditorDiscardDialog", () => {
|
|
11
|
+
const defaultProps = {
|
|
12
|
+
open: true,
|
|
13
|
+
onOpenChange: vi.fn(),
|
|
14
|
+
onDiscard: vi.fn(),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should render dialog content when open", () => {
|
|
22
|
+
render(<CommonEditorDiscardDialog {...defaultProps} />);
|
|
23
|
+
expect(screen.getByText("ui.dialogs.unsaved_changes_title")).toBeInTheDocument();
|
|
24
|
+
expect(screen.getByText("ui.dialogs.unsaved_changes_description")).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should render cancel and discard buttons", () => {
|
|
28
|
+
render(<CommonEditorDiscardDialog {...defaultProps} />);
|
|
29
|
+
expect(screen.getByText("ui.buttons.cancel")).toBeInTheDocument();
|
|
30
|
+
expect(screen.getByText("ui.dialogs.unsaved_changes_discard")).toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should call onDiscard when discard button is clicked", () => {
|
|
34
|
+
render(<CommonEditorDiscardDialog {...defaultProps} />);
|
|
35
|
+
fireEvent.click(screen.getByText("ui.dialogs.unsaved_changes_discard"));
|
|
36
|
+
expect(defaultProps.onDiscard).toHaveBeenCalled();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should not render when closed", () => {
|
|
40
|
+
render(<CommonEditorDiscardDialog {...defaultProps} open={false} />);
|
|
41
|
+
expect(screen.queryByText("ui.dialogs.unsaved_changes_title")).not.toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { renderHook, act } from "@testing-library/react";
|
|
3
|
+
import { useEditorDialog } from "../useEditorDialog";
|
|
4
|
+
|
|
5
|
+
describe("useEditorDialog", () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.clearAllMocks();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe("open state", () => {
|
|
11
|
+
it("should start closed by default", () => {
|
|
12
|
+
const { result } = renderHook(() => useEditorDialog(() => false));
|
|
13
|
+
expect(result.current.open).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should allow setting open state directly", () => {
|
|
17
|
+
const { result } = renderHook(() => useEditorDialog(() => false));
|
|
18
|
+
act(() => result.current.setOpen(true));
|
|
19
|
+
expect(result.current.open).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("handleOpenChange", () => {
|
|
24
|
+
it("should open dialog when called with true", () => {
|
|
25
|
+
const { result } = renderHook(() => useEditorDialog(() => false));
|
|
26
|
+
act(() => result.current.handleOpenChange(true));
|
|
27
|
+
expect(result.current.open).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should close dialog when form is not dirty", () => {
|
|
31
|
+
const { result } = renderHook(() => useEditorDialog(() => false));
|
|
32
|
+
act(() => result.current.setOpen(true));
|
|
33
|
+
act(() => result.current.handleOpenChange(false));
|
|
34
|
+
expect(result.current.open).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should show discard confirmation when form is dirty and closing", () => {
|
|
38
|
+
const { result } = renderHook(() => useEditorDialog(() => true));
|
|
39
|
+
act(() => result.current.setOpen(true));
|
|
40
|
+
act(() => result.current.handleOpenChange(false));
|
|
41
|
+
expect(result.current.open).toBe(true);
|
|
42
|
+
expect(result.current.discardDialogProps.open).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("discardDialogProps", () => {
|
|
47
|
+
it("should close both dialogs on discard", () => {
|
|
48
|
+
const { result } = renderHook(() => useEditorDialog(() => true));
|
|
49
|
+
act(() => result.current.setOpen(true));
|
|
50
|
+
act(() => result.current.handleOpenChange(false));
|
|
51
|
+
expect(result.current.discardDialogProps.open).toBe(true);
|
|
52
|
+
act(() => result.current.discardDialogProps.onDiscard());
|
|
53
|
+
expect(result.current.discardDialogProps.open).toBe(false);
|
|
54
|
+
expect(result.current.open).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should allow dismissing discard dialog via onOpenChange", () => {
|
|
58
|
+
const { result } = renderHook(() => useEditorDialog(() => true));
|
|
59
|
+
act(() => result.current.setOpen(true));
|
|
60
|
+
act(() => result.current.handleOpenChange(false));
|
|
61
|
+
act(() => result.current.discardDialogProps.onOpenChange(false));
|
|
62
|
+
expect(result.current.discardDialogProps.open).toBe(false);
|
|
63
|
+
expect(result.current.open).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("options.dialogOpen", () => {
|
|
68
|
+
it("should sync open state from external dialogOpen prop", () => {
|
|
69
|
+
const { result, rerender } = renderHook(({ dialogOpen }) => useEditorDialog(() => false, { dialogOpen }), {
|
|
70
|
+
initialProps: { dialogOpen: false },
|
|
71
|
+
});
|
|
72
|
+
expect(result.current.open).toBe(false);
|
|
73
|
+
rerender({ dialogOpen: true });
|
|
74
|
+
expect(result.current.open).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("options.onDialogOpenChange", () => {
|
|
79
|
+
it("should notify parent when open state changes", () => {
|
|
80
|
+
const onDialogOpenChange = vi.fn();
|
|
81
|
+
const { result } = renderHook(() => useEditorDialog(() => false, { onDialogOpenChange }));
|
|
82
|
+
act(() => result.current.setOpen(true));
|
|
83
|
+
expect(onDialogOpenChange).toHaveBeenCalledWith(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("options.forceShow", () => {
|
|
88
|
+
it("should open dialog when forceShow becomes true", () => {
|
|
89
|
+
const { result, rerender } = renderHook(({ forceShow }) => useEditorDialog(() => false, { forceShow }), {
|
|
90
|
+
initialProps: { forceShow: false },
|
|
91
|
+
});
|
|
92
|
+
expect(result.current.open).toBe(false);
|
|
93
|
+
rerender({ forceShow: true });
|
|
94
|
+
expect(result.current.open).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("options.onClose", () => {
|
|
99
|
+
it("should call onClose when dialog closes", () => {
|
|
100
|
+
const onClose = vi.fn();
|
|
101
|
+
const { result } = renderHook(() => useEditorDialog(() => false, { onClose }));
|
|
102
|
+
act(() => result.current.setOpen(true));
|
|
103
|
+
act(() => result.current.setOpen(false));
|
|
104
|
+
expect(onClose).toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should call onClose when discard is confirmed", () => {
|
|
108
|
+
const onClose = vi.fn();
|
|
109
|
+
const { result } = renderHook(() => useEditorDialog(() => true, { onClose }));
|
|
110
|
+
act(() => result.current.setOpen(true));
|
|
111
|
+
act(() => result.current.handleOpenChange(false));
|
|
112
|
+
act(() => result.current.discardDialogProps.onDiscard());
|
|
113
|
+
expect(onClose).toHaveBeenCalled();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("escape key handler", () => {
|
|
118
|
+
it("should trigger handleOpenChange(false) on Escape when open", () => {
|
|
119
|
+
const { result } = renderHook(() => useEditorDialog(() => true));
|
|
120
|
+
act(() => result.current.setOpen(true));
|
|
121
|
+
|
|
122
|
+
const event = new KeyboardEvent("keydown", {
|
|
123
|
+
key: "Escape",
|
|
124
|
+
bubbles: true,
|
|
125
|
+
cancelable: true,
|
|
126
|
+
});
|
|
127
|
+
act(() => document.dispatchEvent(event));
|
|
128
|
+
|
|
129
|
+
expect(result.current.discardDialogProps.open).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should not react to Escape when closed", () => {
|
|
133
|
+
const { result } = renderHook(() => useEditorDialog(() => true));
|
|
134
|
+
|
|
135
|
+
const event = new KeyboardEvent("keydown", {
|
|
136
|
+
key: "Escape",
|
|
137
|
+
bubbles: true,
|
|
138
|
+
cancelable: true,
|
|
139
|
+
});
|
|
140
|
+
act(() => document.dispatchEvent(event));
|
|
141
|
+
|
|
142
|
+
expect(result.current.discardDialogProps.open).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -2,7 +2,9 @@ export * from "./CommonAssociationForm";
|
|
|
2
2
|
export * from "./CommonDeleter";
|
|
3
3
|
export * from "./CommonEditorButtons";
|
|
4
4
|
export * from "./CommonEditorHeader";
|
|
5
|
+
export * from "./CommonEditorDiscardDialog";
|
|
5
6
|
export * from "./CommonEditorTrigger";
|
|
7
|
+
export * from "./useEditorDialog";
|
|
6
8
|
export * from "./DatePickerPopover";
|
|
7
9
|
export * from "./DateRangeSelector";
|
|
8
10
|
export * from "./FileUploader";
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
type UseEditorDialogOptions = {
|
|
6
|
+
dialogOpen?: boolean;
|
|
7
|
+
onDialogOpenChange?: (open: boolean) => void;
|
|
8
|
+
forceShow?: boolean;
|
|
9
|
+
onClose?: () => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type UseEditorDialogReturn = {
|
|
13
|
+
open: boolean;
|
|
14
|
+
setOpen: (open: boolean) => void;
|
|
15
|
+
handleOpenChange: (nextOpen: boolean) => void;
|
|
16
|
+
discardDialogProps: {
|
|
17
|
+
open: boolean;
|
|
18
|
+
onOpenChange: (open: boolean) => void;
|
|
19
|
+
onDiscard: () => void;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function useEditorDialog(isFormDirty: () => boolean, options?: UseEditorDialogOptions): UseEditorDialogReturn {
|
|
24
|
+
const [open, setOpen] = useState<boolean>(false);
|
|
25
|
+
const [showDiscardConfirm, setShowDiscardConfirm] = useState<boolean>(false);
|
|
26
|
+
|
|
27
|
+
// Sync open state from external dialogOpen prop
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (options?.dialogOpen !== undefined) {
|
|
30
|
+
setOpen(options.dialogOpen);
|
|
31
|
+
}
|
|
32
|
+
}, [options?.dialogOpen]);
|
|
33
|
+
|
|
34
|
+
// Notify parent when open state changes
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (typeof options?.onDialogOpenChange === "function") {
|
|
37
|
+
options.onDialogOpenChange(open);
|
|
38
|
+
}
|
|
39
|
+
}, [open, options?.onDialogOpenChange]);
|
|
40
|
+
|
|
41
|
+
// Force show
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (options?.forceShow) setOpen(true);
|
|
44
|
+
}, [options?.forceShow]);
|
|
45
|
+
|
|
46
|
+
// Call onClose when dialog closes
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!open) {
|
|
49
|
+
if (options?.onClose) options.onClose();
|
|
50
|
+
}
|
|
51
|
+
}, [open]);
|
|
52
|
+
|
|
53
|
+
const handleOpenChange = useCallback(
|
|
54
|
+
(nextOpen: boolean) => {
|
|
55
|
+
if (!nextOpen && isFormDirty()) {
|
|
56
|
+
setShowDiscardConfirm(true);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
setOpen(nextOpen);
|
|
60
|
+
},
|
|
61
|
+
[isFormDirty],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Escape key handler
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
67
|
+
if (event.key === "Escape" && open) {
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
event.stopPropagation();
|
|
70
|
+
handleOpenChange(false);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (open) {
|
|
75
|
+
document.addEventListener("keydown", handleKeyDown, true);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return () => {
|
|
79
|
+
document.removeEventListener("keydown", handleKeyDown, true);
|
|
80
|
+
};
|
|
81
|
+
}, [open, handleOpenChange]);
|
|
82
|
+
|
|
83
|
+
const discardDialogProps = {
|
|
84
|
+
open: showDiscardConfirm,
|
|
85
|
+
onOpenChange: setShowDiscardConfirm,
|
|
86
|
+
onDiscard: () => {
|
|
87
|
+
setShowDiscardConfirm(false);
|
|
88
|
+
setOpen(false);
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return { open, setOpen, handleOpenChange, discardDialogProps };
|
|
93
|
+
}
|
|
@@ -101,20 +101,19 @@ export const ContentListTable = memo(function ContentListTable(props: ContentLis
|
|
|
101
101
|
<div className="flex w-full items-center justify-between gap-x-2">
|
|
102
102
|
{/* <div className="w-full">{fullWidth ? `` : props.title}</div> */}
|
|
103
103
|
<div className="w-full">
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
)}
|
|
104
|
+
<div
|
|
105
|
+
className={cn(
|
|
106
|
+
"text-muted-foreground flex items-center gap-x-2 font-light whitespace-nowrap",
|
|
107
|
+
fullWidth ? `text-lg` : `text-sm`,
|
|
108
|
+
)}
|
|
109
|
+
>
|
|
110
|
+
{props.tableGeneratorType.icon && (
|
|
111
|
+
<props.tableGeneratorType.icon
|
|
112
|
+
className={cn(`text-primary`, fullWidth ? `h-6 w-6` : `h-4 w-4`)}
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
115
|
+
{props.title}
|
|
116
|
+
</div>
|
|
118
117
|
</div>
|
|
119
118
|
{(props.functions || props.filters || allowSearch) && (
|
|
120
119
|
<>
|