@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.
- package/dist/index.cjs +11715 -11292
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +67 -65
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +564 -0
- package/dist/index.d.ts +11 -10
- package/dist/index.js +11326 -10912
- package/dist/index.js.map +1 -1
- package/package.json +16 -12
- package/src/components/ActionBar.tsx +25 -161
- package/src/components/Admin.tsx +2 -4
- package/src/components/ApiKeysManager.tsx +5 -5
- package/src/components/AuditLogsPage.tsx +2 -13
- package/src/components/AutoForm.tsx +572 -461
- package/src/components/BrandingHub.tsx +7 -4
- package/src/components/CreateView.tsx +2 -0
- package/src/components/DetailView.tsx +52 -65
- package/src/components/DeveloperCenter.tsx +8 -6
- package/src/components/FieldRenderer.tsx +94 -19
- package/src/components/ListView.tsx +57 -216
- package/src/components/MediaGallery.tsx +334 -367
- package/src/components/PluginsManager.tsx +197 -70
- package/src/components/RestPlayground.tsx +59 -52
- package/src/components/SessionsManager.tsx +1 -1
- package/src/components/SettingsPage.tsx +22 -0
- package/src/components/Sidebar.astro +13 -41
- package/src/components/UserManagement.tsx +153 -15
- package/src/components/UserMenu.tsx +30 -4
- package/src/components/VersionHistoryPanel.tsx +112 -119
- package/src/components/WebhookManager.tsx +6 -4
- package/src/components/blocks/ArrayBlock.tsx +6 -23
- package/src/components/blocks/BlockEditModal.tsx +82 -309
- package/src/components/blocks/CardBlock.tsx +35 -0
- package/src/components/blocks/ChildBlocksTree.tsx +57 -31
- package/src/components/blocks/GenericBlock.tsx +44 -0
- package/src/components/blocks/HeadingSubheadingBlock.tsx +32 -0
- package/src/components/blocks/HeroBlock.tsx +5 -14
- package/src/components/blocks/RichTextBlock.tsx +5 -5
- package/src/components/blocks/index.ts +5 -3
- package/src/components/fields/AccordionField.tsx +2 -2
- package/src/components/fields/ArrayField.tsx +1 -1
- package/src/components/fields/ArrayLayout.tsx +120 -29
- package/src/components/fields/BlocksField.tsx +433 -55
- package/src/components/fields/CardField.tsx +73 -0
- package/src/components/fields/CheckboxField.tsx +7 -3
- package/src/components/fields/DateField.tsx +4 -1
- package/src/components/fields/GroupLayout.tsx +2 -2
- package/src/components/fields/HeadingSubheadingField.tsx +43 -0
- package/src/components/fields/ListField.tsx +2 -2
- package/src/components/fields/NumberField.tsx +4 -1
- package/src/components/fields/RelationshipBlockField.tsx +2 -3
- package/src/components/fields/RelationshipField.tsx +155 -90
- package/src/components/fields/RichTextField.tsx +781 -0
- package/src/components/fields/SecretField.tsx +102 -0
- package/src/components/fields/SelectField.tsx +19 -6
- package/src/components/fields/TabsLayout.tsx +19 -9
- package/src/components/fields/TextField.tsx +4 -1
- package/src/components/fields/UploadField.tsx +122 -56
- package/src/components/fields/extensions/blockComponents.tsx +103 -174
- package/src/components/fields/extensions/blocksStore.ts +8 -1
- package/src/components/fields/index.ts +4 -2
- package/src/components/fix_imports.cjs +23 -0
- package/src/components/fix_imports2.cjs +19 -0
- package/src/components/replace_svgs.cjs +63 -0
- package/src/components/ui/Dropdown.tsx +7 -2
- package/src/components/ui/Modal.tsx +24 -27
- package/src/components/ui/PageHeader.tsx +5 -5
- package/src/components/ui/PromptModal.tsx +2 -10
- package/src/components/ui/SlidePanel.tsx +10 -13
- package/src/components/ui/SplitButton.tsx +107 -0
- package/src/components/ui/Toaster.tsx +0 -1
- package/src/components/ui/icons.tsx +110 -109
- package/src/components/users/UserDetail.tsx +79 -16
- package/src/components/users/UsersList.tsx +8 -85
- package/src/hooks/useAutoFormState.ts +187 -196
- package/src/hooks/useQueue.ts +60 -0
- package/src/integration.ts +148 -46
- package/src/kyro-cms.d.ts +7 -2
- package/src/layouts/AdminLayout.astro +22 -2
- package/src/layouts/AuthLayout.astro +67 -7
- package/src/lib/autoform-store.ts +90 -53
- package/src/lib/change-source.ts +9 -0
- package/src/lib/config.ts +104 -8
- package/src/lib/globals.ts +48 -11
- package/src/lib/normalize-upload-fields.ts +41 -0
- package/src/lib/paths.ts +2 -2
- package/src/lib/resolve-field-value.ts +110 -0
- package/src/lib/shim/use-sync-external-store-with-selector.js +30 -0
- package/src/lib/shim/use-sync-external-store.js +1 -0
- package/src/lib/stores/index.ts +1 -0
- package/src/lib/useResourceManager.ts +4 -4
- package/src/lib/vite-shim-plugin.ts +100 -0
- package/src/pages/[collection]/[id].astro +1 -1
- package/src/pages/auth/register.astro +5 -2
- package/src/pages/preview/[collection]/[id].astro +4 -4
- package/src/pages/settings/[slug].astro +2 -2
- package/src/styles/main.css +60 -54
- package/README.md +0 -46
- package/dist/EditorClient-Q23UXR37.cjs +0 -468
- package/dist/EditorClient-Q23UXR37.cjs.map +0 -1
- package/dist/EditorClient-T5PASFNR.js +0 -466
- package/dist/EditorClient-T5PASFNR.js.map +0 -1
- package/dist/chunk-3BGDYKTD.cjs +0 -348
- package/dist/chunk-3BGDYKTD.cjs.map +0 -1
- package/dist/chunk-EEFXLQVT.js +0 -3
- package/dist/chunk-EEFXLQVT.js.map +0 -1
- package/src/components/blocks/ButtonBlock.tsx +0 -64
- package/src/components/blocks/ColumnsBlock.tsx +0 -55
- package/src/components/blocks/DividerBlock.tsx +0 -43
- package/src/components/blocks/LinkBlock.tsx +0 -65
- package/src/components/blocks/VStackBlock.tsx +0 -29
- package/src/components/fields/EditorClient.tsx +0 -535
- package/src/components/fields/PortableTextField.tsx +0 -155
- 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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
90
|
-
export function
|
|
91
|
-
return
|
|
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
|
|
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]
|
|
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=
|
|
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
|
|
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
|
-
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
<
|
|
87
|
-
</
|
|
88
|
-
</
|
|
89
|
-
|
|
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>
|