@kyro-cms/admin 0.1.5 → 0.1.7
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/README.md +149 -51
- package/package.json +52 -5
- package/src/collections/auth/index.ts +2 -2
- package/src/collections/portfolio/index.ts +343 -0
- package/src/components/ActionBar.tsx +153 -16
- package/src/components/Admin.tsx +136 -27
- package/src/components/ApiExplorer.tsx +325 -0
- package/src/components/ApiKeysManager.tsx +563 -0
- package/src/components/AuditLogsPage.tsx +664 -0
- package/src/components/AutoForm.tsx +1417 -661
- package/src/components/BrandingHub.tsx +267 -0
- package/src/components/BulkActionsBar.tsx +3 -3
- package/src/components/CreateView.tsx +3 -3
- package/src/components/Dashboard.tsx +393 -0
- package/src/components/DetailView.tsx +199 -57
- package/src/components/DeveloperCenter.tsx +403 -0
- package/src/components/EnhancedListView.tsx +786 -0
- package/src/components/GraphQLExplorer.tsx +675 -0
- package/src/components/GraphQLPlayground.tsx +627 -0
- package/src/components/ListView.tsx +191 -53
- package/src/components/MediaGallery.tsx +1569 -0
- package/src/components/Modal.tsx +149 -0
- package/src/components/RestPlayground.tsx +951 -0
- package/src/components/Sidebar.astro +237 -0
- package/src/components/UserManagement.tsx +204 -0
- package/src/components/VersionHistoryPanel.tsx +3 -3
- package/src/components/WebhookManager.tsx +608 -0
- package/src/components/blocks/AccordionBlock.tsx +97 -0
- package/src/components/blocks/ArrayBlock.tsx +75 -0
- package/src/components/blocks/BlockEditModal.MARKER +12 -0
- package/src/components/blocks/BlockEditModal.tsx +774 -0
- package/src/components/blocks/ButtonBlock.tsx +165 -0
- package/src/components/blocks/ChildBlocksTree.tsx +551 -0
- package/src/components/blocks/CodeBlock.tsx +66 -0
- package/src/components/blocks/ColumnsBlock.tsx +151 -0
- package/src/components/blocks/DividerBlock.tsx +43 -0
- package/src/components/blocks/FileBlock.tsx +64 -0
- package/src/components/blocks/HeadingBlock.tsx +81 -0
- package/src/components/blocks/HeroBlock.tsx +157 -0
- package/src/components/blocks/ImageBlock.tsx +83 -0
- package/src/components/blocks/LinkBlock.tsx +71 -0
- package/src/components/blocks/ListBlock.tsx +39 -0
- package/src/components/blocks/ParagraphBlock.tsx +61 -0
- package/src/components/blocks/RelationshipBlock.tsx +279 -0
- package/src/components/blocks/VStackBlock.tsx +75 -0
- package/src/components/blocks/VideoBlock.tsx +45 -0
- package/src/components/blocks/index.ts +10 -0
- package/src/components/fields/BlocksField.tsx +323 -0
- package/src/components/fields/CheckboxField.tsx +15 -9
- package/src/components/fields/CodeField.tsx +234 -0
- package/src/components/fields/DateField.tsx +38 -11
- package/src/components/fields/EditorClient.tsx +271 -0
- package/src/components/fields/FileField.tsx +390 -0
- package/src/components/fields/HybridContentField.tsx +109 -0
- package/src/components/fields/ImageField.tsx +429 -0
- package/src/components/fields/JSONField.tsx +361 -0
- package/src/components/fields/MarkdownField.tsx +282 -0
- package/src/components/fields/NumberField.tsx +42 -12
- package/src/components/fields/PortableTextField.tsx +143 -0
- package/src/components/fields/PortableTextRenderer.tsx +68 -0
- package/src/components/fields/RelationshipField.tsx +231 -59
- package/src/components/fields/SelectField.tsx +25 -15
- package/src/components/fields/TextField.tsx +45 -14
- package/src/components/fields/extensions/blockComponents.tsx +237 -0
- package/src/components/fields/extensions/blocksStore.ts +273 -0
- package/src/components/fields/index.ts +13 -0
- package/src/components/index.ts +1 -2
- package/src/components/layout/Header.tsx +2 -2
- package/src/components/layout/Layout.tsx +2 -2
- package/src/components/ui/Badge.tsx +9 -4
- package/src/components/ui/BlockDrawer.tsx +79 -0
- package/src/components/ui/Button.tsx +1 -1
- package/src/components/ui/CommandPalette.tsx +362 -0
- package/src/components/ui/CommandPaletteWrapper.tsx +97 -0
- package/src/components/ui/Dropdown.tsx +1 -1
- package/src/components/ui/Modal.tsx +37 -12
- package/src/components/ui/PromptModal.tsx +94 -0
- package/src/components/ui/SlidePanel.tsx +43 -16
- package/src/components/ui/Toast.tsx +80 -14
- package/src/env.d.ts +16 -0
- package/src/env.ts +20 -0
- package/src/index.ts +0 -1
- package/src/layouts/AdminLayout.astro +164 -170
- package/src/layouts/AuthLayout.astro +50 -0
- package/src/lib/MediaService.ts +541 -0
- package/src/lib/auth/sqlite-adapter.ts +319 -0
- package/src/lib/config.ts +22 -6
- package/src/lib/dataStore.ts +132 -74
- package/src/lib/db/adapter.ts +54 -0
- package/src/lib/db/drizzle-mysql-adapter.ts +194 -0
- package/src/lib/db/drizzle-mysql-auth-adapter.ts +327 -0
- package/src/lib/db/drizzle-postgres-adapter.ts +202 -0
- package/src/lib/db/drizzle-postgres-auth-adapter.ts +304 -0
- package/src/lib/db/drizzle-sqlite-adapter.ts +227 -0
- package/src/lib/db/drizzle-sqlite-auth-adapter.ts +548 -0
- package/src/lib/db/index.ts +449 -0
- package/src/lib/db/mongodb-adapter.ts +207 -0
- package/src/lib/db/mongodb-auth-adapter.ts +305 -0
- package/src/lib/db/schema/mysql-auth.ts +113 -0
- package/src/lib/db/schema/mysql-content.ts +20 -0
- package/src/lib/db/schema/postgres-auth.ts +116 -0
- package/src/lib/db/schema/postgres-content.ts +35 -0
- package/src/lib/db/schema/postgres-media.ts +52 -0
- package/src/lib/db/schema/postgres-settings.ts +11 -0
- package/src/lib/db/schema/sqlite-auth.ts +112 -0
- package/src/lib/db/schema/sqlite-content.ts +20 -0
- package/src/lib/graphql/index.ts +1 -0
- package/src/lib/graphql/schema.ts +443 -0
- package/src/lib/rate-limit.ts +267 -0
- package/src/lib/storage.ts +374 -0
- package/src/lib/store.ts +85 -0
- package/src/middleware.ts +116 -28
- package/src/pages/[collection]/[id].astro +178 -122
- package/src/pages/[collection]/index.astro +24 -156
- package/src/pages/admin/api-explorer.astro +98 -0
- package/src/pages/admin/graphql-explorer.astro +40 -0
- package/src/pages/admin/graphql.astro +97 -0
- package/src/pages/admin/index.astro +286 -0
- package/src/pages/admin/keys.astro +8 -0
- package/src/pages/admin/rest-playground.astro +44 -0
- package/src/pages/admin/webhooks.astro +8 -0
- package/src/pages/api/[collection]/[id]/publish.ts +44 -0
- package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
- package/src/pages/api/[collection]/[id]/versions.ts +36 -0
- package/src/pages/api/[collection]/[id].ts +102 -159
- package/src/pages/api/[collection]/index.ts +151 -230
- package/src/pages/api/auth/[id].ts +48 -69
- package/src/pages/api/auth/audit-logs.ts +20 -43
- package/src/pages/api/auth/login.ts +159 -45
- package/src/pages/api/auth/logout.ts +50 -20
- package/src/pages/api/auth/refresh.ts +119 -0
- package/src/pages/api/auth/register.ts +110 -40
- package/src/pages/api/auth/users.ts +22 -97
- package/src/pages/api/collections.ts +59 -0
- package/src/pages/api/globals/[slug]/test.ts +172 -0
- package/src/pages/api/globals/[slug].ts +42 -0
- package/src/pages/api/graphql.ts +90 -0
- package/src/pages/api/health.ts +417 -40
- package/src/pages/api/keys/[id].ts +26 -0
- package/src/pages/api/keys/index.ts +75 -0
- package/src/pages/api/media/[id].ts +309 -0
- package/src/pages/api/media/folders.ts +609 -0
- package/src/pages/api/media/index.ts +146 -0
- package/src/pages/api/media/resize.ts +267 -0
- package/src/pages/api/search.ts +82 -0
- package/src/pages/api/slug-availability.ts +70 -0
- package/src/pages/api/storage-config.ts +20 -0
- package/src/pages/api/storage-status.ts +206 -0
- package/src/pages/api/upload.ts +334 -0
- package/src/pages/api/webhooks/index.ts +71 -0
- package/src/pages/audit/index.astro +2 -104
- package/src/pages/login.astro +82 -0
- package/src/pages/media.astro +10 -0
- package/src/pages/preview/[collection]/[id].astro +178 -0
- package/src/pages/register.astro +102 -0
- package/src/pages/roles/index.astro +21 -21
- package/src/pages/settings/[slug].astro +162 -0
- package/src/pages/settings/index.astro +9 -0
- package/src/pages/users/[id].astro +29 -21
- package/src/pages/users/index.astro +22 -17
- package/src/pages/users/new.astro +18 -17
- package/src/styles/main.css +553 -128
- package/src/components/layout/Sidebar.tsx +0 -497
- package/src/pages/index.astro +0 -225
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import React, { useEffect, type ReactNode } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
|
|
4
|
+
export function ModalContent({ children }: { children: ReactNode }) {
|
|
5
|
+
return <div className="text-[var(--kyro-text-secondary)]">{children}</div>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function ModalActions({ children }: { children: ReactNode }) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="flex items-center justify-end gap-3 mt-6">{children}</div>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
2
13
|
|
|
3
14
|
interface ModalProps {
|
|
4
15
|
open: boolean;
|
|
@@ -7,6 +18,7 @@ interface ModalProps {
|
|
|
7
18
|
children: ReactNode;
|
|
8
19
|
footer?: ReactNode;
|
|
9
20
|
size?: "sm" | "md" | "lg";
|
|
21
|
+
variant?: "default" | "danger";
|
|
10
22
|
}
|
|
11
23
|
|
|
12
24
|
export function Modal({
|
|
@@ -16,6 +28,7 @@ export function Modal({
|
|
|
16
28
|
children,
|
|
17
29
|
footer,
|
|
18
30
|
size = "md",
|
|
31
|
+
variant = "default",
|
|
19
32
|
}: ModalProps) {
|
|
20
33
|
useEffect(() => {
|
|
21
34
|
const handleEscape = (e: KeyboardEvent) => {
|
|
@@ -41,20 +54,27 @@ export function Modal({
|
|
|
41
54
|
lg: "max-w-2xl",
|
|
42
55
|
};
|
|
43
56
|
|
|
44
|
-
return (
|
|
45
|
-
<div className="fixed inset-0 z-
|
|
57
|
+
return createPortal(
|
|
58
|
+
<div className="fixed inset-0 z-[9999] flex items-center justify-center">
|
|
46
59
|
<div
|
|
47
60
|
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
|
48
61
|
onClick={onClose}
|
|
49
62
|
/>
|
|
50
63
|
<div
|
|
51
|
-
className={`relative w-full ${sizeClasses[size]} mx-4 bg-
|
|
64
|
+
className={`relative w-full ${sizeClasses[size]} mx-4 bg-[var(--kyro-surface)] rounded-lg shadow-2xl animate-in fade-in zoom-in-95 duration-200 border ${
|
|
65
|
+
variant === "danger"
|
|
66
|
+
? "border-red-500/20"
|
|
67
|
+
: "border-[var(--kyro-border)]"
|
|
68
|
+
}`}
|
|
52
69
|
>
|
|
53
|
-
<div className="flex items-center justify-between px-6 py-4 border-b border-
|
|
54
|
-
<h2 className="text-lg font-semibold text-
|
|
70
|
+
<div className="flex items-center justify-between px-6 py-4 border-b border-[var(--kyro-border)]">
|
|
71
|
+
<h2 className="text-lg font-semibold text-[var(--kyro-text-primary)]">
|
|
72
|
+
{title}
|
|
73
|
+
</h2>
|
|
55
74
|
<button
|
|
75
|
+
type="button"
|
|
56
76
|
onClick={onClose}
|
|
57
|
-
className="p-1 text-
|
|
77
|
+
className="p-1 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] rounded-lg hover:bg-[var(--kyro-surface-accent)] transition-colors"
|
|
58
78
|
>
|
|
59
79
|
<svg
|
|
60
80
|
width="20"
|
|
@@ -70,12 +90,13 @@ export function Modal({
|
|
|
70
90
|
</div>
|
|
71
91
|
<div className="px-6 py-4">{children}</div>
|
|
72
92
|
{footer && (
|
|
73
|
-
<div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-
|
|
93
|
+
<div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)] rounded-b-lg">
|
|
74
94
|
{footer}
|
|
75
95
|
</div>
|
|
76
96
|
)}
|
|
77
97
|
</div>
|
|
78
|
-
</div
|
|
98
|
+
</div>,
|
|
99
|
+
document.body,
|
|
79
100
|
);
|
|
80
101
|
}
|
|
81
102
|
|
|
@@ -111,17 +132,21 @@ export function ConfirmModal({
|
|
|
111
132
|
footer={
|
|
112
133
|
<>
|
|
113
134
|
<button
|
|
135
|
+
type="button"
|
|
114
136
|
onClick={onClose}
|
|
115
137
|
disabled={loading}
|
|
116
|
-
className="kyro-
|
|
138
|
+
className="px-4 py-2 rounded-lg font-medium text-sm border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] hover:text-[var(--kyro-text-primary)] transition-colors"
|
|
117
139
|
>
|
|
118
140
|
{cancelLabel}
|
|
119
141
|
</button>
|
|
120
142
|
<button
|
|
143
|
+
type="button"
|
|
121
144
|
onClick={onConfirm}
|
|
122
145
|
disabled={loading}
|
|
123
|
-
className={`
|
|
124
|
-
variant === "danger"
|
|
146
|
+
className={`px-4 py-2 rounded-lg font-medium text-sm transition-colors ${
|
|
147
|
+
variant === "danger"
|
|
148
|
+
? "bg-red-500 text-white hover:bg-red-600"
|
|
149
|
+
: "bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90"
|
|
125
150
|
}`}
|
|
126
151
|
>
|
|
127
152
|
{loading ? "Loading..." : confirmLabel}
|
|
@@ -129,7 +154,7 @@ export function ConfirmModal({
|
|
|
129
154
|
</>
|
|
130
155
|
}
|
|
131
156
|
>
|
|
132
|
-
<p className="text-
|
|
157
|
+
<p className="text-[var(--kyro-text-secondary)]">{message}</p>
|
|
133
158
|
</Modal>
|
|
134
159
|
);
|
|
135
160
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
|
|
4
|
+
interface PromptModalProps {
|
|
5
|
+
open: boolean;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
onSubmit: (value: string) => void;
|
|
8
|
+
title: string;
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
defaultValue?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function PromptModal({
|
|
14
|
+
open,
|
|
15
|
+
onClose,
|
|
16
|
+
onSubmit,
|
|
17
|
+
title,
|
|
18
|
+
placeholder = "",
|
|
19
|
+
defaultValue = "",
|
|
20
|
+
}: PromptModalProps) {
|
|
21
|
+
const [value, setValue] = useState(defaultValue);
|
|
22
|
+
|
|
23
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
if (value.trim()) {
|
|
26
|
+
onSubmit(value.trim());
|
|
27
|
+
setValue("");
|
|
28
|
+
onClose();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (!open) return null;
|
|
33
|
+
|
|
34
|
+
return createPortal(
|
|
35
|
+
<div className="fixed inset-0 z-[9999] flex items-center justify-center">
|
|
36
|
+
<div
|
|
37
|
+
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
|
38
|
+
onClick={onClose}
|
|
39
|
+
/>
|
|
40
|
+
<div className="relative w-full max-w-md mx-4 bg-[var(--kyro-surface)] rounded-lg shadow-2xl animate-in fade-in zoom-in-95 duration-200 border border-[var(--kyro-border)]">
|
|
41
|
+
<div className="flex items-center justify-between px-6 py-4 border-b border-[var(--kyro-border)]">
|
|
42
|
+
<h2 className="text-lg font-semibold text-[var(--kyro-text-primary)]">
|
|
43
|
+
{title}
|
|
44
|
+
</h2>
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
onClick={onClose}
|
|
48
|
+
className="p-1 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] rounded-lg hover:bg-[var(--kyro-surface-accent)] transition-colors"
|
|
49
|
+
>
|
|
50
|
+
<svg
|
|
51
|
+
width="20"
|
|
52
|
+
height="20"
|
|
53
|
+
viewBox="0 0 24 24"
|
|
54
|
+
fill="none"
|
|
55
|
+
stroke="currentColor"
|
|
56
|
+
strokeWidth="2"
|
|
57
|
+
>
|
|
58
|
+
<path d="M18 6L6 18M6 6l12 12" />
|
|
59
|
+
</svg>
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
<form onSubmit={handleSubmit}>
|
|
63
|
+
<div className="px-6 py-4">
|
|
64
|
+
<input
|
|
65
|
+
type="text"
|
|
66
|
+
value={value}
|
|
67
|
+
onChange={(e) => setValue(e.target.value)}
|
|
68
|
+
placeholder={placeholder}
|
|
69
|
+
autoFocus
|
|
70
|
+
className="w-full px-3 py-2 border border-[var(--kyro-input-border)] rounded-lg bg-[var(--kyro-input-bg)] text-[var(--kyro-text-primary)] placeholder-[var(--kyro-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-transparent"
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
<div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)] rounded-b-lg">
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
onClick={onClose}
|
|
77
|
+
className="px-4 py-2 rounded-lg font-medium text-sm border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] hover:text-[var(--kyro-text-primary)] transition-colors"
|
|
78
|
+
>
|
|
79
|
+
Cancel
|
|
80
|
+
</button>
|
|
81
|
+
<button
|
|
82
|
+
type="submit"
|
|
83
|
+
disabled={!value.trim()}
|
|
84
|
+
className="px-4 py-2 rounded-lg font-medium text-sm bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
85
|
+
>
|
|
86
|
+
Confirm
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
</form>
|
|
90
|
+
</div>
|
|
91
|
+
</div>,
|
|
92
|
+
document.body,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import React, { useEffect, useRef, type ReactNode } from "react";
|
|
1
|
+
import React, { useEffect, useRef, useState, type ReactNode } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
2
3
|
|
|
3
4
|
interface SlidePanelProps {
|
|
4
5
|
open: boolean;
|
|
5
6
|
onClose: () => void;
|
|
6
7
|
title: string;
|
|
7
8
|
children: ReactNode;
|
|
8
|
-
width?: "sm" | "md" | "lg";
|
|
9
|
+
width?: "sm" | "md" | "lg" | "xl";
|
|
10
|
+
showOverlay?: boolean;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export function SlidePanel({
|
|
@@ -14,8 +16,14 @@ export function SlidePanel({
|
|
|
14
16
|
title,
|
|
15
17
|
children,
|
|
16
18
|
width = "md",
|
|
19
|
+
showOverlay = false,
|
|
17
20
|
}: SlidePanelProps) {
|
|
18
21
|
const panelRef = useRef<HTMLDivElement>(null);
|
|
22
|
+
const [hydrated, setHydrated] = useState(false);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
setHydrated(true);
|
|
26
|
+
}, []);
|
|
19
27
|
|
|
20
28
|
useEffect(() => {
|
|
21
29
|
const handleEscape = (e: KeyboardEvent) => {
|
|
@@ -35,28 +43,37 @@ export function SlidePanel({
|
|
|
35
43
|
|
|
36
44
|
const widthClasses = {
|
|
37
45
|
sm: "w-[320px]",
|
|
38
|
-
md: "w-[
|
|
39
|
-
lg: "w-[
|
|
46
|
+
md: "w-[400px]",
|
|
47
|
+
lg: "w-[500px]",
|
|
48
|
+
xl: "w-[600px]",
|
|
40
49
|
};
|
|
41
50
|
|
|
42
|
-
if (!open) return null;
|
|
51
|
+
if (!open || !hydrated) return null;
|
|
43
52
|
|
|
44
|
-
return (
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
return createPortal(
|
|
54
|
+
<>
|
|
55
|
+
{showOverlay && (
|
|
56
|
+
<div
|
|
57
|
+
className="fixed inset-0 z-[99998] bg-black/50 backdrop-blur-sm"
|
|
58
|
+
onClick={onClose}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
47
61
|
<div
|
|
48
62
|
ref={panelRef}
|
|
49
|
-
className={`
|
|
63
|
+
className={`fixed right-0 top-0 bottom-0 z-[99999] ${widthClasses[width]} bg-[var(--kyro-surface)] border-l border-[var(--kyro-border)] shadow-2xl flex flex-col animate-slideIn`}
|
|
50
64
|
>
|
|
51
|
-
<div className="flex items-center justify-between px-
|
|
52
|
-
<h2 className="text-
|
|
65
|
+
<div className="flex items-center justify-between px-4 py-3 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface)]">
|
|
66
|
+
<h2 className="text-sm font-semibold text-[var(--kyro-text-primary)]">
|
|
67
|
+
{title}
|
|
68
|
+
</h2>
|
|
53
69
|
<button
|
|
70
|
+
type="button"
|
|
54
71
|
onClick={onClose}
|
|
55
|
-
className="p-1 text-
|
|
72
|
+
className="p-1.5 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)] rounded-lg transition-colors"
|
|
56
73
|
>
|
|
57
74
|
<svg
|
|
58
|
-
width="
|
|
59
|
-
height="
|
|
75
|
+
width="16"
|
|
76
|
+
height="16"
|
|
60
77
|
viewBox="0 0 24 24"
|
|
61
78
|
fill="none"
|
|
62
79
|
stroke="currentColor"
|
|
@@ -66,8 +83,18 @@ export function SlidePanel({
|
|
|
66
83
|
</svg>
|
|
67
84
|
</button>
|
|
68
85
|
</div>
|
|
69
|
-
<div className="flex-1 overflow-y-auto p-
|
|
86
|
+
<div className="flex-1 overflow-y-auto p-4">{children}</div>
|
|
70
87
|
</div>
|
|
71
|
-
|
|
88
|
+
<style>{`
|
|
89
|
+
@keyframes slideIn {
|
|
90
|
+
from { transform: translateX(100%); }
|
|
91
|
+
to { transform: translateX(0); }
|
|
92
|
+
}
|
|
93
|
+
.animate-slideIn {
|
|
94
|
+
animation: slideIn 300ms ease-out;
|
|
95
|
+
}
|
|
96
|
+
`}</style>
|
|
97
|
+
</>,
|
|
98
|
+
document.body,
|
|
72
99
|
);
|
|
73
100
|
}
|
|
@@ -1,21 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useContext,
|
|
4
|
+
useState,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
useCallback,
|
|
7
|
+
} from "react";
|
|
2
8
|
|
|
3
9
|
interface Toast {
|
|
4
10
|
id: string;
|
|
5
|
-
type:
|
|
11
|
+
type: "success" | "error" | "info" | "warning";
|
|
6
12
|
message: string;
|
|
7
13
|
}
|
|
8
14
|
|
|
9
15
|
interface ToastContextType {
|
|
10
16
|
toasts: Toast[];
|
|
11
|
-
addToast: (type: Toast[
|
|
17
|
+
addToast: (type: Toast["type"], message: string) => void;
|
|
12
18
|
removeToast: (id: string) => void;
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
const ToastContext = createContext<ToastContextType | null>(null);
|
|
16
22
|
|
|
23
|
+
export function createToastContext() {
|
|
24
|
+
const [toasts, setToasts] = useState<Toast[]>([]);
|
|
25
|
+
|
|
26
|
+
const addToast = useCallback((type: Toast["type"], message: string) => {
|
|
27
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
28
|
+
setToasts((prev) => [...prev, { id, type, message }]);
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
const removeToast = useCallback((id: string) => {
|
|
32
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
return { toasts, addToast, removeToast };
|
|
36
|
+
}
|
|
37
|
+
|
|
17
38
|
interface ToastProps {
|
|
18
|
-
type: Toast[
|
|
39
|
+
type: Toast["type"];
|
|
19
40
|
message: string;
|
|
20
41
|
onClose: () => void;
|
|
21
42
|
}
|
|
@@ -23,37 +44,72 @@ interface ToastProps {
|
|
|
23
44
|
export function Toast({ type, message, onClose }: ToastProps) {
|
|
24
45
|
const icons = {
|
|
25
46
|
success: (
|
|
26
|
-
<svg
|
|
47
|
+
<svg
|
|
48
|
+
width="20"
|
|
49
|
+
height="20"
|
|
50
|
+
viewBox="0 0 24 24"
|
|
51
|
+
fill="none"
|
|
52
|
+
stroke="currentColor"
|
|
53
|
+
strokeWidth="2"
|
|
54
|
+
>
|
|
27
55
|
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
|
|
28
56
|
<path d="M22 4L12 14.01l-3-3" />
|
|
29
57
|
</svg>
|
|
30
58
|
),
|
|
31
59
|
error: (
|
|
32
|
-
<svg
|
|
60
|
+
<svg
|
|
61
|
+
width="20"
|
|
62
|
+
height="20"
|
|
63
|
+
viewBox="0 0 24 24"
|
|
64
|
+
fill="none"
|
|
65
|
+
stroke="currentColor"
|
|
66
|
+
strokeWidth="2"
|
|
67
|
+
>
|
|
33
68
|
<circle cx="12" cy="12" r="10" />
|
|
34
69
|
<path d="M15 9l-6 6M9 9l6 6" />
|
|
35
70
|
</svg>
|
|
36
71
|
),
|
|
37
72
|
warning: (
|
|
38
|
-
<svg
|
|
73
|
+
<svg
|
|
74
|
+
width="20"
|
|
75
|
+
height="20"
|
|
76
|
+
viewBox="0 0 24 24"
|
|
77
|
+
fill="none"
|
|
78
|
+
stroke="currentColor"
|
|
79
|
+
strokeWidth="2"
|
|
80
|
+
>
|
|
39
81
|
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
|
40
82
|
<path d="M12 9v4M12 17h.01" />
|
|
41
83
|
</svg>
|
|
42
84
|
),
|
|
43
85
|
info: (
|
|
44
|
-
<svg
|
|
86
|
+
<svg
|
|
87
|
+
width="20"
|
|
88
|
+
height="20"
|
|
89
|
+
viewBox="0 0 24 24"
|
|
90
|
+
fill="none"
|
|
91
|
+
stroke="currentColor"
|
|
92
|
+
strokeWidth="2"
|
|
93
|
+
>
|
|
45
94
|
<circle cx="12" cy="12" r="10" />
|
|
46
95
|
<path d="M12 16v-4M12 8h.01" />
|
|
47
96
|
</svg>
|
|
48
|
-
)
|
|
97
|
+
),
|
|
49
98
|
};
|
|
50
99
|
|
|
51
100
|
return (
|
|
52
101
|
<div className={`kyro-toast kyro-toast-${type}`}>
|
|
53
102
|
<span className="kyro-toast-icon">{icons[type]}</span>
|
|
54
103
|
<span className="kyro-toast-message">{message}</span>
|
|
55
|
-
<button className="kyro-toast-close" onClick={onClose}>
|
|
56
|
-
<svg
|
|
104
|
+
<button type="button" className="kyro-toast-close" onClick={onClose}>
|
|
105
|
+
<svg
|
|
106
|
+
width="16"
|
|
107
|
+
height="16"
|
|
108
|
+
viewBox="0 0 24 24"
|
|
109
|
+
fill="none"
|
|
110
|
+
stroke="currentColor"
|
|
111
|
+
strokeWidth="2"
|
|
112
|
+
>
|
|
57
113
|
<path d="M18 6L6 18M6 6l12 12" />
|
|
58
114
|
</svg>
|
|
59
115
|
</button>
|
|
@@ -61,9 +117,19 @@ export function Toast({ type, message, onClose }: ToastProps) {
|
|
|
61
117
|
);
|
|
62
118
|
}
|
|
63
119
|
|
|
64
|
-
export function ToastProvider({
|
|
120
|
+
export function ToastProvider({
|
|
121
|
+
children,
|
|
122
|
+
toasts = [],
|
|
123
|
+
addToast = () => {},
|
|
124
|
+
removeToast = () => {},
|
|
125
|
+
}: {
|
|
126
|
+
children: ReactNode;
|
|
127
|
+
toasts?: Toast[];
|
|
128
|
+
addToast?: (type: Toast["type"], message: string) => void;
|
|
129
|
+
removeToast?: (id: string) => void;
|
|
130
|
+
}) {
|
|
65
131
|
return (
|
|
66
|
-
<ToastContext.Provider value={{ toasts
|
|
132
|
+
<ToastContext.Provider value={{ toasts, addToast, removeToast }}>
|
|
67
133
|
{children}
|
|
68
134
|
</ToastContext.Provider>
|
|
69
135
|
);
|
|
@@ -72,7 +138,7 @@ export function ToastProvider({ children }: { children: ReactNode }) {
|
|
|
72
138
|
export function useToast() {
|
|
73
139
|
const context = useContext(ToastContext);
|
|
74
140
|
if (!context) {
|
|
75
|
-
throw new Error(
|
|
141
|
+
throw new Error("useToast must be used within a ToastProvider");
|
|
76
142
|
}
|
|
77
143
|
return context;
|
|
78
144
|
}
|
package/src/env.d.ts
ADDED
package/src/env.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
const envPath = path.join(process.cwd(), "..", ".env");
|
|
5
|
+
if (fs.existsSync(envPath)) {
|
|
6
|
+
const envContent = fs.readFileSync(envPath, "utf-8");
|
|
7
|
+
envContent.split("\n").forEach((line) => {
|
|
8
|
+
const trimmed = line.trim();
|
|
9
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
10
|
+
const eqIndex = trimmed.indexOf("=");
|
|
11
|
+
if (eqIndex > 0) {
|
|
12
|
+
const key = trimmed.substring(0, eqIndex);
|
|
13
|
+
const value = trimmed.substring(eqIndex + 1);
|
|
14
|
+
if (!process.env[key]) {
|
|
15
|
+
process.env[key] = value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -21,7 +21,6 @@ export {
|
|
|
21
21
|
type ThemeMode,
|
|
22
22
|
} from "./components/ThemeProvider";
|
|
23
23
|
export * from "./components/layout/Header";
|
|
24
|
-
export * from "./components/layout/Sidebar";
|
|
25
24
|
export * from "./components/ui/Button";
|
|
26
25
|
export * from "./components/ui/Badge";
|
|
27
26
|
export * from "./components/ui/Spinner";
|