@kyro-cms/admin 0.1.6 → 0.1.8

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 (179) hide show
  1. package/README.md +149 -51
  2. package/package.json +54 -5
  3. package/src/collections/auth/index.ts +2 -2
  4. package/src/collections/portfolio/index.ts +343 -0
  5. package/src/components/ActionBar.tsx +153 -16
  6. package/src/components/Admin.tsx +137 -28
  7. package/src/components/ApiExplorer.tsx +325 -0
  8. package/src/components/ApiKeysManager.tsx +563 -0
  9. package/src/components/AuditLogsPage.tsx +664 -0
  10. package/src/components/AutoForm.tsx +2155 -770
  11. package/src/components/BrandingHub.tsx +267 -0
  12. package/src/components/BulkActionsBar.tsx +3 -3
  13. package/src/components/CreateView.tsx +4 -4
  14. package/src/components/Dashboard.tsx +393 -0
  15. package/src/components/DetailView.tsx +200 -58
  16. package/src/components/DeveloperCenter.tsx +403 -0
  17. package/src/components/EnhancedListView.tsx +890 -0
  18. package/src/components/GraphQLExplorer.tsx +675 -0
  19. package/src/components/GraphQLPlayground.tsx +627 -0
  20. package/src/components/ListView.tsx +192 -54
  21. package/src/components/MediaGallery.tsx +1569 -0
  22. package/src/components/Modal.tsx +206 -0
  23. package/src/components/RestPlayground.tsx +951 -0
  24. package/src/components/Sidebar.astro +237 -0
  25. package/src/components/ThemeProvider.tsx +8 -2
  26. package/src/components/UserManagement.tsx +204 -0
  27. package/src/components/VersionHistoryPanel.tsx +3 -3
  28. package/src/components/WebhookManager.tsx +608 -0
  29. package/src/components/blocks/AccordionBlock.tsx +65 -0
  30. package/src/components/blocks/ArrayBlock.tsx +84 -0
  31. package/src/components/blocks/BlockEditModal.tsx +363 -0
  32. package/src/components/blocks/ButtonBlock.tsx +64 -0
  33. package/src/components/blocks/ChildBlocksTree.tsx +551 -0
  34. package/src/components/blocks/CodeBlock.tsx +114 -0
  35. package/src/components/blocks/ColumnsBlock.tsx +93 -0
  36. package/src/components/blocks/DividerBlock.tsx +43 -0
  37. package/src/components/blocks/FileBlock.tsx +63 -0
  38. package/src/components/blocks/HeadingBlock.tsx +59 -0
  39. package/src/components/blocks/HeroBlock.tsx +99 -0
  40. package/src/components/blocks/ImageBlock.tsx +82 -0
  41. package/src/components/blocks/LinkBlock.tsx +65 -0
  42. package/src/components/blocks/ListBlock.tsx +60 -0
  43. package/src/components/blocks/ParagraphBlock.tsx +61 -0
  44. package/src/components/blocks/RelationshipBlock.tsx +72 -0
  45. package/src/components/blocks/RichTextBlock.tsx +66 -0
  46. package/src/components/blocks/VStackBlock.tsx +61 -0
  47. package/src/components/blocks/VideoBlock.tsx +65 -0
  48. package/src/components/blocks/index.ts +10 -0
  49. package/src/components/fields/AccordionField.tsx +213 -0
  50. package/src/components/fields/ArrayField.tsx +241 -0
  51. package/src/components/fields/BlocksField.tsx +323 -0
  52. package/src/components/fields/ButtonField.tsx +53 -0
  53. package/src/components/fields/CheckboxField.tsx +18 -8
  54. package/src/components/fields/ChildrenField.tsx +48 -0
  55. package/src/components/fields/CodeField.tsx +294 -0
  56. package/src/components/fields/ColumnsField.tsx +137 -0
  57. package/src/components/fields/DateField.tsx +24 -12
  58. package/src/components/fields/EditorClient.tsx +537 -0
  59. package/src/components/fields/HeadingField.tsx +31 -0
  60. package/src/components/fields/HeroField.tsx +101 -0
  61. package/src/components/fields/JSONField.tsx +341 -0
  62. package/src/components/fields/LinkField.tsx +81 -0
  63. package/src/components/fields/ListField.tsx +74 -0
  64. package/src/components/fields/MarkdownField.tsx +260 -0
  65. package/src/components/fields/NumberField.tsx +25 -13
  66. package/src/components/fields/PortableTextField.tsx +155 -0
  67. package/src/components/fields/PortableTextRenderer.tsx +68 -0
  68. package/src/components/fields/RelationshipBlockField.tsx +233 -0
  69. package/src/components/fields/RelationshipField.tsx +278 -60
  70. package/src/components/fields/SelectField.tsx +28 -16
  71. package/src/components/fields/TextField.tsx +31 -15
  72. package/src/components/fields/UploadField.tsx +613 -0
  73. package/src/components/fields/VideoField.tsx +73 -0
  74. package/src/components/fields/extensions/blockComponents.tsx +247 -0
  75. package/src/components/fields/extensions/blocksStore.ts +273 -0
  76. package/src/components/fields/index.ts +24 -0
  77. package/src/components/index.ts +1 -2
  78. package/src/components/layout/Header.tsx +2 -2
  79. package/src/components/layout/Layout.tsx +3 -3
  80. package/src/components/ui/Badge.tsx +9 -4
  81. package/src/components/ui/BlockDrawer.tsx +79 -0
  82. package/src/components/ui/Button.tsx +1 -1
  83. package/src/components/ui/CommandPalette.tsx +362 -0
  84. package/src/components/ui/CommandPaletteWrapper.tsx +97 -0
  85. package/src/components/ui/Dropdown.tsx +1 -1
  86. package/src/components/ui/Modal.tsx +37 -12
  87. package/src/components/ui/PromptModal.tsx +94 -0
  88. package/src/components/ui/SlidePanel.tsx +43 -16
  89. package/src/components/ui/Toast.tsx +80 -14
  90. package/src/env.d.ts +16 -0
  91. package/src/env.ts +20 -0
  92. package/src/index.ts +0 -1
  93. package/src/layouts/AdminLayout.astro +164 -170
  94. package/src/layouts/AuthLayout.astro +23 -6
  95. package/src/lib/MediaService.ts +541 -0
  96. package/src/lib/api.ts +163 -0
  97. package/src/lib/auth/sqlite-adapter.ts +319 -0
  98. package/src/lib/config.ts +23 -7
  99. package/src/lib/dataStore.ts +188 -73
  100. package/src/lib/date-utils.ts +69 -0
  101. package/src/lib/db/adapter.ts +54 -0
  102. package/src/lib/db/drizzle-mysql-adapter.ts +194 -0
  103. package/src/lib/db/drizzle-mysql-auth-adapter.ts +327 -0
  104. package/src/lib/db/drizzle-postgres-adapter.ts +202 -0
  105. package/src/lib/db/drizzle-postgres-auth-adapter.ts +304 -0
  106. package/src/lib/db/drizzle-sqlite-adapter.ts +227 -0
  107. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +548 -0
  108. package/src/lib/db/index.ts +449 -0
  109. package/src/lib/db/mongodb-adapter.ts +207 -0
  110. package/src/lib/db/mongodb-auth-adapter.ts +305 -0
  111. package/src/lib/db/schema/mysql-auth.ts +113 -0
  112. package/src/lib/db/schema/mysql-content.ts +20 -0
  113. package/src/lib/db/schema/postgres-auth.ts +116 -0
  114. package/src/lib/db/schema/postgres-content.ts +35 -0
  115. package/src/lib/db/schema/postgres-media.ts +52 -0
  116. package/src/lib/db/schema/postgres-settings.ts +11 -0
  117. package/src/lib/db/schema/sqlite-auth.ts +112 -0
  118. package/src/lib/db/schema/sqlite-content.ts +20 -0
  119. package/src/lib/db/version-adapter.ts +248 -0
  120. package/src/lib/graphql/index.ts +1 -0
  121. package/src/lib/graphql/schema.ts +443 -0
  122. package/src/lib/i18n.tsx +353 -0
  123. package/src/lib/rate-limit.ts +267 -0
  124. package/src/lib/slugify.ts +15 -0
  125. package/src/lib/storage.ts +374 -0
  126. package/src/lib/store.ts +85 -0
  127. package/src/lib/validation.ts +250 -0
  128. package/src/middleware.ts +70 -11
  129. package/src/pages/[collection]/[id].astro +178 -122
  130. package/src/pages/[collection]/index.astro +24 -156
  131. package/src/pages/admin/api-explorer.astro +98 -0
  132. package/src/pages/admin/graphql-explorer.astro +40 -0
  133. package/src/pages/admin/graphql.astro +97 -0
  134. package/src/pages/admin/index.astro +200 -139
  135. package/src/pages/admin/keys.astro +8 -0
  136. package/src/pages/admin/rest-playground.astro +44 -0
  137. package/src/pages/admin/webhooks.astro +8 -0
  138. package/src/pages/api/[collection]/[id]/publish.ts +52 -0
  139. package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
  140. package/src/pages/api/[collection]/[id]/versions.ts +66 -0
  141. package/src/pages/api/[collection]/[id].ts +114 -159
  142. package/src/pages/api/[collection]/index.ts +150 -230
  143. package/src/pages/api/auth/[id].ts +48 -69
  144. package/src/pages/api/auth/audit-logs.ts +20 -43
  145. package/src/pages/api/auth/login.ts +159 -45
  146. package/src/pages/api/auth/logout.ts +42 -24
  147. package/src/pages/api/auth/refresh.ts +119 -0
  148. package/src/pages/api/auth/register.ts +110 -40
  149. package/src/pages/api/auth/users.ts +22 -97
  150. package/src/pages/api/collections.ts +59 -0
  151. package/src/pages/api/globals/[slug]/test.ts +172 -0
  152. package/src/pages/api/globals/[slug].ts +42 -0
  153. package/src/pages/api/graphql.ts +90 -0
  154. package/src/pages/api/health.ts +417 -40
  155. package/src/pages/api/keys/[id].ts +26 -0
  156. package/src/pages/api/keys/index.ts +75 -0
  157. package/src/pages/api/media/[id].ts +309 -0
  158. package/src/pages/api/media/folders.ts +609 -0
  159. package/src/pages/api/media/index.ts +146 -0
  160. package/src/pages/api/media/resize.ts +267 -0
  161. package/src/pages/api/search.ts +82 -0
  162. package/src/pages/api/slug-availability.ts +70 -0
  163. package/src/pages/api/storage-config.ts +20 -0
  164. package/src/pages/api/storage-status.ts +206 -0
  165. package/src/pages/api/upload.ts +334 -0
  166. package/src/pages/api/webhooks/index.ts +71 -0
  167. package/src/pages/audit/index.astro +2 -104
  168. package/src/pages/login.astro +11 -11
  169. package/src/pages/media.astro +10 -0
  170. package/src/pages/preview/[collection]/[id].astro +178 -0
  171. package/src/pages/register.astro +13 -13
  172. package/src/pages/roles/index.astro +21 -21
  173. package/src/pages/settings/[slug].astro +162 -0
  174. package/src/pages/settings/index.astro +9 -0
  175. package/src/pages/users/[id].astro +29 -21
  176. package/src/pages/users/index.astro +22 -17
  177. package/src/pages/users/new.astro +18 -17
  178. package/src/styles/main.css +563 -128
  179. package/src/components/layout/Sidebar.tsx +0 -497
@@ -0,0 +1,48 @@
1
+ import React from "react";
2
+ import { ChildBlocksTree } from "../blocks/ChildBlocksTree";
3
+
4
+ interface ChildrenFieldProps {
5
+ blockId: string;
6
+ children: any[];
7
+ onUpdateChildren: (newChildren: any[]) => void;
8
+ label?: string;
9
+ compact?: boolean;
10
+ }
11
+
12
+ export const ChildrenField: React.FC<ChildrenFieldProps> = ({
13
+ blockId,
14
+ children,
15
+ onUpdateChildren,
16
+ label,
17
+ compact = false,
18
+ }) => {
19
+ if (compact) {
20
+ return (
21
+ <div className="pt-2 border-t border-[var(--kyro-border)]">
22
+ <label className="text-[10px] font-medium text-[var(--kyro-text-muted)] mb-1.5 block">
23
+ {label || `Children (${children.length})`}
24
+ </label>
25
+ <ChildBlocksTree
26
+ blockId={blockId}
27
+ children={children}
28
+ onUpdateChildren={onUpdateChildren}
29
+ />
30
+ </div>
31
+ );
32
+ }
33
+
34
+ return (
35
+ <div className="pt-4 border-t border-[var(--kyro-border)]">
36
+ <label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-2 block">
37
+ {label || `Children (${children.length})`}
38
+ </label>
39
+ <ChildBlocksTree
40
+ blockId={blockId}
41
+ children={children}
42
+ onUpdateChildren={onUpdateChildren}
43
+ />
44
+ </div>
45
+ );
46
+ };
47
+
48
+ export default ChildrenField;
@@ -0,0 +1,294 @@
1
+ import React, { useCallback, useEffect, useState, Suspense, lazy } from "react";
2
+ import { githubLight } from "@uiw/codemirror-theme-github";
3
+ import { aura } from "@uiw/codemirror-theme-aura";
4
+ import type { CodeField as CodeFieldType } from "@kyro-cms/core/client";
5
+ import { useTheme } from "../ThemeProvider";
6
+
7
+ interface CodeFieldProps {
8
+ field: CodeFieldType;
9
+ value?: string;
10
+ onChange?: (value: string) => void;
11
+ error?: string;
12
+ disabled?: boolean;
13
+ }
14
+
15
+ const CodeMirrorEditor = lazy(() =>
16
+ import("@uiw/react-codemirror").then((mod) => ({ default: mod.default })),
17
+ );
18
+
19
+ const LANGUAGES = [
20
+ { value: "javascript", label: "JavaScript" },
21
+ { value: "typescript", label: "TypeScript" },
22
+ { value: "json", label: "JSON" },
23
+ { value: "html", label: "HTML" },
24
+ { value: "css", label: "CSS" },
25
+ { value: "sql", label: "SQL" },
26
+ { value: "python", label: "Python" },
27
+ { value: "rust", label: "Rust" },
28
+ { value: "java", label: "Java" },
29
+ { value: "php", label: "PHP" },
30
+ { value: "markdown", label: "Markdown" },
31
+ ];
32
+
33
+ const languageExtensions: Record<string, () => Promise<any>> = {
34
+ javascript: () =>
35
+ import("@codemirror/lang-javascript").then((m) =>
36
+ m.javascript({ jsx: true, typescript: true }),
37
+ ),
38
+ typescript: () =>
39
+ import("@codemirror/lang-javascript").then((m) =>
40
+ m.javascript({ jsx: true, typescript: true }),
41
+ ),
42
+ js: () =>
43
+ import("@codemirror/lang-javascript").then((m) =>
44
+ m.javascript({ jsx: true }),
45
+ ),
46
+ jsx: () =>
47
+ import("@codemirror/lang-javascript").then((m) =>
48
+ m.javascript({ jsx: true }),
49
+ ),
50
+ ts: () =>
51
+ import("@codemirror/lang-javascript").then((m) =>
52
+ m.javascript({ typescript: true }),
53
+ ),
54
+ json: () => import("@codemirror/lang-json").then((m) => m.json()),
55
+ css: () => import("@codemirror/lang-css").then((m) => m.css()),
56
+ sql: () => import("@codemirror/lang-sql").then((m) => m.sql()),
57
+ python: () => import("@codemirror/lang-python").then((m) => m.python()),
58
+ html: () => import("@codemirror/lang-html").then((m) => m.html()),
59
+ rust: () => import("@codemirror/lang-rust").then((m) => m.rust()),
60
+ java: () => import("@codemirror/lang-java").then((m) => m.java()),
61
+ cpp: () => import("@codemirror/lang-cpp").then((m) => m.cpp()),
62
+ c: () => import("@codemirror/lang-cpp").then((m) => m.cpp()),
63
+ php: () => import("@codemirror/lang-php").then((m) => m.php()),
64
+ markdown: () => import("@codemirror/lang-markdown").then((m) => m.markdown()),
65
+ py: () => import("@codemirror/lang-python").then((m) => m.python()),
66
+ };
67
+
68
+ export const CodeField: React.FC<CodeFieldProps> = ({
69
+ field,
70
+ value = "",
71
+ onChange,
72
+ error,
73
+ disabled,
74
+ }) => {
75
+ const [isMounted, setIsMounted] = useState(false);
76
+ const [isDark, setIsDark] = useState(false);
77
+ const [extensions, setExtensions] = useState<any[]>([]);
78
+ const [loading, setLoading] = useState(false);
79
+ const [copied, setCopied] = useState(false);
80
+ const [isFullScreen, setIsFullScreen] = useState(false);
81
+
82
+ const { theme } = useTheme();
83
+ const accent = theme.colors?.accent || theme.colors?.primary || "#6366f1";
84
+ const language = field.language?.toLowerCase() || "javascript";
85
+
86
+ useEffect(() => {
87
+ setIsMounted(true);
88
+ setIsDark(document.documentElement.classList.contains("dark"));
89
+ const observer = new MutationObserver(() => {
90
+ setIsDark(document.documentElement.classList.contains("dark"));
91
+ });
92
+ observer.observe(document.documentElement, {
93
+ attributes: true,
94
+ attributeFilter: ["class"],
95
+ });
96
+ return () => observer.disconnect();
97
+ }, []);
98
+
99
+ useEffect(() => {
100
+ if (!isMounted) return;
101
+ const loadExtensions = async () => {
102
+ setLoading(true);
103
+ try {
104
+ const loader =
105
+ languageExtensions[language] || languageExtensions.javascript;
106
+ const ext = await loader();
107
+ setExtensions([ext]);
108
+ } catch (err) {
109
+ console.error("Failed to load language extension:", err);
110
+ setExtensions([]);
111
+ } finally {
112
+ setLoading(false);
113
+ }
114
+ };
115
+ loadExtensions();
116
+ }, [language, isMounted]);
117
+
118
+ const handleChange = useCallback(
119
+ (val: string) => onChange?.(val),
120
+ [onChange],
121
+ );
122
+
123
+ const handleCopy = useCallback(() => {
124
+ navigator.clipboard.writeText(value);
125
+ setCopied(true);
126
+ setTimeout(() => setCopied(false), 1500);
127
+ }, [value]);
128
+
129
+ const toggleFullScreen = useCallback(() => {
130
+ setIsFullScreen((prev) => !prev);
131
+ document.body.style.overflow = !isFullScreen ? "hidden" : "";
132
+ }, [isFullScreen]);
133
+
134
+ const codeMirrorTheme = isDark ? aura : githubLight;
135
+ const langLabel =
136
+ LANGUAGES.find((l) => l.value === language)?.label || language;
137
+
138
+ const hexToRgba = (hex: string, alpha: number = 1) => {
139
+ const r = parseInt(hex.slice(1, 3), 16);
140
+ const g = parseInt(hex.slice(3, 5), 16);
141
+ const b = parseInt(hex.slice(5, 7), 16);
142
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
143
+ };
144
+
145
+ if (!isMounted) {
146
+ return (
147
+ <div className="space-y-3">
148
+ <div className="flex items-center gap-3">
149
+ <div className="h-5 w-24 rounded bg-[var(--kyro-surface-accent)] animate-pulse" />
150
+ <div className="h-5 w-16 rounded bg-[var(--kyro-surface-accent)] animate-pulse" />
151
+ </div>
152
+ <div className="h-[280px] rounded-xl bg-[var(--kyro-surface-accent)] animate-pulse" />
153
+ </div>
154
+ );
155
+ }
156
+
157
+ return (
158
+ <div
159
+ className="group"
160
+ style={
161
+ {
162
+ "--accent": accent,
163
+ "--accent-light": hexToRgba(accent, 0.1),
164
+ "--accent-light-2": hexToRgba(accent, 0.05),
165
+ } as React.CSSProperties
166
+ }
167
+ >
168
+ {/* Header */}
169
+ <div className="flex items-center justify-between mb-2">
170
+ <div className="flex items-center gap-2">
171
+ <span className="text-sm font-semibold text-[var(--kyro-text-primary)]">
172
+ {field.label || field.name}
173
+ </span>
174
+ <span
175
+ className="text-[10px] px-1.5 py-0.5 rounded font-semibold transition-colors"
176
+ style={{
177
+ backgroundColor: loading ? "var(--kyro-surface-accent)" : accent,
178
+ color: loading ? "var(--kyro-text-muted)" : "white",
179
+ }}
180
+ >
181
+ {loading ? "..." : langLabel}
182
+ </span>
183
+ </div>
184
+
185
+ {/* Action buttons */}
186
+ <div className="flex items-center gap-1">
187
+ <button
188
+ type="button"
189
+ onClick={handleCopy}
190
+ className="text-[10px] px-2 py-0.5 rounded font-medium transition-all"
191
+ style={{
192
+ backgroundColor: copied
193
+ ? "var(--kyro-success)"
194
+ : "var(--kyro-surface-accent)",
195
+ color: copied ? "white" : "var(--kyro-text-secondary)",
196
+ }}
197
+ >
198
+ {copied ? "Copied" : "Copy"}
199
+ </button>
200
+ <button
201
+ type="button"
202
+ onClick={toggleFullScreen}
203
+ className="text-[10px] px-2 py-0.5 rounded font-medium bg-[var(--kyro-surface-accent)] hover:bg-[var(--kyro-border)] transition-all"
204
+ style={{ color: "var(--kyro-text-secondary)" }}
205
+ >
206
+ {isFullScreen ? "Exit" : "Expand"}
207
+ </button>
208
+ </div>
209
+ </div>
210
+
211
+ {/* Editor container */}
212
+ <div
213
+ className={`relative rounded-md overflow-hidden transition-all duration-200 ${
214
+ isFullScreen ? "fixed inset-4 z-50" : ""
215
+ }`}
216
+ style={{
217
+ backgroundColor: "var(--kyro-surface)",
218
+ borderColor: error ? "var(--kyro-error)" : "var(--kyro-border)",
219
+ borderWidth: "1px",
220
+ }}
221
+ >
222
+ {/* Top accent bar */}
223
+ <div
224
+ className="absolute top-0 left-0 right-0 h-0.5 transition-all duration-200"
225
+ style={{ backgroundColor: accent }}
226
+ />
227
+
228
+ <Suspense
229
+ fallback={
230
+ <div
231
+ className="h-[280px] flex items-center justify-center"
232
+ style={{ backgroundColor: "var(--kyro-surface)" }}
233
+ >
234
+ <div
235
+ className="w-6 h-6 rounded-full animate-spin"
236
+ style={{
237
+ borderColor: "var(--kyro-border)",
238
+ borderTopColor: accent,
239
+ borderWidth: "2px",
240
+ }}
241
+ />
242
+ </div>
243
+ }
244
+ >
245
+ <CodeMirrorEditor
246
+ value={value}
247
+ height={isFullScreen ? "calc(100vh - 100px)" : "280px"}
248
+ width="100%"
249
+ extensions={extensions}
250
+ theme={codeMirrorTheme}
251
+ onChange={handleChange}
252
+ editable={!disabled}
253
+ basicSetup={{
254
+ lineNumbers: true,
255
+ highlightActiveLine: true,
256
+ bracketMatching: true,
257
+ closeBrackets: true,
258
+ autocompletion: true,
259
+ foldGutter: true,
260
+ }}
261
+ style={{
262
+ fontSize: "13px",
263
+ fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
264
+ }}
265
+ />
266
+ </Suspense>
267
+
268
+ {/* Error message */}
269
+ {error && (
270
+ <div
271
+ className="absolute bottom-0 left-0 right-0 px-3 py-2 text-xs font-medium"
272
+ style={{
273
+ backgroundColor: "var(--kyro-danger-bg)",
274
+ color: "var(--kyro-error)",
275
+ }}
276
+ >
277
+ {error}
278
+ </div>
279
+ )}
280
+ </div>
281
+
282
+ {/* Footer */}
283
+ <div
284
+ className="flex items-center justify-between mt-2 text-[10px]"
285
+ style={{ color: "var(--kyro-text-muted)" }}
286
+ >
287
+ <span>{value.split("\n").length} lines</span>
288
+ <span style={{ color: accent }}>CodeMirror</span>
289
+ </div>
290
+ </div>
291
+ );
292
+ };
293
+
294
+ export default CodeField;
@@ -0,0 +1,137 @@
1
+ import React from "react";
2
+ import { ChildBlocksTree } from "../blocks/ChildBlocksTree";
3
+
4
+ interface ColumnData {
5
+ id: number;
6
+ children: any[];
7
+ }
8
+
9
+ interface ColumnsFieldProps {
10
+ columns?: number;
11
+ columnData?: ColumnData[];
12
+ onColumnsChange: (columns: number) => void;
13
+ onUpdateColumnChildren: (columnIndex: number, newChildren: any[]) => void;
14
+ compact?: boolean;
15
+ }
16
+
17
+ export const ColumnsField: React.FC<ColumnsFieldProps> = ({
18
+ columns = 2,
19
+ columnData = [],
20
+ onColumnsChange,
21
+ onUpdateColumnChildren,
22
+ compact = false,
23
+ }) => {
24
+ const normalizedColumnData = Array.from({ length: columns }, (_, i) => ({
25
+ id: i,
26
+ children: columnData[i]?.children || [],
27
+ }));
28
+
29
+ if (compact) {
30
+ return (
31
+ <div className="space-y-2">
32
+ <div className="flex items-center gap-2">
33
+ <span className="text-xs font-medium text-[var(--kyro-text-muted)]">
34
+ Columns:
35
+ </span>
36
+ <div className="flex items-center gap-0.5 bg-[var(--kyro-surface-accent)] rounded-lg p-0.5">
37
+ {Array.from({ length: 6 }, (_, i) => i + 1).map((n) => (
38
+ <button
39
+ key={n}
40
+ type="button"
41
+ onClick={() => onColumnsChange(n)}
42
+ className={`px-2.5 py-1 rounded-md text-xs font-medium transition-all ${
43
+ columns === n
44
+ ? "bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] shadow-sm"
45
+ : "text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)]"
46
+ }`}
47
+ >
48
+ {n}
49
+ </button>
50
+ ))}
51
+ </div>
52
+ </div>
53
+ <div className="overflow-x-auto pb-1">
54
+ <div className="flex gap-2 min-w-fit">
55
+ {normalizedColumnData.map((col, i) => (
56
+ <div
57
+ key={i}
58
+ className="w-[180px] border-2 border-dashed border-[var(--kyro-border)] rounded-lg p-2"
59
+ >
60
+ <div className="flex items-center justify-between mb-1.5 pb-1 border-b border-[var(--kyro-border)]">
61
+ <span className="text-[10px] font-medium text-[var(--kyro-text-muted)]">
62
+ Col {i + 1}
63
+ </span>
64
+ <span className="text-[10px] text-[var(--kyro-text-muted)]">
65
+ {col.children.length}
66
+ </span>
67
+ </div>
68
+ <ChildBlocksTree
69
+ blockId={`col-${i}`}
70
+ children={col.children}
71
+ onUpdateChildren={(c) => onUpdateColumnChildren(i, c)}
72
+ />
73
+ </div>
74
+ ))}
75
+ </div>
76
+ </div>
77
+ </div>
78
+ );
79
+ }
80
+
81
+ return (
82
+ <div className="space-y-4">
83
+ <div className="flex items-center gap-2">
84
+ <span className="text-xs font-medium text-[var(--kyro-text-muted)]">
85
+ Columns:
86
+ </span>
87
+ <div className="flex items-center gap-0.5 bg-[var(--kyro-surface-accent)] rounded-lg p-0.5">
88
+ {Array.from({ length: 6 }, (_, i) => i + 1).map((n) => (
89
+ <button
90
+ key={n}
91
+ type="button"
92
+ onClick={() => onColumnsChange(n)}
93
+ className={`px-3 py-1.5 rounded-md text-xs font-medium transition-all ${
94
+ columns === n
95
+ ? "bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] shadow-sm"
96
+ : "text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)]"
97
+ }`}
98
+ >
99
+ {n}
100
+ </button>
101
+ ))}
102
+ </div>
103
+ </div>
104
+ <div className="overflow-x-auto pb-2">
105
+ <div
106
+ className="grid gap-3 min-w-fit"
107
+ style={{
108
+ gridTemplateColumns: `repeat(${columns}, minmax(200px, 1fr))`,
109
+ }}
110
+ >
111
+ {normalizedColumnData.map((col, i) => (
112
+ <div
113
+ key={i}
114
+ className="border-2 border-dashed border-[var(--kyro-border)] rounded-lg p-3"
115
+ >
116
+ <div className="flex items-center justify-between mb-3 pb-2 border-b border-[var(--kyro-border)]">
117
+ <span className="text-xs font-medium text-[var(--kyro-text-primary)]">
118
+ Column {i + 1}
119
+ </span>
120
+ <span className="text-[10px] text-[var(--kyro-text-muted)]">
121
+ {col.children.length} blocks
122
+ </span>
123
+ </div>
124
+ <ChildBlocksTree
125
+ blockId={`col-${i}`}
126
+ children={col.children}
127
+ onUpdateChildren={(c) => onUpdateColumnChildren(i, c)}
128
+ />
129
+ </div>
130
+ ))}
131
+ </div>
132
+ </div>
133
+ </div>
134
+ );
135
+ };
136
+
137
+ export default ColumnsField;
@@ -1,4 +1,4 @@
1
- import type { DateField as DateFieldType } from '@kyro-cms/core';
1
+ import type { DateField as DateFieldType } from "@kyro-cms/core/client";
2
2
 
3
3
  interface DateFieldComponentProps {
4
4
  field: DateFieldType;
@@ -8,17 +8,25 @@ interface DateFieldComponentProps {
8
8
  disabled?: boolean;
9
9
  }
10
10
 
11
- export default function DateField({ field, value = '', onChange, error, disabled }: DateFieldComponentProps) {
11
+ export default function DateField({
12
+ field,
13
+ value = "",
14
+ onChange,
15
+ error,
16
+ disabled,
17
+ }: DateFieldComponentProps) {
12
18
  return (
13
19
  <div className="space-y-1">
14
20
  {field.label && (
15
- <label className="block text-sm font-medium text-gray-700">
21
+ <label className="block text-sm font-medium text-[var(--kyro-text-primary)]">
16
22
  {field.label}
17
- {field.required && <span className="text-red-500 ml-1">*</span>}
23
+ {field.required && (
24
+ <span className="text-[var(--kyro-error)] ml-1">*</span>
25
+ )}
18
26
  </label>
19
27
  )}
20
28
  <input
21
- type={field.time ? 'datetime-local' : 'date'}
29
+ type={field.time ? "datetime-local" : "date"}
22
30
  value={value}
23
31
  onChange={(e) => onChange?.(e.target.value)}
24
32
  disabled={disabled || field.admin?.readOnly}
@@ -27,16 +35,20 @@ export default function DateField({ field, value = '', onChange, error, disabled
27
35
  required={field.required}
28
36
  className={`w-full px-3 py-2 border rounded-md text-sm transition-colors ${
29
37
  error
30
- ? 'border-red-300 focus:border-red-500 focus:ring-red-500'
31
- : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
32
- } ${disabled || field.admin?.readOnly ? 'bg-gray-50 text-gray-500' : 'bg-white'}`}
38
+ ? "border-[var(--kyro-error)] focus:border-[var(--kyro-error)] focus:ring-[var(--kyro-error)]"
39
+ : "border-[var(--kyro-border)] focus:border-[var(--kyro-primary)] focus:ring-[var(--kyro-primary)]"
40
+ } ${
41
+ disabled || field.admin?.readOnly
42
+ ? "bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-secondary)] opacity-50"
43
+ : "bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)]"
44
+ }`}
33
45
  />
34
46
  {field.admin?.description && !error && (
35
- <p className="text-xs text-gray-500">{field.admin.description}</p>
36
- )}
37
- {error && (
38
- <p className="text-xs text-red-600">{error}</p>
47
+ <p className="text-xs text-[var(--kyro-text-muted)]">
48
+ {field.admin.description}
49
+ </p>
39
50
  )}
51
+ {error && <p className="text-xs text-[var(--kyro-error)]">{error}</p>}
40
52
  </div>
41
53
  );
42
54
  }