@kyro-cms/admin 0.3.2 → 0.3.4
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/EditorClient-XEUOVAAC.js +466 -0
- package/dist/EditorClient-XEUOVAAC.js.map +1 -0
- package/dist/EditorClient-YLCGVDXY.cjs +468 -0
- package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
- package/dist/chunk-7KPIUCGT.js +384 -0
- package/dist/chunk-7KPIUCGT.js.map +1 -0
- package/dist/chunk-GOACG6R7.cjs +473 -0
- package/dist/chunk-GOACG6R7.cjs.map +1 -0
- package/dist/index.cjs +14861 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +1661 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +563 -0
- package/dist/index.js +14784 -0
- package/dist/index.js.map +1 -0
- package/package.json +19 -19
- package/src/components/ActionBar.tsx +7 -43
- package/src/components/Admin.tsx +138 -277
- package/src/components/ApiKeysManager.tsx +428 -419
- package/src/components/AuditLogsPage.tsx +35 -39
- package/src/components/AuthBridge.tsx +51 -0
- package/src/components/AutoForm.tsx +495 -1230
- package/src/components/BrandingHub.tsx +18 -19
- package/src/components/BulkActionsBar.tsx +1 -1
- package/src/components/CreateView.tsx +22 -36
- package/src/components/Dashboard.tsx +60 -84
- package/src/components/DetailView.tsx +113 -91
- package/src/components/DeveloperCenter.tsx +200 -198
- package/src/components/FieldRenderer.tsx +206 -0
- package/src/components/GraphQLPlayground.tsx +340 -480
- package/src/components/ListView.tsx +828 -254
- package/src/components/LoginPage.tsx +3 -4
- package/src/components/MarketplaceManager.tsx +254 -0
- package/src/components/MediaGallery.tsx +856 -1192
- package/src/components/PluginsManager.tsx +277 -0
- package/src/components/RestPlayground.tsx +398 -560
- package/src/components/SessionsManager.tsx +211 -0
- package/src/components/Sidebar.astro +179 -151
- package/src/components/ThemeProvider.tsx +7 -161
- package/src/components/UserManagement.tsx +162 -146
- package/src/components/UserMenu.tsx +110 -0
- package/src/components/WebhookManager.tsx +305 -367
- package/src/components/blocks/AccordionBlock.tsx +4 -4
- package/src/components/blocks/ArrayBlock.tsx +3 -3
- package/src/components/blocks/BlockEditModal.tsx +8 -8
- package/src/components/blocks/BlockWrapper.tsx +61 -0
- package/src/components/blocks/ButtonBlock.tsx +4 -4
- package/src/components/blocks/ChildBlocksTree.tsx +23 -25
- package/src/components/blocks/CodeBlock.tsx +15 -15
- package/src/components/blocks/ColumnsBlock.tsx +6 -44
- package/src/components/blocks/DividerBlock.tsx +3 -3
- package/src/components/blocks/FileBlock.tsx +4 -4
- package/src/components/blocks/HeadingBlock.tsx +6 -38
- package/src/components/blocks/HeroBlock.tsx +4 -4
- package/src/components/blocks/ImageBlock.tsx +4 -4
- package/src/components/blocks/LinkBlock.tsx +4 -4
- package/src/components/blocks/ListBlock.tsx +3 -3
- package/src/components/blocks/ParagraphBlock.tsx +12 -42
- package/src/components/blocks/RelationshipBlock.tsx +4 -4
- package/src/components/blocks/RichTextBlock.tsx +4 -4
- package/src/components/blocks/VStackBlock.tsx +5 -37
- package/src/components/blocks/VideoBlock.tsx +4 -4
- package/src/components/blocks/types.ts +11 -0
- package/src/components/fields/AccordionField.tsx +1 -1
- package/src/components/fields/ArrayField.tsx +2 -2
- package/src/components/fields/ArrayLayout.tsx +93 -0
- package/src/components/fields/BlocksField.tsx +122 -111
- package/src/components/fields/ButtonField.tsx +1 -1
- package/src/components/fields/CheckboxField.tsx +14 -15
- package/src/components/fields/ChildrenField.tsx +2 -2
- package/src/components/fields/CodeField.tsx +3 -3
- package/src/components/fields/ColumnsField.tsx +2 -2
- package/src/components/fields/DateField.tsx +13 -26
- package/src/components/fields/EditorClient.tsx +26 -28
- package/src/components/fields/FieldLayout.tsx +52 -0
- package/src/components/fields/GroupLayout.tsx +35 -0
- package/src/components/fields/JSONField.tsx +7 -7
- package/src/components/fields/LinkField.tsx +1 -1
- package/src/components/fields/MarkdownField.tsx +1 -1
- package/src/components/fields/NumberField.tsx +13 -26
- package/src/components/fields/PortableTextField.tsx +4 -4
- package/src/components/fields/PortableTextRenderer.tsx +1 -1
- package/src/components/fields/RelationshipBlockField.tsx +31 -23
- package/src/components/fields/RelationshipField.tsx +14 -14
- package/src/components/fields/SelectField.tsx +17 -26
- package/src/components/fields/TabsLayout.tsx +69 -0
- package/src/components/fields/TextField.tsx +85 -38
- package/src/components/fields/UploadField.tsx +71 -41
- package/src/components/fields/VideoField.tsx +1 -1
- package/src/components/fields/extensions/blockComponents.tsx +2 -2
- package/src/components/fields/extensions/blocksStore.ts +207 -193
- package/src/components/fields/types.ts +22 -0
- package/src/components/layout/Layout.tsx +1 -1
- package/src/components/ui/ActionMenu.tsx +63 -0
- package/src/components/ui/Badge.tsx +59 -5
- package/src/components/ui/BlockDrawer.tsx +4 -5
- package/src/components/ui/CommandPalette.tsx +58 -36
- package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
- package/src/components/ui/Dropdown.tsx +18 -16
- package/src/components/ui/EmptyState.tsx +25 -0
- package/src/components/ui/GlobalModal.tsx +49 -0
- package/src/components/ui/IconButton.tsx +44 -0
- package/src/components/ui/Modal.tsx +19 -20
- package/src/components/ui/PageHeader.tsx +158 -0
- package/src/components/ui/Pagination.tsx +61 -0
- package/src/components/ui/PromptModal.tsx +1 -1
- package/src/components/ui/SearchInput.tsx +57 -0
- package/src/components/ui/SeoPreview.tsx +31 -0
- package/src/components/ui/SessionModal.tsx +0 -0
- package/src/components/ui/SlidePanel.tsx +2 -0
- package/src/components/ui/Toast.tsx +65 -122
- package/src/components/ui/Toaster.tsx +18 -0
- package/src/components/ui/icons.tsx +112 -0
- package/src/components/users/UserDetail.tsx +290 -0
- package/src/components/users/UserForm.tsx +242 -0
- package/src/components/users/UsersList.tsx +338 -0
- package/src/env.d.ts +13 -13
- package/src/fields/index.ts +2 -1
- package/src/global.d.ts +7 -0
- package/src/hooks/data.ts +2 -9
- package/src/hooks/useAsyncData.ts +36 -0
- package/src/hooks/useAutoFormState.ts +527 -0
- package/src/hooks/useSelection.ts +49 -0
- package/src/hooks/useSession.ts +0 -0
- package/src/index.ts +11 -1
- package/src/integration.ts +86 -11
- package/src/kyro-cms.d.ts +209 -0
- package/src/layouts/AdminLayout.astro +128 -11
- package/src/layouts/AuthLayout.astro +21 -5
- package/src/lib/api.ts +175 -55
- package/src/lib/autoform-store.ts +435 -0
- package/src/lib/config.ts +82 -34
- package/src/lib/createRegistry.ts +29 -0
- package/src/lib/default-kyro-config.ts +4 -0
- package/src/lib/globals.ts +50 -0
- package/src/lib/media-utils.ts +18 -0
- package/src/lib/object-utils.ts +77 -0
- package/src/lib/paths.ts +61 -0
- package/src/lib/stores/index.ts +370 -0
- package/src/lib/types.ts +43 -0
- package/src/lib/useResourceManager.ts +105 -0
- package/src/pages/403.astro +67 -0
- package/src/pages/[collection]/[id].astro +14 -180
- package/src/pages/[collection]/index.astro +11 -6
- package/src/pages/api-explorer.astro +173 -0
- package/src/pages/audit/index.astro +2 -0
- package/src/pages/auth/login.astro +122 -0
- package/src/pages/auth/register.astro +167 -0
- package/src/pages/graphql-explorer.astro +59 -0
- package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
- package/src/pages/index.astro +577 -0
- package/src/pages/index_ALT.astro +3 -0
- package/src/pages/keys.astro +11 -0
- package/src/pages/marketplace.astro +11 -0
- package/src/pages/media.astro +3 -0
- package/src/pages/plugins.astro +8 -0
- package/src/pages/preview/[collection]/[id].astro +188 -123
- package/src/pages/rest-playground.astro +62 -0
- package/src/pages/roles/index.astro +183 -76
- package/src/pages/sessions.astro +8 -0
- package/src/pages/settings/[slug].astro +92 -114
- package/src/pages/settings/index.astro +5 -3
- package/src/pages/users/[id].astro +25 -154
- package/src/pages/users/index.astro +19 -130
- package/src/pages/users/new.astro +9 -86
- package/src/pages/webhooks.astro +11 -0
- package/src/routes.ts +80 -0
- package/src/styles/main.css +119 -79
- package/src/theme/tokens.ts +1 -0
- package/src/vite-env.d.ts +14 -0
- package/src/collections/auth/index.ts +0 -155
- package/src/collections/portfolio/index.ts +0 -343
- package/src/components/ApiExplorer.tsx +0 -325
- package/src/components/EnhancedListView.tsx +0 -889
- package/src/components/GraphQLExplorer.tsx +0 -675
- package/src/components/Icons.tsx +0 -23
- package/src/components/StatusBadge.tsx +0 -76
- package/src/lib/MediaService.ts +0 -541
- package/src/lib/auth/sqlite-adapter.ts +0 -319
- package/src/lib/dataStore.ts +0 -226
- package/src/lib/db/adapter.ts +0 -54
- package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
- package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
- package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
- package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
- package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
- package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
- package/src/lib/db/index.ts +0 -449
- package/src/lib/db/mongodb-adapter.ts +0 -207
- package/src/lib/db/mongodb-auth-adapter.ts +0 -305
- package/src/lib/db/schema/mysql-auth.ts +0 -113
- package/src/lib/db/schema/mysql-content.ts +0 -20
- package/src/lib/db/schema/postgres-auth.ts +0 -116
- package/src/lib/db/schema/postgres-content.ts +0 -35
- package/src/lib/db/schema/postgres-media.ts +0 -52
- package/src/lib/db/schema/postgres-settings.ts +0 -11
- package/src/lib/db/schema/sqlite-auth.ts +0 -112
- package/src/lib/db/schema/sqlite-content.ts +0 -20
- package/src/lib/db/version-adapter.ts +0 -248
- package/src/lib/graphql/index.ts +0 -1
- package/src/lib/graphql/schema.ts +0 -443
- package/src/lib/rate-limit.ts +0 -267
- package/src/lib/storage.ts +0 -374
- package/src/lib/store.ts +0 -85
- package/src/middleware.ts +0 -177
- package/src/pages/admin/api-explorer.astro +0 -98
- package/src/pages/admin/graphql-explorer.astro +0 -40
- package/src/pages/admin/index.astro +0 -286
- package/src/pages/admin/keys.astro +0 -8
- package/src/pages/admin/rest-playground.astro +0 -44
- package/src/pages/admin/webhooks.astro +0 -8
- package/src/pages/api/[collection]/[id]/publish.ts +0 -52
- package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
- package/src/pages/api/[collection]/[id]/versions.ts +0 -66
- package/src/pages/api/[collection]/[id].ts +0 -213
- package/src/pages/api/[collection]/index.ts +0 -209
- package/src/pages/api/auth/[id].ts +0 -121
- package/src/pages/api/auth/audit-logs.ts +0 -57
- package/src/pages/api/auth/login.ts +0 -211
- package/src/pages/api/auth/logout.ts +0 -66
- package/src/pages/api/auth/me.ts +0 -36
- package/src/pages/api/auth/refresh.ts +0 -119
- package/src/pages/api/auth/register.ts +0 -188
- package/src/pages/api/auth/users.ts +0 -97
- package/src/pages/api/collections.ts +0 -59
- package/src/pages/api/globals/[slug].ts +0 -42
- package/src/pages/api/graphql.ts +0 -90
- package/src/pages/api/health.ts +0 -426
- package/src/pages/api/keys/[id].ts +0 -26
- package/src/pages/api/keys/index.ts +0 -75
- package/src/pages/api/media/[id].ts +0 -309
- package/src/pages/api/media/folders.ts +0 -609
- package/src/pages/api/media/index.ts +0 -146
- package/src/pages/api/media/resize.ts +0 -267
- package/src/pages/api/search.ts +0 -82
- package/src/pages/api/slug-availability.ts +0 -70
- package/src/pages/api/storage-config.ts +0 -20
- package/src/pages/api/storage-status.ts +0 -206
- package/src/pages/api/upload.ts +0 -334
- package/src/pages/api/webhooks/index.ts +0 -71
- package/src/pages/login.astro +0 -82
- package/src/pages/register.astro +0 -102
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createStore, type StoreApi } from "zustand/vanilla";
|
|
2
|
+
import { useStore } from "zustand";
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
2
4
|
import type { BlockData } from "@kyro-cms/core/client";
|
|
3
5
|
|
|
4
6
|
export interface BlocksStore {
|
|
@@ -12,10 +14,130 @@ export interface BlocksStore {
|
|
|
12
14
|
setOnBlocksChange: (cb: () => void) => void;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
export type BlocksStoreApi = StoreApi<BlocksStore>;
|
|
18
|
+
|
|
19
|
+
export const BlocksContext = createContext<BlocksStoreApi | null>(null);
|
|
20
|
+
|
|
21
|
+
export function createBlocksStore(): BlocksStoreApi {
|
|
22
|
+
return createStore<BlocksStore>((set, get) => ({
|
|
23
|
+
blocks: [],
|
|
24
|
+
setBlocks: (blocks) => {
|
|
25
|
+
const ensuredBlocks = ensureIds(blocks || []);
|
|
26
|
+
set({ blocks: ensuredBlocks });
|
|
27
|
+
},
|
|
28
|
+
onBlocksChange: null,
|
|
29
|
+
setOnBlocksChange: (cb) => set({ onBlocksChange: cb }),
|
|
30
|
+
addBlock: (type, index) => {
|
|
31
|
+
const newBlock = createNewBlock(type);
|
|
32
|
+
const { blocks } = get();
|
|
33
|
+
const newBlocks = [...blocks];
|
|
34
|
+
if (index !== undefined) {
|
|
35
|
+
newBlocks.splice(index, 0, newBlock);
|
|
36
|
+
} else {
|
|
37
|
+
newBlocks.push(newBlock);
|
|
38
|
+
}
|
|
39
|
+
set({ blocks: newBlocks });
|
|
40
|
+
const { onBlocksChange } = get();
|
|
41
|
+
if (onBlocksChange) onBlocksChange();
|
|
42
|
+
},
|
|
43
|
+
updateBlock: (id, data) => {
|
|
44
|
+
const { blocks } = get();
|
|
45
|
+
|
|
46
|
+
const newBlocks = traverseBlocks(blocks, (blocksList) => {
|
|
47
|
+
const index = blocksList.findIndex(b => b.id === id);
|
|
48
|
+
if (index !== -1) {
|
|
49
|
+
const newBlocksList = [...blocksList];
|
|
50
|
+
newBlocksList[index] = { ...newBlocksList[index], ...data };
|
|
51
|
+
return { newList: newBlocksList, found: true };
|
|
52
|
+
}
|
|
53
|
+
return { newList: blocksList, found: false };
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (newBlocks !== blocks) {
|
|
57
|
+
set({ blocks: newBlocks });
|
|
58
|
+
const { onBlocksChange } = get();
|
|
59
|
+
if (onBlocksChange) onBlocksChange();
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
removeBlock: (id) => {
|
|
63
|
+
if (!id) return;
|
|
64
|
+
const { blocks } = get();
|
|
65
|
+
|
|
66
|
+
const newBlocks = traverseBlocks(blocks, (blocksList) => {
|
|
67
|
+
const filtered = blocksList.filter(b => b.id !== id);
|
|
68
|
+
if (filtered.length !== blocksList.length) {
|
|
69
|
+
return { newList: filtered, found: true };
|
|
70
|
+
}
|
|
71
|
+
return { newList: blocksList, found: false };
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (newBlocks !== blocks) {
|
|
75
|
+
set({ blocks: newBlocks });
|
|
76
|
+
const { onBlocksChange } = get();
|
|
77
|
+
if (onBlocksChange) onBlocksChange();
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
moveBlock: (id, direction) => {
|
|
81
|
+
const { blocks } = get();
|
|
82
|
+
|
|
83
|
+
const newBlocks = traverseBlocks(blocks, (blocksList) => {
|
|
84
|
+
const index = blocksList.findIndex(b => b.id === id);
|
|
85
|
+
if (index !== -1) {
|
|
86
|
+
const targetIndex = direction === "up" ? index - 1 : index + 1;
|
|
87
|
+
if (targetIndex >= 0 && targetIndex < blocksList.length) {
|
|
88
|
+
const newBlocksList = [...blocksList];
|
|
89
|
+
[newBlocksList[index], newBlocksList[targetIndex]] = [
|
|
90
|
+
newBlocksList[targetIndex],
|
|
91
|
+
newBlocksList[index],
|
|
92
|
+
];
|
|
93
|
+
return { newList: newBlocksList, found: true };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { newList: blocksList, found: false };
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (newBlocks !== blocks) {
|
|
100
|
+
set({ blocks: newBlocks });
|
|
101
|
+
const { onBlocksChange } = get();
|
|
102
|
+
if (onBlocksChange) onBlocksChange();
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Recursively ensures all blocks and nested children have unique IDs
|
|
110
|
+
*/
|
|
111
|
+
export function ensureIds(blocks: BlockData[]): BlockData[] {
|
|
112
|
+
if (!Array.isArray(blocks)) return [];
|
|
113
|
+
|
|
114
|
+
return blocks.map((block) => {
|
|
115
|
+
const updatedBlock = {
|
|
116
|
+
...block,
|
|
117
|
+
id: block.id || Math.random().toString(36).substr(2, 9),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (updatedBlock.children && Array.isArray(updatedBlock.children)) {
|
|
121
|
+
updatedBlock.children = ensureIds(updatedBlock.children);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (updatedBlock.data?.columnData && Array.isArray(updatedBlock.data.columnData)) {
|
|
125
|
+
updatedBlock.data = {
|
|
126
|
+
...updatedBlock.data,
|
|
127
|
+
columnData: updatedBlock.data.columnData.map((col: Record<string, unknown>) => ({
|
|
128
|
+
...col,
|
|
129
|
+
children: col.children ? ensureIds(col.children) : col.children,
|
|
130
|
+
})),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return updatedBlock;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Create new block helper (pure function, no store needed)
|
|
16
139
|
export function createNewBlock(type: string): BlockData {
|
|
17
140
|
const defaultData = getDefaultData(type);
|
|
18
|
-
// Extract options and children from defaultData if present
|
|
19
141
|
const { options, children, ...data } = defaultData;
|
|
20
142
|
return {
|
|
21
143
|
id: Math.random().toString(36).substr(2, 9),
|
|
@@ -27,8 +149,8 @@ export function createNewBlock(type: string): BlockData {
|
|
|
27
149
|
};
|
|
28
150
|
}
|
|
29
151
|
|
|
30
|
-
function getDefaultData(type: string): Record<string,
|
|
31
|
-
const defaults: Record<string,
|
|
152
|
+
function getDefaultData(type: string): Record<string, unknown> {
|
|
153
|
+
const defaults: Record<string, unknown> = {
|
|
32
154
|
heading: { level: 1, text: "" },
|
|
33
155
|
paragraph: { text: "" },
|
|
34
156
|
divider: {},
|
|
@@ -41,7 +163,7 @@ function getDefaultData(type: string): Record<string, any> {
|
|
|
41
163
|
table: { rows: 3, columns: 3, content: "" },
|
|
42
164
|
quote: { text: "", author: "" },
|
|
43
165
|
file: { filename: "", url: "" },
|
|
44
|
-
|
|
166
|
+
columns: { columns: 2, direction: "horizontal" },
|
|
45
167
|
vstack: { direction: "vertical", gap: "md" },
|
|
46
168
|
container: {
|
|
47
169
|
options: {
|
|
@@ -62,185 +184,19 @@ function getDefaultData(type: string): Record<string, any> {
|
|
|
62
184
|
return defaults[type] || {};
|
|
63
185
|
}
|
|
64
186
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const newBlocks = [...blocks];
|
|
74
|
-
if (index !== undefined) {
|
|
75
|
-
newBlocks.splice(index, 0, newBlock);
|
|
76
|
-
} else {
|
|
77
|
-
newBlocks.push(newBlock);
|
|
78
|
-
}
|
|
79
|
-
set({ blocks: newBlocks });
|
|
80
|
-
const { onBlocksChange } = get();
|
|
81
|
-
if (onBlocksChange) onBlocksChange();
|
|
82
|
-
},
|
|
83
|
-
updateBlock: (id, data) => {
|
|
84
|
-
const { blocks } = get();
|
|
85
|
-
|
|
86
|
-
const updateRecursive = (blocksList: BlockData[]): BlockData[] => {
|
|
87
|
-
let changed = false;
|
|
88
|
-
const newList = blocksList.map(b => {
|
|
89
|
-
if (b.id === id) {
|
|
90
|
-
changed = true;
|
|
91
|
-
return { ...b, ...data };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
let newB = b;
|
|
95
|
-
if (b.children && b.children.length > 0) {
|
|
96
|
-
const newChildren = updateRecursive(b.children);
|
|
97
|
-
if (newChildren !== b.children) {
|
|
98
|
-
newB = { ...newB, children: newChildren };
|
|
99
|
-
changed = true;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (b.data?.columnData) {
|
|
104
|
-
const newColumnData = b.data.columnData.map((col: any) => {
|
|
105
|
-
if (col.children && col.children.length > 0) {
|
|
106
|
-
const newChildren = updateRecursive(col.children);
|
|
107
|
-
if (newChildren !== col.children) {
|
|
108
|
-
return { ...col, children: newChildren };
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return col;
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
if (newColumnData.some((col: any, i: number) => col !== b.data.columnData[i])) {
|
|
115
|
-
newB = { ...newB, data: { ...newB.data, columnData: newColumnData } };
|
|
116
|
-
changed = true;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return newB;
|
|
121
|
-
});
|
|
122
|
-
return changed ? newList : blocksList;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const newBlocks = updateRecursive(blocks);
|
|
126
|
-
if (newBlocks !== blocks) {
|
|
127
|
-
set({ blocks: newBlocks });
|
|
128
|
-
const { onBlocksChange } = get();
|
|
129
|
-
if (onBlocksChange) onBlocksChange();
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
removeBlock: (id) => {
|
|
133
|
-
const { blocks } = get();
|
|
134
|
-
|
|
135
|
-
const removeRecursive = (blocksList: BlockData[]): BlockData[] => {
|
|
136
|
-
const filtered = blocksList.filter(b => b.id !== id);
|
|
137
|
-
if (filtered.length !== blocksList.length) {
|
|
138
|
-
return filtered; // found and removed at this level
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
let changed = false;
|
|
142
|
-
const newList = blocksList.map(b => {
|
|
143
|
-
let newB = b;
|
|
144
|
-
if (b.children && b.children.length > 0) {
|
|
145
|
-
const newChildren = removeRecursive(b.children);
|
|
146
|
-
if (newChildren !== b.children) {
|
|
147
|
-
newB = { ...newB, children: newChildren };
|
|
148
|
-
changed = true;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (b.data?.columnData) {
|
|
153
|
-
const newColumnData = b.data.columnData.map((col: any) => {
|
|
154
|
-
if (col.children && col.children.length > 0) {
|
|
155
|
-
const newChildren = removeRecursive(col.children);
|
|
156
|
-
if (newChildren !== col.children) {
|
|
157
|
-
return { ...col, children: newChildren };
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return col;
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
if (newColumnData.some((col: any, i: number) => col !== b.data.columnData[i])) {
|
|
164
|
-
newB = { ...newB, data: { ...newB.data, columnData: newColumnData } };
|
|
165
|
-
changed = true;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return newB;
|
|
170
|
-
});
|
|
171
|
-
return changed ? newList : blocksList;
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
const newBlocks = removeRecursive(blocks);
|
|
175
|
-
if (newBlocks !== blocks) {
|
|
176
|
-
set({ blocks: newBlocks });
|
|
177
|
-
const { onBlocksChange } = get();
|
|
178
|
-
if (onBlocksChange) onBlocksChange();
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
moveBlock: (id, direction) => {
|
|
182
|
-
const { blocks } = get();
|
|
183
|
-
|
|
184
|
-
const moveRecursive = (blocksList: BlockData[]): BlockData[] => {
|
|
185
|
-
const index = blocksList.findIndex(b => b.id === id);
|
|
186
|
-
if (index !== -1) {
|
|
187
|
-
const targetIndex = direction === "up" ? index - 1 : index + 1;
|
|
188
|
-
if (targetIndex >= 0 && targetIndex < blocksList.length) {
|
|
189
|
-
const newBlocksList = [...blocksList];
|
|
190
|
-
[newBlocksList[index], newBlocksList[targetIndex]] = [
|
|
191
|
-
newBlocksList[targetIndex],
|
|
192
|
-
newBlocksList[index],
|
|
193
|
-
];
|
|
194
|
-
return newBlocksList;
|
|
195
|
-
}
|
|
196
|
-
return blocksList;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
let changed = false;
|
|
200
|
-
const newList = blocksList.map(b => {
|
|
201
|
-
let newB = b;
|
|
202
|
-
if (b.children && b.children.length > 0) {
|
|
203
|
-
const newChildren = moveRecursive(b.children);
|
|
204
|
-
if (newChildren !== b.children) {
|
|
205
|
-
newB = { ...newB, children: newChildren };
|
|
206
|
-
changed = true;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (b.data?.columnData) {
|
|
211
|
-
const newColumnData = b.data.columnData.map((col: any) => {
|
|
212
|
-
if (col.children && col.children.length > 0) {
|
|
213
|
-
const newChildren = moveRecursive(col.children);
|
|
214
|
-
if (newChildren !== col.children) {
|
|
215
|
-
return { ...col, children: newChildren };
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
return col;
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
if (newColumnData.some((col: any, i: number) => col !== b.data.columnData[i])) {
|
|
222
|
-
newB = { ...newB, data: { ...newB.data, columnData: newColumnData } };
|
|
223
|
-
changed = true;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return newB;
|
|
228
|
-
});
|
|
229
|
-
return changed ? newList : blocksList;
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const newBlocks = moveRecursive(blocks);
|
|
233
|
-
if (newBlocks !== blocks) {
|
|
234
|
-
set({ blocks: newBlocks });
|
|
235
|
-
const { onBlocksChange } = get();
|
|
236
|
-
if (onBlocksChange) onBlocksChange();
|
|
237
|
-
}
|
|
238
|
-
},
|
|
239
|
-
}));
|
|
187
|
+
// React hooks that read from context
|
|
188
|
+
export function useBlocksStore(): BlocksStore {
|
|
189
|
+
const store = useContext(BlocksContext);
|
|
190
|
+
if (!store) {
|
|
191
|
+
throw new Error("useBlocksStore must be used within a BlocksContext.Provider");
|
|
192
|
+
}
|
|
193
|
+
return useStore(store);
|
|
194
|
+
}
|
|
240
195
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
196
|
+
export function useBlockById(id: string): BlockData | undefined {
|
|
197
|
+
const store = useContext(BlocksContext);
|
|
198
|
+
if (!store) return undefined;
|
|
199
|
+
return useStore(store, (state) => {
|
|
244
200
|
const findRecursive = (blocksList: BlockData[]): BlockData | undefined => {
|
|
245
201
|
for (const b of blocksList) {
|
|
246
202
|
if (b.id === id) return b;
|
|
@@ -250,7 +206,7 @@ export const useBlockById = (id: string) =>
|
|
|
250
206
|
}
|
|
251
207
|
if (b.data?.columnData) {
|
|
252
208
|
for (const col of b.data.columnData) {
|
|
253
|
-
if (col.children && col.children.length > 0) {
|
|
209
|
+
if (col && col.children && col.children.length > 0) {
|
|
254
210
|
const found = findRecursive(col.children);
|
|
255
211
|
if (found) return found;
|
|
256
212
|
}
|
|
@@ -261,13 +217,71 @@ export const useBlockById = (id: string) =>
|
|
|
261
217
|
};
|
|
262
218
|
return findRecursive(state.blocks);
|
|
263
219
|
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function useBlockCount(): number {
|
|
223
|
+
const store = useContext(BlocksContext);
|
|
224
|
+
if (!store) return 0;
|
|
225
|
+
return useStore(store, (state) => state.blocks.length);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function useBlockActions() {
|
|
229
|
+
const store = useContext(BlocksContext);
|
|
230
|
+
if (!store) {
|
|
231
|
+
throw new Error("useBlockActions must be used within a BlocksContext.Provider");
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
updateBlock: (id: string, data: Partial<BlockData>) => store.getState().updateBlock(id, data),
|
|
235
|
+
removeBlock: (id: string) => store.getState().removeBlock(id),
|
|
236
|
+
moveBlock: (id: string, direction: "up" | "down") => store.getState().moveBlock(id, direction),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
264
239
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Generic tree traversal helper for blocks
|
|
242
|
+
*/
|
|
243
|
+
export function traverseBlocks(
|
|
244
|
+
blocks: BlockData[],
|
|
245
|
+
action: (blocksList: BlockData[]) => { newList: BlockData[]; found: boolean }
|
|
246
|
+
): BlockData[] {
|
|
247
|
+
const { newList, found } = action(blocks);
|
|
248
|
+
if (found) return newList;
|
|
268
249
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
250
|
+
let overallChanged = false;
|
|
251
|
+
const deepUpdatedList = blocks.map((block) => {
|
|
252
|
+
let updatedBlock = { ...block };
|
|
253
|
+
let blockChanged = false;
|
|
254
|
+
|
|
255
|
+
// Handle children
|
|
256
|
+
if (block.children && block.children.length > 0) {
|
|
257
|
+
const updatedChildren = traverseBlocks(block.children, action);
|
|
258
|
+
if (updatedChildren !== block.children) {
|
|
259
|
+
updatedBlock.children = updatedChildren;
|
|
260
|
+
blockChanged = true;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Handle columnData
|
|
265
|
+
if (block.data?.columnData && Array.isArray(block.data.columnData)) {
|
|
266
|
+
const updatedColumnData = block.data.columnData.map((col: Record<string, unknown>) => {
|
|
267
|
+
if (col.children && col.children.length > 0) {
|
|
268
|
+
const updatedColChildren = traverseBlocks(col.children, action);
|
|
269
|
+
if (updatedColChildren !== col.children) {
|
|
270
|
+
return { ...col, children: updatedColChildren };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return col;
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
if (updatedColumnData.some((col: Record<string, unknown>, i: number) => col !== block.data.columnData[i])) {
|
|
277
|
+
updatedBlock.data = { ...updatedBlock.data, columnData: updatedColumnData };
|
|
278
|
+
blockChanged = true;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (blockChanged) overallChanged = true;
|
|
283
|
+
return blockChanged ? updatedBlock : block;
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
return overallChanged ? deepUpdatedList : blocks;
|
|
287
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { Field } from "@kyro-cms/core/client";
|
|
3
|
+
|
|
4
|
+
export interface FieldComponentProps<TField extends Field = Field> {
|
|
5
|
+
field: TField;
|
|
6
|
+
value?: unknown;
|
|
7
|
+
onChange?: (value: unknown) => void;
|
|
8
|
+
error?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Compactable {
|
|
13
|
+
compact?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type FieldChangeHandler = (field: string, value: unknown) => void;
|
|
17
|
+
|
|
18
|
+
export type RenderFieldFn = (
|
|
19
|
+
field: Field,
|
|
20
|
+
parentData: Record<string, unknown>,
|
|
21
|
+
onChange: (value: unknown) => void,
|
|
22
|
+
) => ReactNode;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useState, useRef, useEffect } from "react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { IconButton } from "./IconButton";
|
|
4
|
+
import { IconMoreVertical } from "./icons";
|
|
5
|
+
|
|
6
|
+
export interface ActionMenuItem {
|
|
7
|
+
label: string;
|
|
8
|
+
icon?: ReactNode;
|
|
9
|
+
onClick: () => void;
|
|
10
|
+
danger?: boolean;
|
|
11
|
+
divider?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ActionMenuProps {
|
|
15
|
+
items: ActionMenuItem[];
|
|
16
|
+
label?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ActionMenu({ items, label = "Actions" }: ActionMenuProps) {
|
|
20
|
+
const [open, setOpen] = useState(false);
|
|
21
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const handleClickOutside = (e: MouseEvent) => {
|
|
25
|
+
if (ref.current && !ref.current.contains(e.target as Node)) {
|
|
26
|
+
setOpen(false);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
30
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div ref={ref} className="relative">
|
|
35
|
+
<IconButton
|
|
36
|
+
icon={<IconMoreVertical className="w-4 h-4" />}
|
|
37
|
+
label={label}
|
|
38
|
+
variant="ghost"
|
|
39
|
+
size="sm"
|
|
40
|
+
onClick={() => setOpen(!open)}
|
|
41
|
+
/>
|
|
42
|
+
{open && (
|
|
43
|
+
<div className="absolute right-0 z-50 min-w-[160px] bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-lg shadow-lg py-1">
|
|
44
|
+
{items.map((item, i) => (
|
|
45
|
+
<div key={i}>
|
|
46
|
+
{item.divider && <div className="border-t border-[var(--kyro-border)] my-1" />}
|
|
47
|
+
<button
|
|
48
|
+
className={`w-full flex items-center gap-2 px-3 py-2 text-xs font-bold transition-colors ${item.danger
|
|
49
|
+
? "text-red-500 hover:bg-red-50"
|
|
50
|
+
: "text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"
|
|
51
|
+
}`}
|
|
52
|
+
onClick={() => { item.onClick(); setOpen(false); }}
|
|
53
|
+
>
|
|
54
|
+
{item.icon && <span className="w-4 h-4">{item.icon}</span>}
|
|
55
|
+
{item.label}
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -1,19 +1,73 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
|
|
3
|
+
export type BadgeVariant =
|
|
4
|
+
| "default"
|
|
5
|
+
| "success"
|
|
6
|
+
| "warning"
|
|
7
|
+
| "danger"
|
|
8
|
+
| "info"
|
|
9
|
+
| "outline"
|
|
10
|
+
| "draft"
|
|
11
|
+
| "published"
|
|
12
|
+
| "scheduled"
|
|
13
|
+
| "archived"
|
|
14
|
+
| "active"
|
|
15
|
+
| "inactive"
|
|
16
|
+
| "pending"
|
|
17
|
+
| "completed"
|
|
18
|
+
| "cancelled";
|
|
19
|
+
|
|
3
20
|
interface BadgeProps {
|
|
4
|
-
variant?:
|
|
21
|
+
variant?: BadgeVariant;
|
|
22
|
+
status?: BadgeVariant; // Alias for variant when used for statuses
|
|
5
23
|
className?: string;
|
|
6
|
-
children
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
dot?: boolean;
|
|
7
26
|
}
|
|
8
27
|
|
|
28
|
+
const statusConfig: Record<string, { class: string; label?: string }> = {
|
|
29
|
+
draft: { class: "bg-gray-100 text-gray-600", label: "Draft" },
|
|
30
|
+
published: { class: "bg-green-100 text-green-700", label: "Published" },
|
|
31
|
+
scheduled: { class: "bg-blue-100 text-blue-700", label: "Scheduled" },
|
|
32
|
+
archived: { class: "bg-yellow-100 text-yellow-700", label: "Archived" },
|
|
33
|
+
active: { class: "bg-green-100 text-green-700", label: "Active" },
|
|
34
|
+
inactive: { class: "bg-gray-100 text-gray-600", label: "Inactive" },
|
|
35
|
+
pending: { class: "bg-yellow-100 text-yellow-700", label: "Pending" },
|
|
36
|
+
completed: { class: "bg-green-100 text-green-700", label: "Completed" },
|
|
37
|
+
cancelled: { class: "bg-red-100 text-red-700", label: "Cancelled" },
|
|
38
|
+
};
|
|
39
|
+
|
|
9
40
|
export function Badge({
|
|
10
|
-
variant
|
|
41
|
+
variant,
|
|
42
|
+
status,
|
|
11
43
|
className = "",
|
|
12
44
|
children,
|
|
45
|
+
dot = false,
|
|
13
46
|
}: BadgeProps) {
|
|
47
|
+
const activeVariant = variant || status || "default";
|
|
48
|
+
const config = statusConfig[activeVariant];
|
|
49
|
+
|
|
50
|
+
const variantClass = config
|
|
51
|
+
? config.class
|
|
52
|
+
: `kyro-badge-${activeVariant}`;
|
|
53
|
+
|
|
14
54
|
return (
|
|
15
|
-
<span className={`kyro-badge
|
|
16
|
-
{
|
|
55
|
+
<span className={`kyro-badge ${variantClass} ${className}`}>
|
|
56
|
+
{dot && (
|
|
57
|
+
<span className="w-1.5 h-1.5 rounded-full bg-current mr-1.5 opacity-60" />
|
|
58
|
+
)}
|
|
59
|
+
{children || config?.label || activeVariant}
|
|
17
60
|
</span>
|
|
18
61
|
);
|
|
19
62
|
}
|
|
63
|
+
|
|
64
|
+
export function CountBadge({ count, max = 99 }: { count: number; max?: number }) {
|
|
65
|
+
if (count === 0) return null;
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<span className="inline-flex items-center justify-center min-w-[20px] h-5 px-1.5 text-xs font-medium bg-gray-200 text-gray-700 rounded-full">
|
|
69
|
+
{count > max ? `${max}+` : count}
|
|
70
|
+
</span>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
@@ -33,7 +33,7 @@ export function DraggableBlockType({
|
|
|
33
33
|
onSelect,
|
|
34
34
|
children,
|
|
35
35
|
}: {
|
|
36
|
-
block: { type: string; label: string; icon:
|
|
36
|
+
block: { type: string; label: string; icon: React.ReactNode; description: string };
|
|
37
37
|
onSelect: (type: string) => void;
|
|
38
38
|
children?: ReactNode;
|
|
39
39
|
}) {
|
|
@@ -48,9 +48,8 @@ export function DraggableBlockType({
|
|
|
48
48
|
{...listeners}
|
|
49
49
|
{...attributes}
|
|
50
50
|
onClick={() => onSelect(block.type)}
|
|
51
|
-
className={`flex flex-col items-center text-center gap-1 p-2 rounded-md border border-[var(--kyro-border)] hover:border-[var(--kyro-primary)]/60 hover:bg-[var(--kyro-surface-accent)]/30 transition-all cursor-pointer group ${
|
|
52
|
-
|
|
53
|
-
}`}
|
|
51
|
+
className={`flex flex-col items-center text-center gap-1 p-2 rounded-md border border-[var(--kyro-border)] hover:border-[var(--kyro-primary)]/60 hover:bg-[var(--kyro-surface-accent)]/30 transition-all cursor-pointer group ${isDragging ? "opacity-50 border-[var(--kyro-primary)]" : ""
|
|
52
|
+
}`}
|
|
54
53
|
style={{ opacity: isDragging ? 0.5 : 1 }}
|
|
55
54
|
>
|
|
56
55
|
<div className="w-6 h-6 flex items-center justify-center rounded group-hover:bg-[var(--kyro-primary)]/10 group-hover:text-[var(--kyro-primary)] transition-all duration-300">
|
|
@@ -61,7 +60,7 @@ export function DraggableBlockType({
|
|
|
61
60
|
)}
|
|
62
61
|
</div>
|
|
63
62
|
<div className="flex-1 min-w-0">
|
|
64
|
-
<div className="text-xs font-medium
|
|
63
|
+
<div className="text-xs font-medium tracking-tight text-[var(--kyro-text-primary)] leading-tight">
|
|
65
64
|
{block.label}
|
|
66
65
|
</div>
|
|
67
66
|
<div className="text-[10px] text-[var(--kyro-text-muted)] mt-0.5 leading-tight">
|