@kyro-cms/admin 0.9.0 → 0.9.1

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 (100) hide show
  1. package/dist/index.cjs +11960 -11006
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +67 -65
  4. package/dist/index.css.map +1 -1
  5. package/dist/index.d.cts +563 -0
  6. package/dist/index.d.ts +7 -7
  7. package/dist/index.js +12183 -11238
  8. package/dist/index.js.map +1 -1
  9. package/package.json +15 -11
  10. package/src/components/ActionBar.tsx +27 -14
  11. package/src/components/Admin.tsx +1 -1
  12. package/src/components/ApiKeysManager.tsx +5 -5
  13. package/src/components/AutoForm.tsx +585 -369
  14. package/src/components/BrandingHub.tsx +7 -4
  15. package/src/components/CreateView.tsx +2 -0
  16. package/src/components/DetailView.tsx +71 -56
  17. package/src/components/DeveloperCenter.tsx +8 -6
  18. package/src/components/FieldRenderer.tsx +94 -19
  19. package/src/components/ListView.tsx +33 -20
  20. package/src/components/MediaGallery.tsx +219 -194
  21. package/src/components/PluginsManager.tsx +197 -70
  22. package/src/components/RestPlayground.tsx +7 -7
  23. package/src/components/SessionsManager.tsx +1 -1
  24. package/src/components/SettingsPage.tsx +22 -0
  25. package/src/components/Sidebar.astro +13 -41
  26. package/src/components/UserManagement.tsx +153 -15
  27. package/src/components/UserMenu.tsx +30 -4
  28. package/src/components/VersionHistoryPanel.tsx +112 -119
  29. package/src/components/WebhookManager.tsx +6 -4
  30. package/src/components/blocks/ArrayBlock.tsx +6 -23
  31. package/src/components/blocks/BlockEditModal.tsx +82 -309
  32. package/src/components/blocks/CardBlock.tsx +35 -0
  33. package/src/components/blocks/ChildBlocksTree.tsx +57 -31
  34. package/src/components/blocks/GenericBlock.tsx +44 -0
  35. package/src/components/blocks/HeadingSubheadingBlock.tsx +32 -0
  36. package/src/components/blocks/HeroBlock.tsx +5 -14
  37. package/src/components/blocks/RichTextBlock.tsx +5 -5
  38. package/src/components/blocks/index.ts +5 -3
  39. package/src/components/fields/AccordionField.tsx +2 -2
  40. package/src/components/fields/ArrayField.tsx +1 -1
  41. package/src/components/fields/ArrayLayout.tsx +120 -29
  42. package/src/components/fields/BlocksField.tsx +430 -50
  43. package/src/components/fields/CardField.tsx +73 -0
  44. package/src/components/fields/CheckboxField.tsx +7 -3
  45. package/src/components/fields/DateField.tsx +4 -1
  46. package/src/components/fields/GroupLayout.tsx +2 -2
  47. package/src/components/fields/HeadingSubheadingField.tsx +43 -0
  48. package/src/components/fields/ListField.tsx +2 -2
  49. package/src/components/fields/NumberField.tsx +4 -1
  50. package/src/components/fields/RelationshipField.tsx +153 -87
  51. package/src/components/fields/RichTextField.tsx +781 -0
  52. package/src/components/fields/SecretField.tsx +102 -0
  53. package/src/components/fields/SelectField.tsx +19 -6
  54. package/src/components/fields/TabsLayout.tsx +19 -9
  55. package/src/components/fields/TextField.tsx +4 -1
  56. package/src/components/fields/UploadField.tsx +122 -56
  57. package/src/components/fields/extensions/blockComponents.tsx +103 -174
  58. package/src/components/fields/extensions/blocksStore.ts +8 -1
  59. package/src/components/fields/index.ts +4 -2
  60. package/src/components/ui/PageHeader.tsx +5 -5
  61. package/src/components/ui/SlidePanel.tsx +8 -3
  62. package/src/components/ui/icons.tsx +109 -109
  63. package/src/components/users/UserDetail.tsx +79 -16
  64. package/src/hooks/useAutoFormState.ts +125 -62
  65. package/src/integration.ts +148 -46
  66. package/src/kyro-cms.d.ts +7 -2
  67. package/src/layouts/AuthLayout.astro +14 -2
  68. package/src/lib/autoform-store.ts +85 -52
  69. package/src/lib/change-source.ts +9 -0
  70. package/src/lib/config.ts +104 -8
  71. package/src/lib/globals.ts +44 -9
  72. package/src/lib/normalize-upload-fields.ts +41 -0
  73. package/src/lib/paths.ts +2 -2
  74. package/src/lib/resolve-field-value.ts +110 -0
  75. package/src/lib/shim/use-sync-external-store-with-selector.js +30 -0
  76. package/src/lib/shim/use-sync-external-store.js +1 -0
  77. package/src/lib/stores/index.ts +1 -0
  78. package/src/lib/useResourceManager.ts +4 -4
  79. package/src/lib/vite-shim-plugin.ts +100 -0
  80. package/src/pages/[collection]/[id].astro +1 -1
  81. package/src/pages/preview/[collection]/[id].astro +4 -4
  82. package/src/pages/settings/[slug].astro +2 -2
  83. package/src/styles/main.css +60 -54
  84. package/README.md +0 -46
  85. package/dist/EditorClient-Q23UXR37.cjs +0 -468
  86. package/dist/EditorClient-Q23UXR37.cjs.map +0 -1
  87. package/dist/EditorClient-T5PASFNR.js +0 -466
  88. package/dist/EditorClient-T5PASFNR.js.map +0 -1
  89. package/dist/chunk-3BGDYKTD.cjs +0 -348
  90. package/dist/chunk-3BGDYKTD.cjs.map +0 -1
  91. package/dist/chunk-EEFXLQVT.js +0 -3
  92. package/dist/chunk-EEFXLQVT.js.map +0 -1
  93. package/src/components/blocks/ButtonBlock.tsx +0 -64
  94. package/src/components/blocks/ColumnsBlock.tsx +0 -55
  95. package/src/components/blocks/DividerBlock.tsx +0 -43
  96. package/src/components/blocks/LinkBlock.tsx +0 -65
  97. package/src/components/blocks/VStackBlock.tsx +0 -29
  98. package/src/components/fields/EditorClient.tsx +0 -535
  99. package/src/components/fields/PortableTextField.tsx +0 -155
  100. package/src/components/fields/PortableTextRenderer.tsx +0 -68
@@ -0,0 +1,30 @@
1
+ import { useSyncExternalStore, useMemo } from "react";
2
+
3
+ export function useSyncExternalStoreWithSelector(
4
+ subscribe,
5
+ getSnapshot,
6
+ getServerSnapshot,
7
+ selector,
8
+ isEqual,
9
+ ) {
10
+ const memoizedSelector = useMemo(() => {
11
+ let last, lastSnap, hasMemo = false;
12
+ return (snap) => {
13
+ if (!hasMemo || !Object.is(lastSnap, snap)) {
14
+ const next = selector(snap);
15
+ if (!hasMemo || !(isEqual ? isEqual(last, next) : Object.is(last, next))) {
16
+ last = next;
17
+ lastSnap = snap;
18
+ hasMemo = true;
19
+ }
20
+ }
21
+ return last;
22
+ };
23
+ }, [selector, isEqual]);
24
+ const getSelection = () => memoizedSelector(getSnapshot());
25
+ const getServerSelection =
26
+ getServerSnapshot != null
27
+ ? () => memoizedSelector(getServerSnapshot())
28
+ : undefined;
29
+ return useSyncExternalStore(subscribe, getSelection, getServerSelection);
30
+ }
@@ -0,0 +1 @@
1
+ export { useSyncExternalStore } from "react";
@@ -10,6 +10,7 @@ export interface AuthUser {
10
10
  id: string;
11
11
  email: string;
12
12
  role: string;
13
+ avatar?: string;
13
14
  tenantId?: string;
14
15
  [key: string]: unknown;
15
16
  }
@@ -1,6 +1,6 @@
1
1
  import { useState, useEffect, useCallback } from "react";
2
2
  import { apiGet, apiPost, apiDelete, apiPatch } from "./api";
3
- import { useUIStore } from "./stores";
3
+ import { useUIStore, toast } from "./stores";
4
4
 
5
5
  interface UseResourceManagerOptions<T> {
6
6
  endpoint: string;
@@ -16,7 +16,7 @@ export function useResourceManager<T extends { id: string }>(
16
16
  const [loading, setLoading] = useState(false);
17
17
  const [error, setError] = useState<string | null>(null);
18
18
  const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
19
- const { confirm, alert } = useUIStore();
19
+ const { confirm } = useUIStore();
20
20
 
21
21
  const load = useCallback(async () => {
22
22
  setLoading(true);
@@ -52,12 +52,12 @@ export function useResourceManager<T extends { id: string }>(
52
52
  options.onSuccess?.("delete", id);
53
53
  } catch (e: unknown) {
54
54
  const message = e instanceof Error ? e.message : `Failed to delete ${resourceName}`;
55
- alert({ title: "Error", message });
55
+ toast.error(message);
56
56
  options.onError?.("delete", e as Error);
57
57
  }
58
58
  },
59
59
  });
60
- }, [options.endpoint, confirm, alert]);
60
+ }, [options.endpoint, confirm]);
61
61
 
62
62
  const create = useCallback(async (data: unknown) => {
63
63
  setError(null);
@@ -0,0 +1,100 @@
1
+ import type { Plugin } from "vite";
2
+
3
+ /**
4
+ * Vite plugin that intercepts `use-sync-external-store/shim` and
5
+ * `use-sync-external-store/shim/with-selector` imports and replaces them
6
+ * with proper ESM implementations.
7
+ *
8
+ * Why this is needed:
9
+ * - xstate-react (and other libs) import these shim files which are CJS-only.
10
+ * - When @kyro-cms/admin is consumed as a local `file:` workspace dependency,
11
+ * Vite skips pre-bundling for its transitive deps entirely, so the raw CJS
12
+ * files are served directly to the browser — which cannot parse them.
13
+ * - resolve.alias does not help because Vite's `resolveExportsOrImports` runs
14
+ * before alias matching for deep sub-path imports.
15
+ * - optimizeDeps.include does not help because it only applies to external npm
16
+ * packages, not transitive deps of local workspace packages.
17
+ * - A virtual Vite plugin with `enforce: "pre"` intercepts the import in the
18
+ * very first step of the resolution pipeline, before the filesystem is touched.
19
+ *
20
+ * useSyncExternalStoreWithSelector is NOT in the React core package, so we
21
+ * provide a minimal, spec-compliant implementation using the native
22
+ * useSyncExternalStore hook from React 18+.
23
+ */
24
+ export function useSyncExternalStoreShimPlugin(): Plugin {
25
+ const SHIM_ID = "\0virtual:use-sync-external-store-shim";
26
+ const SHIM_WITH_SELECTOR_ID =
27
+ "\0virtual:use-sync-external-store-shim-with-selector";
28
+
29
+ const SHIM_PATHS = new Set([
30
+ "use-sync-external-store/shim",
31
+ "use-sync-external-store/shim/index.js",
32
+ ]);
33
+
34
+ const SHIM_WITH_SELECTOR_PATHS = new Set([
35
+ "use-sync-external-store/shim/with-selector",
36
+ "use-sync-external-store/shim/with-selector.js",
37
+ ]);
38
+
39
+ return {
40
+ name: "use-sync-external-store-shim-fix",
41
+ enforce: "pre",
42
+
43
+ resolveId(id: string) {
44
+ if (SHIM_PATHS.has(id)) return SHIM_ID;
45
+ if (SHIM_WITH_SELECTOR_PATHS.has(id)) return SHIM_WITH_SELECTOR_ID;
46
+ },
47
+
48
+ load(id: string) {
49
+ if (id === SHIM_ID) {
50
+ // React 18+ ships useSyncExternalStore natively.
51
+ return `export { useSyncExternalStore } from "react";`;
52
+ }
53
+
54
+ if (id === SHIM_WITH_SELECTOR_ID) {
55
+ // useSyncExternalStoreWithSelector is not in React core.
56
+ // This is a minimal, spec-compliant implementation that wraps
57
+ // React's native useSyncExternalStore with selector memoisation.
58
+ return `
59
+ import { useSyncExternalStore, useRef, useMemo } from "react";
60
+
61
+ export function useSyncExternalStoreWithSelector(
62
+ subscribe,
63
+ getSnapshot,
64
+ getServerSnapshot,
65
+ selector,
66
+ isEqual
67
+ ) {
68
+ const memoRef = useRef(null);
69
+
70
+ const memoizedSelector = useMemo(() => {
71
+ let lastSnapshot = undefined;
72
+ let lastSelection = undefined;
73
+ let hasMemo = false;
74
+
75
+ return (snapshot) => {
76
+ if (!hasMemo || !Object.is(lastSnapshot, snapshot)) {
77
+ const nextSelection = selector(snapshot);
78
+ if (!hasMemo || !(isEqual ? isEqual(lastSelection, nextSelection) : Object.is(lastSelection, nextSelection))) {
79
+ lastSnapshot = snapshot;
80
+ lastSelection = nextSelection;
81
+ hasMemo = true;
82
+ }
83
+ }
84
+ return lastSelection;
85
+ };
86
+ }, [selector, isEqual]);
87
+
88
+ const getSelection = () => memoizedSelector(getSnapshot());
89
+ const getServerSelection =
90
+ getServerSnapshot !== undefined && getServerSnapshot !== null
91
+ ? () => memoizedSelector(getServerSnapshot())
92
+ : undefined;
93
+
94
+ return useSyncExternalStore(subscribe, getSelection, getServerSelection);
95
+ }
96
+ `;
97
+ }
98
+ },
99
+ };
100
+ }
@@ -58,7 +58,7 @@ const description =
58
58
  data={doc || {}}
59
59
  collectionSlug={collection}
60
60
  documentId={id || undefined}
61
- documentName={doc?.title || doc?.name || doc?.slug || "new-document"}
61
+ documentName={(typeof doc?.title === "object" ? "" : doc?.title) || (typeof doc?.name === "object" ? "" : doc?.name) || doc?.slug || "new-document"}
62
62
  />
63
63
  <input type="hidden" id="form-data" name="form-data" value="{}" />
64
64
  </form>
@@ -36,12 +36,12 @@ if (id) {
36
36
  }
37
37
  }
38
38
 
39
- const title = doc
40
- ? `Preview: ${doc.title || doc.name || doc.slug || id}`
39
+ const pageTitle = doc
40
+ ? `Preview: ${(typeof doc.title === "object" ? "" : doc.title) || (typeof doc.name === "object" ? "" : doc.name) || doc.slug || id}`
41
41
  : "Preview";
42
42
  ---
43
43
 
44
- <AdminLayout title={title}>
44
+ <AdminLayout title={pageTitle}>
45
45
  <Fragment set:html={`<style>${richTextStyles}</style>`} />
46
46
  <div class="flex-1 overflow-y-auto space-y-6">
47
47
  <div class="surface-tile p-6 flex items-center justify-between">
@@ -222,7 +222,7 @@ const title = doc
222
222
  </pre>
223
223
  )}
224
224
  {(!field.type || ["text", "string"].includes(field.type)) && (
225
- <p>{value}</p>
225
+ <p>{typeof value === "object" ? JSON.stringify(value) : value}</p>
226
226
  )}
227
227
  </div>
228
228
  </div>
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  import AdminLayout from "../../layouts/AdminLayout.astro";
3
3
  import { globals } from "../../lib/config";
4
- import { AutoForm } from "../../components/AutoForm";
4
+ import { SettingsPage } from "../../components/SettingsPage";
5
5
 
6
6
  import { adminPath, apiPath } from "../../lib/paths";
7
7
 
@@ -112,7 +112,7 @@ const description =
112
112
  )}
113
113
  <form id="settings-form">
114
114
  <input type="hidden" id="form-slug" value={slug} />
115
- <AutoForm
115
+ <SettingsPage
116
116
  client:only="react"
117
117
  config={config}
118
118
  globalSlug={slug}
@@ -1078,6 +1078,35 @@
1078
1078
  color: var(--kyro-sidebar-active);
1079
1079
  }
1080
1080
 
1081
+ /* Compact inline array mode */
1082
+ .kyro-form-array--compact .kyro-form-field {
1083
+ margin-bottom: 0 !important;
1084
+ }
1085
+ .kyro-form-array--compact .kyro-form-field > div > :not([hidden]) ~ :not([hidden]) {
1086
+ margin-top: 2px !important;
1087
+ }
1088
+ .kyro-form-array--compact label {
1089
+ font-size: 9px;
1090
+ letter-spacing: 0.08em;
1091
+ margin-bottom: 0;
1092
+ opacity: 0.6;
1093
+ display: block;
1094
+ }
1095
+ .kyro-form-array--compact input:not([type="checkbox"]):not([type="radio"]),
1096
+ .kyro-form-array--compact textarea,
1097
+ .kyro-form-array--compact select {
1098
+ padding: 3px 6px !important;
1099
+ font-size: 11px !important;
1100
+ min-height: 26px !important;
1101
+ border-radius: 4px !important;
1102
+ }
1103
+ .kyro-form-array--compact label > span {
1104
+ display: none;
1105
+ }
1106
+ .kyro-form-array--compact [class*="description"] {
1107
+ display: none !important;
1108
+ }
1109
+
1081
1110
  /* Blocks field */
1082
1111
  .kyro-form-blocks {
1083
1112
  display: flex;
@@ -1364,14 +1393,14 @@
1364
1393
  color: var(--kyro-text-secondary);
1365
1394
  }
1366
1395
 
1367
- /* Toast — Glassmorphic Monochrome */
1396
+ /* Toast — Colored Flat */
1368
1397
  .kyro-toasts-container {
1369
1398
  position: fixed;
1370
- bottom: 24px;
1371
- right: 24px;
1399
+ bottom: 16px;
1400
+ right: 16px;
1372
1401
  display: flex;
1373
1402
  flex-direction: column;
1374
- gap: 12px;
1403
+ gap: 6px;
1375
1404
  z-index: 9999;
1376
1405
  pointer-events: none;
1377
1406
  }
@@ -1379,82 +1408,59 @@
1379
1408
  .kyro-toast {
1380
1409
  display: flex;
1381
1410
  align-items: center;
1382
- gap: 12px;
1383
- min-width: 320px;
1384
- max-width: 420px;
1385
- padding: 12px;
1386
- background: rgba(11, 18, 34, 0.85);
1387
- backdrop-filter: blur(12px);
1388
- -webkit-backdrop-filter: blur(12px);
1411
+ gap: 8px;
1412
+ min-width: 240px;
1413
+ max-width: 340px;
1414
+ padding: 8px 10px;
1389
1415
  color: white;
1390
- border-radius: 16px;
1391
- border: 1px solid rgba(255, 255, 255, 0.1);
1392
- box-shadow:
1393
- 0 4px 6px -1px rgba(0, 0, 0, 0.1),
1394
- 0 20px 25px -5px rgba(0, 0, 0, 0.2);
1416
+ border-radius: 8px;
1417
+ box-shadow:
1418
+ 0 2px 4px -1px rgba(0, 0, 0, 0.08),
1419
+ 0 8px 16px -4px rgba(0, 0, 0, 0.15),
1420
+ 0 16px 24px -8px rgba(0, 0, 0, 0.2);
1395
1421
  pointer-events: auto;
1396
- overflow: hidden;
1397
1422
  position: relative;
1423
+ transition: box-shadow 0.2s ease;
1398
1424
  }
1399
1425
 
1400
- .dark .kyro-toast {
1401
- background: rgba(5, 9, 18, 0.9);
1402
- border: 1px solid rgba(255, 255, 255, 0.05);
1403
- }
1404
-
1405
- .kyro-toast-accent {
1406
- position: absolute;
1407
- left: 0;
1408
- top: 0;
1409
- bottom: 0;
1410
- width: 4px;
1411
- background: var(--kyro-primary);
1412
- }
1413
-
1414
- .kyro-toast-success .kyro-toast-accent { background: #10b981; }
1415
- .kyro-toast-error .kyro-toast-accent { background: #ef4444; }
1416
- .kyro-toast-warning .kyro-toast-accent { background: #f59e0b; }
1417
- .kyro-toast-info .kyro-toast-accent { background: #3b82f6; }
1426
+ .kyro-toast-success { background: #16a34a; }
1427
+ .kyro-toast-error { background: #dc2626; }
1428
+ .kyro-toast-warning { background: #d97706; }
1429
+ .kyro-toast-info { background: #4f46e5; }
1418
1430
 
1419
1431
  .kyro-toast-icon-container {
1420
1432
  flex-shrink: 0;
1421
- width: 32px;
1422
- height: 32px;
1423
- border-radius: 10px;
1424
- background: rgba(255, 255, 255, 0.05);
1433
+ width: 20px;
1434
+ height: 20px;
1425
1435
  display: flex;
1426
1436
  align-items: center;
1427
1437
  justify-content: center;
1428
- color: rgba(255, 255, 255, 0.9);
1438
+ color: rgba(255, 255, 255, 0.85);
1429
1439
  }
1430
1440
 
1431
- .kyro-toast-success .kyro-toast-icon-container { color: #10b981; background: rgba(16, 185, 129, 0.1); }
1432
- .kyro-toast-error .kyro-toast-icon-container { color: #f87171; background: rgba(239, 68, 68, 0.1); }
1433
- .kyro-toast-warning .kyro-toast-icon-container { color: #fbbf24; background: rgba(245, 158, 11, 0.1); }
1434
- .kyro-toast-info .kyro-toast-icon-container { color: #60a5fa; background: rgba(59, 130, 246, 0.1); }
1435
-
1436
1441
  .kyro-toast-content {
1437
1442
  flex: 1;
1438
1443
  min-width: 0;
1439
1444
  }
1440
1445
 
1441
1446
  .kyro-toast-message {
1442
- font-size: 13px;
1443
- font-weight: 600;
1447
+ font-size: 12px;
1448
+ font-weight: 500;
1444
1449
  letter-spacing: -0.01em;
1445
- color: rgba(255, 255, 255, 0.9);
1446
- line-height: 1.4;
1450
+ color: rgba(255, 255, 255, 0.92);
1451
+ line-height: 1.3;
1447
1452
  }
1448
1453
 
1449
1454
  .kyro-toast-close {
1450
- padding: 6px;
1451
- border-radius: 8px;
1452
- color: rgba(255, 255, 255, 0.4);
1453
- transition: all 0.2s ease;
1455
+ padding: 3px;
1456
+ border-radius: 4px;
1457
+ color: rgba(255, 255, 255, 0.35);
1458
+ transition: all 0.15s ease;
1459
+ flex-shrink: 0;
1454
1460
  }
1455
1461
 
1456
1462
  .kyro-toast-close:hover {
1457
- background: rgba(255, 255, 255, 0.1);
1463
+ background: rgba(255, 255, 255, 0.15);
1458
1464
  color: white;
1459
1465
  }
1460
1466
 
package/README.md DELETED
@@ -1,46 +0,0 @@
1
- # Kyro Admin MVP Extensibility
2
-
3
- This repository contains a minimal, upgrade-safe extension surface for @kyro-cms/admin.
4
-
5
- - Exposed modules: theme, hooks, plugins, blocks, fields, and the Astro integration (kyroAdmin).
6
- - MVP samples are included for quick experimentation:
7
- - MVP Hook samples: sampleHook, sampleHook2
8
- - MVP Block samples: sampleBlock, sampleBlock2
9
- - MVP Field samples: sampleField, sampleField2
10
- - MVP Plugin sample: samplePlugin
11
-
12
- How to try locally
13
-
14
- - Build:
15
- - Run: bun install
16
- - Run: bun run build
17
- - Dist artifacts will be emitted in admin/dist
18
- - Test:
19
- - Run: bun run test
20
- - Tests cover MVP hooks; additional MVP tests can be added later
21
-
22
- Usage notes
23
-
24
- - kyroAdmin() is available from @kyro-cms/admin/integration and can be wired into Astro config
25
- - MVP samples are designed to be initial templates for plugin authors; replace or extend with real logic
26
-
27
- Next steps
28
-
29
- - Decide on final API shape (per-feature exports vs central namespace)
30
- - Add more MVP samples for blocks/fields/plugins and write accompanying tests
31
- - Add documentation explaining how to author plugins and blocks
32
- - MVP samples are included for quick experimentation:
33
- - MVP Hook samples: sampleHook, sampleHook2
34
- - MVP Block samples: sampleBlock, sampleBlock2
35
- - MVP Field samples: sampleField, sampleField2
36
- - MVP Plugin sample: samplePlugin
37
-
38
- Export surface (MVP)
39
-
40
- - The MVP exports are available per module:
41
- - @kyro-cms/admin/hooks -> sampleHook, sampleHook2 (and lifecycle/data hooks)
42
- - @kyro-cms/admin/blocks -> sampleBlock, sampleBlock2 (and registry)
43
- - @kyro-cms/admin/fields -> sampleField, sampleField2 (and registry)
44
- - @kyro-cms/admin/plugins -> samplePlugin, samplePlugin2 (and registry)
45
- - The main integration (kyroAdmin) is available via @kyro-cms/admin/integration
46
- - The theme module is available via @kyro-cms/admin/theme