@kyro-cms/admin 0.9.0 → 0.9.2

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 (114) hide show
  1. package/dist/index.cjs +11715 -11292
  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 +564 -0
  6. package/dist/index.d.ts +11 -10
  7. package/dist/index.js +11326 -10912
  8. package/dist/index.js.map +1 -1
  9. package/package.json +16 -12
  10. package/src/components/ActionBar.tsx +25 -161
  11. package/src/components/Admin.tsx +2 -4
  12. package/src/components/ApiKeysManager.tsx +5 -5
  13. package/src/components/AuditLogsPage.tsx +2 -13
  14. package/src/components/AutoForm.tsx +572 -461
  15. package/src/components/BrandingHub.tsx +7 -4
  16. package/src/components/CreateView.tsx +2 -0
  17. package/src/components/DetailView.tsx +52 -65
  18. package/src/components/DeveloperCenter.tsx +8 -6
  19. package/src/components/FieldRenderer.tsx +94 -19
  20. package/src/components/ListView.tsx +57 -216
  21. package/src/components/MediaGallery.tsx +334 -367
  22. package/src/components/PluginsManager.tsx +197 -70
  23. package/src/components/RestPlayground.tsx +59 -52
  24. package/src/components/SessionsManager.tsx +1 -1
  25. package/src/components/SettingsPage.tsx +22 -0
  26. package/src/components/Sidebar.astro +13 -41
  27. package/src/components/UserManagement.tsx +153 -15
  28. package/src/components/UserMenu.tsx +30 -4
  29. package/src/components/VersionHistoryPanel.tsx +112 -119
  30. package/src/components/WebhookManager.tsx +6 -4
  31. package/src/components/blocks/ArrayBlock.tsx +6 -23
  32. package/src/components/blocks/BlockEditModal.tsx +82 -309
  33. package/src/components/blocks/CardBlock.tsx +35 -0
  34. package/src/components/blocks/ChildBlocksTree.tsx +57 -31
  35. package/src/components/blocks/GenericBlock.tsx +44 -0
  36. package/src/components/blocks/HeadingSubheadingBlock.tsx +32 -0
  37. package/src/components/blocks/HeroBlock.tsx +5 -14
  38. package/src/components/blocks/RichTextBlock.tsx +5 -5
  39. package/src/components/blocks/index.ts +5 -3
  40. package/src/components/fields/AccordionField.tsx +2 -2
  41. package/src/components/fields/ArrayField.tsx +1 -1
  42. package/src/components/fields/ArrayLayout.tsx +120 -29
  43. package/src/components/fields/BlocksField.tsx +433 -55
  44. package/src/components/fields/CardField.tsx +73 -0
  45. package/src/components/fields/CheckboxField.tsx +7 -3
  46. package/src/components/fields/DateField.tsx +4 -1
  47. package/src/components/fields/GroupLayout.tsx +2 -2
  48. package/src/components/fields/HeadingSubheadingField.tsx +43 -0
  49. package/src/components/fields/ListField.tsx +2 -2
  50. package/src/components/fields/NumberField.tsx +4 -1
  51. package/src/components/fields/RelationshipBlockField.tsx +2 -3
  52. package/src/components/fields/RelationshipField.tsx +155 -90
  53. package/src/components/fields/RichTextField.tsx +781 -0
  54. package/src/components/fields/SecretField.tsx +102 -0
  55. package/src/components/fields/SelectField.tsx +19 -6
  56. package/src/components/fields/TabsLayout.tsx +19 -9
  57. package/src/components/fields/TextField.tsx +4 -1
  58. package/src/components/fields/UploadField.tsx +122 -56
  59. package/src/components/fields/extensions/blockComponents.tsx +103 -174
  60. package/src/components/fields/extensions/blocksStore.ts +8 -1
  61. package/src/components/fields/index.ts +4 -2
  62. package/src/components/fix_imports.cjs +23 -0
  63. package/src/components/fix_imports2.cjs +19 -0
  64. package/src/components/replace_svgs.cjs +63 -0
  65. package/src/components/ui/Dropdown.tsx +7 -2
  66. package/src/components/ui/Modal.tsx +24 -27
  67. package/src/components/ui/PageHeader.tsx +5 -5
  68. package/src/components/ui/PromptModal.tsx +2 -10
  69. package/src/components/ui/SlidePanel.tsx +10 -13
  70. package/src/components/ui/SplitButton.tsx +107 -0
  71. package/src/components/ui/Toaster.tsx +0 -1
  72. package/src/components/ui/icons.tsx +110 -109
  73. package/src/components/users/UserDetail.tsx +79 -16
  74. package/src/components/users/UsersList.tsx +8 -85
  75. package/src/hooks/useAutoFormState.ts +187 -196
  76. package/src/hooks/useQueue.ts +60 -0
  77. package/src/integration.ts +148 -46
  78. package/src/kyro-cms.d.ts +7 -2
  79. package/src/layouts/AdminLayout.astro +22 -2
  80. package/src/layouts/AuthLayout.astro +67 -7
  81. package/src/lib/autoform-store.ts +90 -53
  82. package/src/lib/change-source.ts +9 -0
  83. package/src/lib/config.ts +104 -8
  84. package/src/lib/globals.ts +48 -11
  85. package/src/lib/normalize-upload-fields.ts +41 -0
  86. package/src/lib/paths.ts +2 -2
  87. package/src/lib/resolve-field-value.ts +110 -0
  88. package/src/lib/shim/use-sync-external-store-with-selector.js +30 -0
  89. package/src/lib/shim/use-sync-external-store.js +1 -0
  90. package/src/lib/stores/index.ts +1 -0
  91. package/src/lib/useResourceManager.ts +4 -4
  92. package/src/lib/vite-shim-plugin.ts +100 -0
  93. package/src/pages/[collection]/[id].astro +1 -1
  94. package/src/pages/auth/register.astro +5 -2
  95. package/src/pages/preview/[collection]/[id].astro +4 -4
  96. package/src/pages/settings/[slug].astro +2 -2
  97. package/src/styles/main.css +60 -54
  98. package/README.md +0 -46
  99. package/dist/EditorClient-Q23UXR37.cjs +0 -468
  100. package/dist/EditorClient-Q23UXR37.cjs.map +0 -1
  101. package/dist/EditorClient-T5PASFNR.js +0 -466
  102. package/dist/EditorClient-T5PASFNR.js.map +0 -1
  103. package/dist/chunk-3BGDYKTD.cjs +0 -348
  104. package/dist/chunk-3BGDYKTD.cjs.map +0 -1
  105. package/dist/chunk-EEFXLQVT.js +0 -3
  106. package/dist/chunk-EEFXLQVT.js.map +0 -1
  107. package/src/components/blocks/ButtonBlock.tsx +0 -64
  108. package/src/components/blocks/ColumnsBlock.tsx +0 -55
  109. package/src/components/blocks/DividerBlock.tsx +0 -43
  110. package/src/components/blocks/LinkBlock.tsx +0 -65
  111. package/src/components/blocks/VStackBlock.tsx +0 -29
  112. package/src/components/fields/EditorClient.tsx +0 -535
  113. package/src/components/fields/PortableTextField.tsx +0 -155
  114. package/src/components/fields/PortableTextRenderer.tsx +0 -68
@@ -1,247 +1,176 @@
1
1
  import React from "react";
2
- import { ColumnsBlock } from "../../blocks/ColumnsBlock";
3
2
  import { HeadingBlock } from "../../blocks/HeadingBlock";
4
3
  import { ParagraphBlock } from "../../blocks/ParagraphBlock";
5
- import { DividerBlock } from "../../blocks/DividerBlock";
6
4
  import { ImageBlock } from "../../blocks/ImageBlock";
7
5
  import { VideoBlock } from "../../blocks/VideoBlock";
8
6
  import { ListBlock } from "../../blocks/ListBlock";
9
7
  import { CodeBlock } from "../../blocks/CodeBlock";
10
- import { LinkBlock } from "../../blocks/LinkBlock";
11
8
  import { FileBlock } from "../../blocks/FileBlock";
12
- import { VStackBlock } from "../../blocks/VStackBlock";
13
- import { ButtonBlock } from "../../blocks/ButtonBlock";
14
9
  import { AccordionBlock } from "../../blocks/AccordionBlock";
15
10
  import { RichTextBlock } from "../../blocks/RichTextBlock";
16
11
 
17
12
  import { HeroBlock } from "../../blocks/HeroBlock";
13
+ import { HeadingSubheadingBlock } from "../../blocks/HeadingSubheadingBlock";
14
+ import { CardBlock } from "../../blocks/CardBlock";
18
15
  import { ArrayBlock } from "../../blocks/ArrayBlock";
19
16
  import { RelationshipBlock } from "../../blocks/RelationshipBlock";
20
17
 
21
18
  import {
22
- Columns3,
19
+ Box,
23
20
  Heading1,
24
21
  AlignLeft,
25
- Minus,
26
22
  Image,
27
23
  Video,
28
24
  List,
29
25
  Code,
30
- Link,
31
26
  File,
32
- ArrowDown,
33
- MousePointerClick,
34
27
  ChevronDown,
35
28
  Star,
36
29
  ListOrdered,
37
30
  Link2,
31
+ Columns3,
32
+ Sparkles,
33
+ Users,
34
+ HelpCircle,
35
+ Activity,
36
+ Tag,
37
+ Database,
38
+ Mail,
39
+ Blocks,
40
+ Clock,
38
41
  } from "../../ui/icons";
39
42
 
40
43
  // Block component registry
41
44
  export const BLOCK_COMPONENTS: Record<string, React.ComponentType<{ block: Record<string, unknown>; index: number }>> = {
42
- columns: ColumnsBlock,
43
45
  heading: HeadingBlock,
44
46
  paragraph: ParagraphBlock,
45
- divider: DividerBlock,
46
47
  image: ImageBlock,
47
48
  video: VideoBlock,
48
49
  list: ListBlock,
49
50
  code: CodeBlock,
50
- link: LinkBlock,
51
51
  file: FileBlock,
52
- vstack: VStackBlock,
53
- button: ButtonBlock,
54
52
  accordion: AccordionBlock,
55
53
  richtext: RichTextBlock,
56
54
 
57
55
  hero: HeroBlock,
56
+ "heading-subheading": HeadingSubheadingBlock,
57
+ card: CardBlock,
58
58
  array: ArrayBlock,
59
59
  relationship: RelationshipBlock,
60
60
  };
61
61
 
62
+ // Block Theme mapping for consistent coloring across UI
63
+ export const blockTheme: Record<string, { text: string; border: string; borderLeft: string }> = {
64
+ "feature-split": { text: "text-indigo-500", border: "border-indigo-500", borderLeft: "border-l-indigo-500" },
65
+ "feature-grid": { text: "text-blue-500", border: "border-blue-500", borderLeft: "border-l-blue-500" },
66
+ "cta-banner": { text: "text-amber-500", border: "border-amber-500", borderLeft: "border-l-amber-500" },
67
+ testimonials: { text: "text-emerald-500", border: "border-emerald-500", borderLeft: "border-l-emerald-500" },
68
+ faq: { text: "text-orange-500", border: "border-orange-500", borderLeft: "border-l-orange-500" },
69
+ stats: { text: "text-rose-500", border: "border-rose-500", borderLeft: "border-l-rose-500" },
70
+ "logo-cloud": { text: "text-cyan-500", border: "border-cyan-500", borderLeft: "border-l-cyan-500" },
71
+ pricing: { text: "text-green-500", border: "border-green-500", borderLeft: "border-l-green-500" },
72
+ team: { text: "text-violet-500", border: "border-violet-500", borderLeft: "border-l-violet-500" },
73
+ "recent-feed": { text: "text-sky-500", border: "border-sky-500", borderLeft: "border-l-sky-500" },
74
+ "process-steps": { text: "text-fuchsia-500", border: "border-fuchsia-500", borderLeft: "border-l-fuchsia-500" },
75
+ "form-embed": { text: "text-pink-500", border: "border-pink-500", borderLeft: "border-l-pink-500" },
76
+ "video-showcase": { text: "text-red-500", border: "border-red-500", borderLeft: "border-l-red-500" },
77
+ hero: { text: "text-yellow-500", border: "border-yellow-500", borderLeft: "border-l-yellow-500" },
78
+ card: { text: "text-teal-500", border: "border-teal-500", borderLeft: "border-l-teal-500" },
79
+ default: { text: "text-zinc-400", border: "border-zinc-400", borderLeft: "border-l-zinc-400" },
80
+ };
81
+
62
82
  // Icon mapping for drawer (actual Lucide components)
63
83
  export const blockIcons: Record<string, React.ReactNode> = {
64
- columns: <Columns3 className="w-4 h-4" />,
65
- heading: <Heading1 className="w-4 h-4" />,
66
- paragraph: <AlignLeft className="w-4 h-4" />,
67
- divider: <Minus className="w-4 h-4" />,
68
- image: <Image className="w-4 h-4" />,
69
- video: <Video className="w-4 h-4" />,
70
- list: <List className="w-4 h-4" />,
71
- code: <Code className="w-4 h-4" />,
72
- link: <Link className="w-4 h-4" />,
73
- file: <File className="w-4 h-4" />,
74
- vstack: <ArrowDown className="w-4 h-4" />,
75
- button: <MousePointerClick className="w-4 h-4" />,
76
- accordion: <ChevronDown className="w-4 h-4" />,
77
- richtext: <AlignLeft className="w-4 h-4" />,
84
+ heading: <Heading1 className={`w-4 h-4 ${blockTheme.default.text}`} />,
85
+ paragraph: <AlignLeft className={`w-4 h-4 ${blockTheme.default.text}`} />,
86
+ image: <Image className={`w-4 h-4 ${blockTheme.default.text}`} />,
87
+ video: <Video className={`w-4 h-4 ${blockTheme.default.text}`} />,
88
+ list: <List className={`w-4 h-4 ${blockTheme.default.text}`} />,
89
+ code: <Code className={`w-4 h-4 ${blockTheme.default.text}`} />,
90
+ file: <File className={`w-4 h-4 ${blockTheme.default.text}`} />,
91
+ accordion: <ChevronDown className={`w-4 h-4 ${blockTheme.default.text}`} />,
92
+ richtext: <AlignLeft className={`w-4 h-4 ${blockTheme.default.text}`} />,
93
+
94
+ hero: <Star className={`w-4 h-4 ${blockTheme.hero.text}`} />,
95
+ "heading-subheading": <Heading1 className={`w-4 h-4 ${blockTheme.default.text}`} />,
96
+ card: <Box className={`w-4 h-4 ${blockTheme.card.text}`} />,
97
+ array: <ListOrdered className={`w-4 h-4 ${blockTheme.default.text}`} />,
98
+ relationship: <Link2 className={`w-4 h-4 ${blockTheme.default.text}`} />,
78
99
 
79
- hero: <Star className="w-4 h-4" />,
80
- array: <ListOrdered className="w-4 h-4" />,
81
- relationship: <Link2 className="w-4 h-4" />,
100
+ // New Block Icons
101
+ "feature-split": <Columns3 className={`w-4 h-4 ${blockTheme["feature-split"].text}`} />,
102
+ "feature-grid": <Blocks className={`w-4 h-4 ${blockTheme["feature-grid"].text}`} />,
103
+ "cta-banner": <Sparkles className={`w-4 h-4 ${blockTheme["cta-banner"].text}`} />,
104
+ testimonials: <Users className={`w-4 h-4 ${blockTheme.testimonials.text}`} />,
105
+ faq: <HelpCircle className={`w-4 h-4 ${blockTheme.faq.text}`} />,
106
+ stats: <Activity className={`w-4 h-4 ${blockTheme.stats.text}`} />,
107
+ "logo-cloud": <Image className={`w-4 h-4 ${blockTheme["logo-cloud"].text}`} />,
108
+ pricing: <Tag className={`w-4 h-4 ${blockTheme.pricing.text}`} />,
109
+ team: <Users className={`w-4 h-4 ${blockTheme.team.text}`} />,
110
+ "recent-feed": <Database className={`w-4 h-4 ${blockTheme["recent-feed"].text}`} />,
111
+ "process-steps": <Clock className={`w-4 h-4 ${blockTheme["process-steps"].text}`} />,
112
+ "form-embed": <Mail className={`w-4 h-4 ${blockTheme["form-embed"].text}`} />,
113
+ "video-showcase": <Video className={`w-4 h-4 ${blockTheme["video-showcase"].text}`} />,
82
114
  };
83
115
 
84
- // Get block component by type
116
+ // Internal utility to check if block has a specific custom react component
85
117
  export function getBlockComponent(type: string) {
86
- return BLOCK_COMPONENTS[type];
118
+ return BLOCK_COMPONENTS[type] || null;
87
119
  }
88
120
 
89
- // Check if block type is supported
90
- export function isSupportedBlockType(type: string): boolean {
91
- return type in BLOCK_COMPONENTS;
121
+ // Determines if block is a native layout requiring generic rendering
122
+ export function isGenericSemanticBlock(type: string) {
123
+ return [
124
+ "feature-split", "feature-grid", "cta-banner",
125
+ "testimonials", "faq", "stats", "logo-cloud",
126
+ "pricing", "team", "recent-feed", "process-steps", "form-embed", "video-showcase"
127
+ ].includes(type);
92
128
  }
93
129
 
94
130
  // Get human-readable label for block type
95
131
  export function getBlockLabel(type: string): string {
96
132
  const labelMap: Record<string, string> = {
133
+ // Primitives
97
134
  paragraph: "Paragraph",
98
135
  heading: "Heading",
99
136
  image: "Image",
100
137
  video: "Video",
101
- link: "Link",
102
- button: "Button",
103
138
  list: "List",
104
139
  code: "Code",
105
140
  file: "File",
106
- divider: "Divider",
107
141
  accordion: "Accordion",
108
142
  array: "Repeater",
109
- hero: "Hero",
110
- vstack: "VStack",
111
- columns: "Columns",
112
143
  relationship: "Relationship",
113
144
  richtext: "Rich Text",
145
+
146
+ // Core Semantic Blocks
147
+ hero: "Hero Section",
148
+ "heading-subheading": "Heading + Subheading",
149
+ card: "Card Block",
150
+ "feature-split": "Feature Split",
151
+ "feature-grid": "Feature Grid",
152
+ "cta-banner": "CTA Banner",
153
+ testimonials: "Testimonials Stack",
154
+ faq: "FAQ Section",
155
+ stats: "Stats & Metrics",
156
+ "logo-cloud": "Logo Cloud",
157
+
158
+ // Brand New Blocks
159
+ pricing: "Pricing Grid / Plan",
160
+ team: "Team Profiles Showcase",
161
+ "recent-feed": "Dynamic Content Feed",
162
+ "process-steps": "Process Timeline / Steps",
163
+ "form-embed": "Lead Intake Form",
164
+ "video-showcase": "Cinematic Video Showcase",
165
+
166
+ // Inline Content Elements
167
+ "heading-element": "Heading",
168
+ "text-element": "Text",
169
+ "image-element": "Image",
170
+ "richtext-element": "Rich Text",
171
+ "button-element": "Button",
172
+ "video-element": "Video",
173
+ "list-element": "List",
114
174
  };
115
175
  return labelMap[type] || type;
116
176
  }
117
-
118
- // Block categories for the drawer
119
- export const blockCategories = [
120
- {
121
- title: "Layout",
122
- blocks: [
123
- {
124
- type: "columns",
125
- label: "Columns",
126
- icon: "columns",
127
- description: "1-6 columns side-by-side",
128
- },
129
- {
130
- type: "vstack",
131
- label: "VStack",
132
- icon: "vstack",
133
- description: "Stack blocks vertically",
134
- },
135
- {
136
- type: "hero",
137
- label: "Hero",
138
- icon: "hero",
139
- description: "Hero with content + video",
140
- },
141
- ],
142
- },
143
- {
144
- title: "Text",
145
- blocks: [
146
- {
147
- type: "heading",
148
- label: "Heading",
149
- icon: "heading",
150
- description: "Heading text",
151
- },
152
- {
153
- type: "paragraph",
154
- label: "Paragraph",
155
- icon: "paragraph",
156
- description: "Plain text content",
157
- },
158
- {
159
- type: "richtext",
160
- label: "Rich Text",
161
- icon: "richtext",
162
- description: "Formatted text with links & styles",
163
- },
164
- {
165
- type: "list",
166
- label: "List",
167
- icon: "list",
168
- description: "Ordered/unordered list",
169
- },
170
- { type: "link", label: "Link", icon: "link", description: "Hyperlink" },
171
- ],
172
- },
173
- {
174
- title: "Media",
175
- blocks: [
176
- {
177
- type: "image",
178
- label: "Image",
179
- icon: "image",
180
- description: "Single image",
181
- },
182
- {
183
- type: "video",
184
- label: "Video",
185
- icon: "video",
186
- description: "Embed video",
187
- },
188
- {
189
- type: "file",
190
- label: "File",
191
- icon: "file",
192
- description: "File download link",
193
- },
194
- ],
195
- },
196
- {
197
- title: "Interactive",
198
- blocks: [
199
- {
200
- type: "button",
201
- label: "Button",
202
- icon: "button",
203
- description: "CTA button",
204
- },
205
- {
206
- type: "accordion",
207
- label: "Accordion",
208
- icon: "accordion",
209
- description: "Collapsible sections",
210
- },
211
- ],
212
- },
213
- {
214
- title: "Data",
215
- blocks: [
216
- {
217
- type: "array",
218
- label: "Repeater",
219
- icon: "array",
220
- description: "Add multiple child blocks",
221
- },
222
- {
223
- type: "code",
224
- label: "Code",
225
- icon: "code",
226
- description: "Code snippet",
227
- },
228
- {
229
- type: "relationship",
230
- label: "Relationship",
231
- icon: "relationship",
232
- description: "Link to other collection",
233
- },
234
- ],
235
- },
236
- {
237
- title: "Basic",
238
- blocks: [
239
- {
240
- type: "divider",
241
- label: "Divider",
242
- icon: "divider",
243
- description: "Horizontal separator",
244
- },
245
- ],
246
- },
247
- ];
@@ -12,15 +12,19 @@ export interface BlocksStore {
12
12
  moveBlock: (id: string, direction: "up" | "down") => void;
13
13
  onBlocksChange: (() => void) | null;
14
14
  setOnBlocksChange: (cb: () => void) => void;
15
+ allowedBlocks: any[];
16
+ dynamicCategories: { title: string; blocks: any[] }[];
15
17
  }
16
18
 
17
19
  export type BlocksStoreApi = StoreApi<BlocksStore>;
18
20
 
19
21
  export const BlocksContext = createContext<BlocksStoreApi | null>(null);
20
22
 
21
- export function createBlocksStore(): BlocksStoreApi {
23
+ export function createBlocksStore(allowedBlocks: any[] = [], dynamicCategories: any[] = []): BlocksStoreApi {
22
24
  return createStore<BlocksStore>((set, get) => ({
23
25
  blocks: [],
26
+ allowedBlocks,
27
+ dynamicCategories,
24
28
  setBlocks: (blocks) => {
25
29
  const ensuredBlocks = ensureIds(blocks || []);
26
30
  set({ blocks: ensuredBlocks });
@@ -152,6 +156,9 @@ export function createNewBlock(type: string): BlockData {
152
156
  function getDefaultData(type: string): Record<string, unknown> {
153
157
  const defaults: Record<string, unknown> = {
154
158
  heading: { level: 1, text: "" },
159
+ "heading-subheading": { heading: "", subheading: "" },
160
+ hero: { isMultiScreen: false },
161
+ card: { title: "", description: "", icon: "", link: "", linkText: "", isMultiCard: false },
155
162
  paragraph: { text: "" },
156
163
  divider: {},
157
164
  callout: { text: "", variant: "info" },
@@ -1,8 +1,8 @@
1
- export { default as PortableTextField } from "./PortableTextField";
2
- export { PortableTextRenderer } from "./PortableTextRenderer";
1
+ export { default as RichTextField } from "./RichTextField";
3
2
  export { CodeField } from "./CodeField";
4
3
  export { JSONField } from "./JSONField";
5
4
  export { MarkdownField } from "./MarkdownField";
5
+ export { default as SecretField } from "./SecretField";
6
6
  export { default as TextField } from "./TextField";
7
7
  export { default as NumberField } from "./NumberField";
8
8
  export { default as CheckboxField } from "./CheckboxField";
@@ -22,3 +22,5 @@ export { ArrayField } from "./ArrayField";
22
22
  export { ChildrenField } from "./ChildrenField";
23
23
  export { ColumnsField } from "./ColumnsField";
24
24
  export { RelationshipBlockField } from "./RelationshipBlockField";
25
+ export { HeadingSubheadingField } from "./HeadingSubheadingField";
26
+ export { CardField } from "./CardField";
@@ -0,0 +1,23 @@
1
+ const fs = require('fs');
2
+
3
+ const addImport = (file, icons, relativePath) => {
4
+ let content = fs.readFileSync(file, 'utf8');
5
+ if (!content.includes('from "' + relativePath + '"')) {
6
+ const importStatement = `import { ${icons.join(', ')} } from "${relativePath}";\n`;
7
+ content = importStatement + content;
8
+ fs.writeFileSync(file, content);
9
+ } else {
10
+ // If it exists, we might need to append to the existing import.
11
+ // To be safe and simple, let's just add a new line. TS merges imports.
12
+ const importStatement = `import { ${icons.join(', ')} } from "${relativePath}";\n`;
13
+ content = importStatement + content;
14
+ fs.writeFileSync(file, content);
15
+ }
16
+ }
17
+
18
+ addImport('/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/components/MediaGallery.tsx', ['Search', 'Check', 'Server'], './ui/icons');
19
+ addImport('/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/components/ui/Modal.tsx', ['X'], './icons');
20
+ addImport('/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/components/ui/PromptModal.tsx', ['X'], './icons');
21
+ addImport('/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/components/ui/SlidePanel.tsx', ['X'], './icons');
22
+ addImport('/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/components/users/UsersList.tsx', ['Plus', 'Lock', 'CheckCircle2', 'Edit2', 'Trash2', 'XCircle', 'X'], '../ui/icons');
23
+
@@ -0,0 +1,19 @@
1
+ const fs = require('fs');
2
+
3
+ const addImport = (file, icons, relativePath) => {
4
+ let content = fs.readFileSync(file, 'utf8');
5
+ if (!content.includes('from "' + relativePath + '"')) {
6
+ const importStatement = `import { ${icons.join(', ')} } from "${relativePath}";\n`;
7
+ content = importStatement + content;
8
+ fs.writeFileSync(file, content);
9
+ } else {
10
+ const importStatement = `import { ${icons.join(', ')} } from "${relativePath}";\n`;
11
+ content = importStatement + content;
12
+ fs.writeFileSync(file, content);
13
+ }
14
+ }
15
+
16
+ addImport('/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/components/AuditLogsPage.tsx', ['Search'], './ui/icons');
17
+ addImport('/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/components/AutoForm.tsx', ['ChevronRight', 'Check', 'ExternalLink', 'X'], './ui/icons');
18
+ addImport('/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/components/ListView.tsx', ['Search', 'Filter', 'Columns3', 'X', 'Trash2', 'Archive', 'ChevronUp', 'Edit2'], './ui/icons');
19
+
@@ -0,0 +1,63 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const dir = '/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/components';
5
+ const getFiles = (dir) => {
6
+ const dirents = fs.readdirSync(dir, { withFileTypes: true });
7
+ const files = dirents.map((dirent) => {
8
+ const res = path.resolve(dir, dirent.name);
9
+ return dirent.isDirectory() ? getFiles(res) : res;
10
+ });
11
+ return Array.prototype.concat(...files).filter(f => f.endsWith('.tsx'));
12
+ };
13
+
14
+ const files = getFiles(dir);
15
+
16
+ const replacements = [
17
+ { match: /<svg[^>]*>\s*<path[^>]*d="M12 4v16m8-8H4"[^>]*\/>\s*<\/svg>/g, replace: '<Plus className="w-4 h-4" />', icon: 'Plus' },
18
+ { match: /<svg[^>]*>\s*<path[^>]*d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"[^>]*\/>\s*<\/svg>/g, replace: '<Upload className="w-4 h-4" />', icon: 'Upload' },
19
+ { match: /<svg[^>]*>\s*<path[^>]*d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"[^>]*\/>\s*<\/svg>/g, replace: '<Copy className="w-4 h-4" />', icon: 'Copy' },
20
+ { match: /<svg[^>]*>\s*<path[^>]*d="M19 7l-\.867 12\.142A2 2 0 0116\.138 21H7\.862a2 2 0 01-1\.995-1\.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"[^>]*\/>\s*<\/svg>/g, replace: '<Trash2 className="w-4 h-4" />', icon: 'Trash2' },
21
+ { match: /<svg[^>]*>\s*<path[^>]*d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"[^>]*\/>\s*<\/svg>/g, replace: '<Lock className="w-4 h-4" />', icon: 'Lock' },
22
+ { match: /<svg[^>]*>\s*<path[^>]*d="M10 18a8 8 0 100-16 8 8 0 000 16zm3\.707-9\.293a1 1 0 00-1\.414-1\.414L9 10\.586 7\.707 9\.293a1 1 0 00-1\.414 1\.414l2 2a1 1 0 001\.414 0l4-4z"[^>]*\/>\s*<\/svg>/g, replace: '<CheckCircle2 className="w-4 h-4" />', icon: 'CheckCircle2' },
23
+ { match: /<svg[^>]*>\s*<path[^>]*d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1\.414-9\.414a2 2 0 112\.828 2\.828L11\.828 15H9v-2\.828l8\.586-8\.586z"[^>]*\/>\s*<\/svg>/g, replace: '<Edit2 className="w-4 h-4" />', icon: 'Edit2' },
24
+ { match: /<svg[^>]*>\s*<path[^>]*d="M10 18a8 8 0 100-16 8 8 0 000 16zM8\.707 7\.293a1 1 0 00-1\.414 1\.414L8\.586 10l-1\.293 1\.293a1 1 0 101\.414 1\.414L10 11\.414l1\.293 1\.293a1 1 0 001\.414-1\.414L11\.414 10l1\.293-1\.293a1 1 0 00-1\.414-1\.414L10 8\.586 8\.707 7\.293z"[^>]*\/>\s*<\/svg>/g, replace: '<XCircle className="w-4 h-4" />', icon: 'XCircle' },
25
+ { match: /<svg[^>]*>\s*<path[^>]*d="M6 18L18 6M6 6l12 12"[^>]*\/>\s*<\/svg>/g, replace: '<X className="w-4 h-4" />', icon: 'X' },
26
+ { match: /<svg[^>]*>\s*<path[^>]*d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"[^>]*\/>\s*<\/svg>/g, replace: '<Search className="w-4 h-4" />', icon: 'Search' },
27
+ { match: /<svg[^>]*>\s*<path[^>]*d="M5 13l4 4L19 7"[^>]*\/>\s*<\/svg>/g, replace: '<Check className="w-4 h-4" />', icon: 'Check' },
28
+ { match: /<svg[^>]*>\s*<path[^>]*d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2\.586a1 1 0 01-\.293\.707l-6\.414 6\.414a1 1 0 00-\.293\.707V17l-4 4v-6\.586a1 1 0 00-\.293-\.707L3\.293 7\.293A1 1 0 013 6\.586V4z"[^>]*\/>\s*<\/svg>/g, replace: '<Filter className="w-4 h-4" />', icon: 'Filter' },
29
+ { match: /<svg[^>]*>\s*<path[^>]*d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2"[^>]*\/>\s*<\/svg>/g, replace: '<Columns3 className="w-4 h-4" />', icon: 'Columns3' },
30
+ { match: /<svg[^>]*>\s*<path[^>]*d="M12 5v14M5 12h14"[^>]*\/>\s*<\/svg>/g, replace: '<Plus className="w-4 h-4" />', icon: 'Plus' },
31
+ { match: /<svg[^>]*>\s*<path[^>]*d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"[^>]*\/>\s*<\/svg>/g, replace: '<Archive className="w-4 h-4" />', icon: 'Archive' },
32
+ { match: /<svg[^>]*>\s*<path[^>]*d="M5 15l7-7 7 7"[^>]*\/>\s*<\/svg>/g, replace: '<ChevronUp className="w-4 h-4" />', icon: 'ChevronUp' },
33
+ { match: /<svg[^>]*>\s*<path[^>]*d="M15 19l-7-7 7-7"[^>]*\/>\s*<\/svg>/g, replace: '<ChevronRight className="w-4 h-4" />', icon: 'ChevronRight' },
34
+ { match: /<svg[^>]*>\s*<path[^>]*d="M20 6L9 17l-5-5"[^>]*\/>\s*<\/svg>/g, replace: '<Check className="w-4 h-4" />', icon: 'Check' },
35
+ { match: /<svg[^>]*>\s*<path[^>]*d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3"[^>]*\/>\s*<\/svg>/g, replace: '<ExternalLink className="w-4 h-4" />', icon: 'ExternalLink' },
36
+ { match: /<svg[^>]*>\s*<path[^>]*d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"[^>]*\/>\s*<\/svg>/g, replace: '<File className="w-4 h-4" />', icon: 'File' },
37
+ { match: /<svg[^>]*>\s*<path[^>]*d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"[^>]*\/>\s*<\/svg>/g, replace: '<Clipboard className="w-4 h-4" />', icon: 'Clipboard' },
38
+ { match: /<svg[^>]*>\s*<path[^>]*d="M18 6L6 18M6 6l12 12"[^>]*\/>\s*<\/svg>/g, replace: '<X className="w-4 h-4" />', icon: 'X' },
39
+ { match: /<svg[^>]*>\s*<path[^>]*d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2H5a2 2 0 00-2 2v1m2 2a2 2 0 11-4 0 2 2 0 014 0zm2 2h\.008v\.008H5v-\.008z"[^>]*\/>\s*<\/svg>/g, replace: '<Server className="w-8 h-8 text-[var(--kyro-sidebar-text-active)]" />', icon: 'Server' }
40
+ ];
41
+
42
+ const results = {};
43
+
44
+ for (const file of files) {
45
+ let content = fs.readFileSync(file, 'utf8');
46
+ let changed = false;
47
+ let addedIcons = new Set();
48
+
49
+ for (const r of replacements) {
50
+ if (content.match(r.match)) {
51
+ content = content.replace(r.match, r.replace);
52
+ addedIcons.add(r.icon);
53
+ changed = true;
54
+ }
55
+ }
56
+
57
+ if (changed) {
58
+ results[file] = Array.from(addedIcons);
59
+ fs.writeFileSync(file, content);
60
+ }
61
+ }
62
+
63
+ console.log(JSON.stringify(results, null, 2));
@@ -4,12 +4,14 @@ interface DropdownProps {
4
4
  trigger: ReactNode;
5
5
  children: ReactNode;
6
6
  align?: "left" | "right";
7
+ direction?: "up" | "down";
7
8
  }
8
9
 
9
10
  export function Dropdown({
10
11
  trigger,
11
12
  children,
12
13
  align = "right",
14
+ direction = "up",
13
15
  }: DropdownProps) {
14
16
  const [open, setOpen] = useState(false);
15
17
  const ref = useRef<HTMLDivElement>(null);
@@ -34,8 +36,11 @@ export function Dropdown({
34
36
  </div>
35
37
  {open && (
36
38
  <div
37
- className={`absolute z-[100] mt-2 min-w-[200px] py-2 bg-[var(--kyro-surface)] rounded-2xl shadow-2xl border border-[var(--kyro-border)] animate-in fade-in zoom-in-95 duration-100 ${align === "right" ? "right-0 bottom-full mb-2" : "left-0 bottom-full mb-2"
38
- }`}
39
+ className={`absolute z-[100] min-w-[200px] py-2 bg-[var(--kyro-surface)] rounded-2xl shadow-2xl border border-[var(--kyro-border)] animate-in fade-in zoom-in-95 duration-100 ${
40
+ align === "right" ? "right-0" : "left-0"
41
+ } ${
42
+ direction === "down" ? "top-full mt-2" : "bottom-full mb-2"
43
+ }`}
39
44
  onClick={() => setOpen(false)}
40
45
  >
41
46
  {children}
@@ -1,3 +1,4 @@
1
+ import { X } from "./icons";
1
2
  import React, { useEffect, type ReactNode } from "react";
2
3
  import { createPortal } from "react-dom";
3
4
 
@@ -17,8 +18,8 @@ interface ModalProps {
17
18
  title: string;
18
19
  children: ReactNode;
19
20
  footer?: ReactNode;
20
- size?: "sm" | "md" | "lg";
21
- variant?: "default" | "danger";
21
+ size?: "sm" | "md" | "lg" | "full";
22
+ variant?: "default" | "danger" | "lightbox";
22
23
  }
23
24
 
24
25
  export function Modal({
@@ -52,43 +53,39 @@ export function Modal({
52
53
  sm: "max-w-sm",
53
54
  md: "max-w-md",
54
55
  lg: "max-w-lg",
56
+ full: "w-full h-full max-w-none rounded-none border-0",
55
57
  };
56
58
 
59
+ const isLightbox = variant === "lightbox";
60
+
57
61
  return createPortal(
58
62
  <div className="fixed inset-0 z-[9999] flex items-center justify-center p-4">
59
63
  <div
60
- className="absolute inset-0 bg-[var(--kyro-black)]/40 backdrop-blur-md transition-all duration-500"
64
+ className={`absolute inset-0 transition-all duration-500 ${isLightbox ? "bg-black/95 backdrop-blur-none" : "bg-[var(--kyro-black)]/40 backdrop-blur-md"}`}
61
65
  onClick={onClose}
62
66
  />
63
67
  <div
64
- className={`relative w-full ${sizeClasses[size]} bg-[var(--kyro-surface)] rounded-[var(--kyro-radius-lg)] shadow-2xl animate-in fade-in zoom-in-95 duration-300 border ${variant === "danger"
68
+ className={`relative ${sizeClasses[size]} ${isLightbox ? "bg-transparent text-white" : "bg-[var(--kyro-surface)]"} ${!isLightbox && size !== "full" ? "rounded-[var(--kyro-radius-lg)]" : ""} shadow-2xl animate-in fade-in zoom-in-95 duration-300 ${!isLightbox ? "border" : ""} ${variant === "danger"
65
69
  ? "border-red-500/30"
66
70
  : "border-[var(--kyro-border)]"
67
- } overflow-hidden`}
71
+ } flex flex-col overflow-hidden`}
68
72
  >
69
- <div className="flex items-center justify-between px-8 py-6 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]/50 backdrop-blur-md">
70
- <h2 className="text-xl font-bold text-[var(--kyro-text-primary)]">
71
- {title}
72
- </h2>
73
- <button
74
- type="button"
75
- onClick={onClose}
76
- className="p-2 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] rounded-xl hover:bg-[var(--kyro-surface)] transition-all duration-200"
77
- >
78
- <svg
79
- width="20"
80
- height="20"
81
- viewBox="0 0 24 24"
82
- fill="none"
83
- stroke="currentColor"
84
- strokeWidth="2.5"
73
+ {!isLightbox && (
74
+ <div className="flex items-center justify-between px-8 py-6 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]/50 backdrop-blur-md">
75
+ <h2 className="text-xl font-bold text-[var(--kyro-text-primary)]">
76
+ {title}
77
+ </h2>
78
+ <button
79
+ type="button"
80
+ onClick={onClose}
81
+ className="p-2 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] rounded-xl hover:bg-[var(--kyro-surface)] transition-all duration-200"
85
82
  >
86
- <path d="M18 6L6 18M6 6l12 12" />
87
- </svg>
88
- </button>
89
- </div>
90
- <div className="px-8 py-8">{children}</div>
91
- {footer && (
83
+ <X className="w-4 h-4" />
84
+ </button>
85
+ </div>
86
+ )}
87
+ <div className={`flex-1 overflow-auto ${isLightbox ? "" : "px-8 py-8"}`}>{children}</div>
88
+ {footer && !isLightbox && (
92
89
  <div className="flex items-center justify-end gap-3 px-8 py-6 border-t border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]/50">
93
90
  {footer}
94
91
  </div>