@kyro-cms/admin 0.3.2 → 0.3.5

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 (242) hide show
  1. package/dist/EditorClient-XEUOVAAC.js +466 -0
  2. package/dist/EditorClient-XEUOVAAC.js.map +1 -0
  3. package/dist/EditorClient-YLCGVDXY.cjs +468 -0
  4. package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
  5. package/dist/chunk-7KPIUCGT.js +384 -0
  6. package/dist/chunk-7KPIUCGT.js.map +1 -0
  7. package/dist/chunk-GOACG6R7.cjs +473 -0
  8. package/dist/chunk-GOACG6R7.cjs.map +1 -0
  9. package/dist/index.cjs +14861 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.css +1661 -0
  12. package/dist/index.css.map +1 -0
  13. package/dist/index.d.ts +563 -0
  14. package/dist/index.js +14784 -0
  15. package/dist/index.js.map +1 -0
  16. package/package.json +19 -19
  17. package/src/components/ActionBar.tsx +7 -43
  18. package/src/components/Admin.tsx +138 -277
  19. package/src/components/ApiKeysManager.tsx +428 -419
  20. package/src/components/AuditLogsPage.tsx +35 -39
  21. package/src/components/AuthBridge.tsx +51 -0
  22. package/src/components/AutoForm.tsx +495 -1230
  23. package/src/components/BrandingHub.tsx +18 -19
  24. package/src/components/BulkActionsBar.tsx +1 -1
  25. package/src/components/CreateView.tsx +22 -36
  26. package/src/components/Dashboard.tsx +60 -84
  27. package/src/components/DetailView.tsx +113 -91
  28. package/src/components/DeveloperCenter.tsx +200 -198
  29. package/src/components/FieldRenderer.tsx +206 -0
  30. package/src/components/GraphQLPlayground.tsx +340 -480
  31. package/src/components/ListView.tsx +828 -254
  32. package/src/components/LoginPage.tsx +3 -4
  33. package/src/components/MarketplaceManager.tsx +254 -0
  34. package/src/components/MediaGallery.tsx +856 -1192
  35. package/src/components/PluginsManager.tsx +277 -0
  36. package/src/components/RestPlayground.tsx +398 -560
  37. package/src/components/SessionsManager.tsx +211 -0
  38. package/src/components/Sidebar.astro +179 -151
  39. package/src/components/ThemeProvider.tsx +7 -161
  40. package/src/components/UserManagement.tsx +162 -146
  41. package/src/components/UserMenu.tsx +110 -0
  42. package/src/components/WebhookManager.tsx +305 -367
  43. package/src/components/blocks/AccordionBlock.tsx +4 -4
  44. package/src/components/blocks/ArrayBlock.tsx +3 -3
  45. package/src/components/blocks/BlockEditModal.tsx +8 -8
  46. package/src/components/blocks/BlockWrapper.tsx +61 -0
  47. package/src/components/blocks/ButtonBlock.tsx +4 -4
  48. package/src/components/blocks/ChildBlocksTree.tsx +23 -25
  49. package/src/components/blocks/CodeBlock.tsx +15 -15
  50. package/src/components/blocks/ColumnsBlock.tsx +6 -44
  51. package/src/components/blocks/DividerBlock.tsx +3 -3
  52. package/src/components/blocks/FileBlock.tsx +4 -4
  53. package/src/components/blocks/HeadingBlock.tsx +6 -38
  54. package/src/components/blocks/HeroBlock.tsx +4 -4
  55. package/src/components/blocks/ImageBlock.tsx +4 -4
  56. package/src/components/blocks/LinkBlock.tsx +4 -4
  57. package/src/components/blocks/ListBlock.tsx +3 -3
  58. package/src/components/blocks/ParagraphBlock.tsx +12 -42
  59. package/src/components/blocks/RelationshipBlock.tsx +4 -4
  60. package/src/components/blocks/RichTextBlock.tsx +4 -4
  61. package/src/components/blocks/VStackBlock.tsx +5 -37
  62. package/src/components/blocks/VideoBlock.tsx +4 -4
  63. package/src/components/blocks/types.ts +11 -0
  64. package/src/components/fields/AccordionField.tsx +1 -1
  65. package/src/components/fields/ArrayField.tsx +2 -2
  66. package/src/components/fields/ArrayLayout.tsx +93 -0
  67. package/src/components/fields/BlocksField.tsx +122 -111
  68. package/src/components/fields/ButtonField.tsx +1 -1
  69. package/src/components/fields/CheckboxField.tsx +14 -15
  70. package/src/components/fields/ChildrenField.tsx +2 -2
  71. package/src/components/fields/CodeField.tsx +3 -3
  72. package/src/components/fields/ColumnsField.tsx +2 -2
  73. package/src/components/fields/DateField.tsx +13 -26
  74. package/src/components/fields/EditorClient.tsx +26 -28
  75. package/src/components/fields/FieldLayout.tsx +52 -0
  76. package/src/components/fields/GroupLayout.tsx +35 -0
  77. package/src/components/fields/JSONField.tsx +7 -7
  78. package/src/components/fields/LinkField.tsx +1 -1
  79. package/src/components/fields/MarkdownField.tsx +1 -1
  80. package/src/components/fields/NumberField.tsx +13 -26
  81. package/src/components/fields/PortableTextField.tsx +4 -4
  82. package/src/components/fields/PortableTextRenderer.tsx +1 -1
  83. package/src/components/fields/RelationshipBlockField.tsx +31 -23
  84. package/src/components/fields/RelationshipField.tsx +14 -14
  85. package/src/components/fields/SelectField.tsx +17 -26
  86. package/src/components/fields/TabsLayout.tsx +69 -0
  87. package/src/components/fields/TextField.tsx +85 -38
  88. package/src/components/fields/UploadField.tsx +71 -41
  89. package/src/components/fields/VideoField.tsx +1 -1
  90. package/src/components/fields/extensions/blockComponents.tsx +2 -2
  91. package/src/components/fields/extensions/blocksStore.ts +207 -193
  92. package/src/components/fields/types.ts +22 -0
  93. package/src/components/layout/Layout.tsx +1 -1
  94. package/src/components/ui/ActionMenu.tsx +63 -0
  95. package/src/components/ui/Badge.tsx +59 -5
  96. package/src/components/ui/BlockDrawer.tsx +4 -5
  97. package/src/components/ui/CommandPalette.tsx +58 -36
  98. package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
  99. package/src/components/ui/Dropdown.tsx +18 -16
  100. package/src/components/ui/EmptyState.tsx +25 -0
  101. package/src/components/ui/GlobalModal.tsx +49 -0
  102. package/src/components/ui/IconButton.tsx +44 -0
  103. package/src/components/ui/Modal.tsx +19 -20
  104. package/src/components/ui/PageHeader.tsx +158 -0
  105. package/src/components/ui/Pagination.tsx +61 -0
  106. package/src/components/ui/PromptModal.tsx +1 -1
  107. package/src/components/ui/SearchInput.tsx +57 -0
  108. package/src/components/ui/SeoPreview.tsx +31 -0
  109. package/src/components/ui/SessionModal.tsx +0 -0
  110. package/src/components/ui/SlidePanel.tsx +2 -0
  111. package/src/components/ui/Toast.tsx +65 -122
  112. package/src/components/ui/Toaster.tsx +18 -0
  113. package/src/components/ui/icons.tsx +112 -0
  114. package/src/components/users/UserDetail.tsx +290 -0
  115. package/src/components/users/UserForm.tsx +242 -0
  116. package/src/components/users/UsersList.tsx +338 -0
  117. package/src/env.d.ts +13 -13
  118. package/src/fields/index.ts +2 -1
  119. package/src/global.d.ts +7 -0
  120. package/src/hooks/data.ts +2 -9
  121. package/src/hooks/useAsyncData.ts +36 -0
  122. package/src/hooks/useAutoFormState.ts +527 -0
  123. package/src/hooks/useSelection.ts +49 -0
  124. package/src/hooks/useSession.ts +0 -0
  125. package/src/index.ts +11 -1
  126. package/src/integration.ts +86 -11
  127. package/src/kyro-cms.d.ts +209 -0
  128. package/src/layouts/AdminLayout.astro +128 -11
  129. package/src/layouts/AuthLayout.astro +21 -5
  130. package/src/lib/api.ts +175 -55
  131. package/src/lib/autoform-store.ts +435 -0
  132. package/src/lib/config.ts +82 -34
  133. package/src/lib/createRegistry.ts +29 -0
  134. package/src/lib/default-kyro-config.ts +4 -0
  135. package/src/lib/globals.ts +50 -0
  136. package/src/lib/media-utils.ts +18 -0
  137. package/src/lib/object-utils.ts +77 -0
  138. package/src/lib/paths.ts +61 -0
  139. package/src/lib/stores/index.ts +370 -0
  140. package/src/lib/types.ts +43 -0
  141. package/src/lib/useResourceManager.ts +105 -0
  142. package/src/pages/403.astro +67 -0
  143. package/src/pages/[collection]/[id].astro +14 -180
  144. package/src/pages/[collection]/index.astro +11 -6
  145. package/src/pages/api-explorer.astro +173 -0
  146. package/src/pages/audit/index.astro +2 -0
  147. package/src/pages/auth/login.astro +122 -0
  148. package/src/pages/auth/register.astro +167 -0
  149. package/src/pages/graphql-explorer.astro +59 -0
  150. package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
  151. package/src/pages/index.astro +577 -0
  152. package/src/pages/index_ALT.astro +3 -0
  153. package/src/pages/keys.astro +11 -0
  154. package/src/pages/marketplace.astro +11 -0
  155. package/src/pages/media.astro +3 -0
  156. package/src/pages/plugins.astro +8 -0
  157. package/src/pages/preview/[collection]/[id].astro +188 -123
  158. package/src/pages/rest-playground.astro +62 -0
  159. package/src/pages/roles/index.astro +183 -76
  160. package/src/pages/sessions.astro +8 -0
  161. package/src/pages/settings/[slug].astro +92 -114
  162. package/src/pages/settings/index.astro +5 -3
  163. package/src/pages/users/[id].astro +25 -154
  164. package/src/pages/users/index.astro +19 -130
  165. package/src/pages/users/new.astro +9 -86
  166. package/src/pages/webhooks.astro +11 -0
  167. package/src/routes.ts +80 -0
  168. package/src/styles/main.css +119 -79
  169. package/src/theme/tokens.ts +1 -0
  170. package/src/vite-env.d.ts +14 -0
  171. package/src/collections/auth/index.ts +0 -155
  172. package/src/collections/portfolio/index.ts +0 -343
  173. package/src/components/ApiExplorer.tsx +0 -325
  174. package/src/components/EnhancedListView.tsx +0 -889
  175. package/src/components/GraphQLExplorer.tsx +0 -675
  176. package/src/components/Icons.tsx +0 -23
  177. package/src/components/StatusBadge.tsx +0 -76
  178. package/src/lib/MediaService.ts +0 -541
  179. package/src/lib/auth/sqlite-adapter.ts +0 -319
  180. package/src/lib/dataStore.ts +0 -226
  181. package/src/lib/db/adapter.ts +0 -54
  182. package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
  183. package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
  184. package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
  185. package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
  186. package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
  187. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
  188. package/src/lib/db/index.ts +0 -449
  189. package/src/lib/db/mongodb-adapter.ts +0 -207
  190. package/src/lib/db/mongodb-auth-adapter.ts +0 -305
  191. package/src/lib/db/schema/mysql-auth.ts +0 -113
  192. package/src/lib/db/schema/mysql-content.ts +0 -20
  193. package/src/lib/db/schema/postgres-auth.ts +0 -116
  194. package/src/lib/db/schema/postgres-content.ts +0 -35
  195. package/src/lib/db/schema/postgres-media.ts +0 -52
  196. package/src/lib/db/schema/postgres-settings.ts +0 -11
  197. package/src/lib/db/schema/sqlite-auth.ts +0 -112
  198. package/src/lib/db/schema/sqlite-content.ts +0 -20
  199. package/src/lib/db/version-adapter.ts +0 -248
  200. package/src/lib/graphql/index.ts +0 -1
  201. package/src/lib/graphql/schema.ts +0 -443
  202. package/src/lib/rate-limit.ts +0 -267
  203. package/src/lib/storage.ts +0 -374
  204. package/src/lib/store.ts +0 -85
  205. package/src/middleware.ts +0 -177
  206. package/src/pages/admin/api-explorer.astro +0 -98
  207. package/src/pages/admin/graphql-explorer.astro +0 -40
  208. package/src/pages/admin/index.astro +0 -286
  209. package/src/pages/admin/keys.astro +0 -8
  210. package/src/pages/admin/rest-playground.astro +0 -44
  211. package/src/pages/admin/webhooks.astro +0 -8
  212. package/src/pages/api/[collection]/[id]/publish.ts +0 -52
  213. package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
  214. package/src/pages/api/[collection]/[id]/versions.ts +0 -66
  215. package/src/pages/api/[collection]/[id].ts +0 -213
  216. package/src/pages/api/[collection]/index.ts +0 -209
  217. package/src/pages/api/auth/[id].ts +0 -121
  218. package/src/pages/api/auth/audit-logs.ts +0 -57
  219. package/src/pages/api/auth/login.ts +0 -211
  220. package/src/pages/api/auth/logout.ts +0 -66
  221. package/src/pages/api/auth/me.ts +0 -36
  222. package/src/pages/api/auth/refresh.ts +0 -119
  223. package/src/pages/api/auth/register.ts +0 -188
  224. package/src/pages/api/auth/users.ts +0 -97
  225. package/src/pages/api/collections.ts +0 -59
  226. package/src/pages/api/globals/[slug].ts +0 -42
  227. package/src/pages/api/graphql.ts +0 -90
  228. package/src/pages/api/health.ts +0 -426
  229. package/src/pages/api/keys/[id].ts +0 -26
  230. package/src/pages/api/keys/index.ts +0 -75
  231. package/src/pages/api/media/[id].ts +0 -309
  232. package/src/pages/api/media/folders.ts +0 -609
  233. package/src/pages/api/media/index.ts +0 -146
  234. package/src/pages/api/media/resize.ts +0 -267
  235. package/src/pages/api/search.ts +0 -82
  236. package/src/pages/api/slug-availability.ts +0 -70
  237. package/src/pages/api/storage-config.ts +0 -20
  238. package/src/pages/api/storage-status.ts +0 -206
  239. package/src/pages/api/upload.ts +0 -334
  240. package/src/pages/api/webhooks/index.ts +0 -71
  241. package/src/pages/login.astro +0 -82
  242. package/src/pages/register.astro +0 -102
@@ -0,0 +1,158 @@
1
+ import React, { type ReactNode } from "react";
2
+ import type { IconType } from "react-icons";
3
+
4
+ interface Breadcrumb {
5
+ label: string;
6
+ href?: string;
7
+ onClick?: () => void;
8
+ }
9
+
10
+ interface Action {
11
+ label: string;
12
+ onClick: () => void;
13
+ icon?: IconType;
14
+ variant?: "primary" | "outline" | "ghost";
15
+ className?: string;
16
+ }
17
+
18
+ interface PageHeaderProps {
19
+ title?: string;
20
+ description?: string;
21
+ icon?: IconType;
22
+ breadcrumbs?: Breadcrumb[];
23
+ metadata?: ReactNode[];
24
+ back?: { label?: string; href?: string; onClick?: () => void };
25
+ action?: Action;
26
+ actions?: ReactNode | Action[];
27
+ children?: ReactNode;
28
+ }
29
+
30
+ export function PageHeader({
31
+ title,
32
+ description,
33
+ icon: Icon,
34
+ breadcrumbs,
35
+ metadata,
36
+ back,
37
+ action,
38
+ actions,
39
+ children,
40
+ }: PageHeaderProps) {
41
+ return (
42
+ <div className="flex flex-col lg:flex-row lg:items-center surface-tile justify-between gap-6 pt-4 mb-8">
43
+ <div className="min-w-0 flex-1">
44
+ {/* Breadcrumbs / Back */}
45
+ {(breadcrumbs || back) && (
46
+ <div className="flex items-center gap-2 mb-3">
47
+ {back && (
48
+ <a
49
+ href={back.href}
50
+ onClick={(e) => {
51
+ if (back.onClick) {
52
+ e.preventDefault();
53
+ back.onClick();
54
+ }
55
+ }}
56
+ className="p-1.5 rounded-lg hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] transition-all"
57
+ >
58
+ <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
59
+ <path d="M19 12H5M12 19l-7-7 7-7" />
60
+ </svg>
61
+ </a>
62
+ )}
63
+ {breadcrumbs?.map((crumb: Breadcrumb, i: number) => (
64
+ <React.Fragment key={i}>
65
+ {i > 0 && <span className="opacity-20 text-[10px]">/</span>}
66
+ {crumb.href || crumb.onClick ? (
67
+ <a
68
+ href={crumb.href}
69
+ onClick={(e) => {
70
+ if (crumb.onClick) {
71
+ e.preventDefault();
72
+ crumb.onClick();
73
+ }
74
+ }}
75
+ className="text-[10px] font-bold tracking-widest text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-primary)] transition-all"
76
+ >
77
+ {crumb.label}
78
+ </a>
79
+ ) : (
80
+ <span className="text-[10px] font-bold tracking-widest opacity-40">
81
+ {crumb.label}
82
+ </span>
83
+ )}
84
+ </React.Fragment>
85
+ ))}
86
+ </div>
87
+ )}
88
+
89
+ <div className="flex items-center gap-3">
90
+ {Icon && <Icon className="w-6 h-6 text-[var(--kyro-primary)]" />}
91
+ {title && (
92
+ <h1 className="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)] truncate">
93
+ {title}
94
+ </h1>
95
+ )}
96
+ </div>
97
+
98
+ {description && (
99
+ <div className="flex items-center gap-2 mt-1">
100
+ <p className="text-[var(--kyro-text-secondary)] font-medium opacity-60 line-clamp-1">
101
+ {description}
102
+ </p>
103
+ {metadata && (
104
+ <div className="flex items-center gap-2">
105
+ {metadata.map((item: ReactNode, i: number) => (
106
+ <React.Fragment key={i}>
107
+ {i === 0 && <span className="opacity-20 ml-1">·</span>}
108
+ {item}
109
+ </React.Fragment>
110
+ ))}
111
+ </div>
112
+ )}
113
+ {children}
114
+ </div>
115
+ )}
116
+ </div>
117
+
118
+ <div className="flex items-center gap-3">
119
+ {actions && (
120
+ Array.isArray(actions) ? (
121
+ <div className="flex items-center gap-3">
122
+ {actions.map((act, i) => (
123
+ <button
124
+ key={i}
125
+ type="button"
126
+ onClick={act.onClick}
127
+ className={`flex items-center gap-2 px-6 py-2.5 rounded-xl font-bold text-sm transition-all shadow-lg shadow-[var(--kyro-primary)]/10 ${
128
+ act.variant === "outline"
129
+ ? "border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)]"
130
+ : act.variant === "ghost"
131
+ ? "text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] shadow-none"
132
+ : "bg-[var(--kyro-primary)] text-white hover:opacity-90"
133
+ } ${act.className || ""}`}
134
+ >
135
+ {act.icon && <act.icon className="w-4 h-4" />}
136
+ {act.label}
137
+ </button>
138
+ ))}
139
+ </div>
140
+ ) : (
141
+ actions
142
+ )
143
+ )}
144
+ {action && (
145
+ <button
146
+ type="button"
147
+ onClick={action.onClick}
148
+ className={`flex items-center gap-2 px-6 py-2.5 rounded-xl font-bold text-sm bg-[var(--kyro-primary)] text-white hover:opacity-90 transition-all shadow-lg shadow-[var(--kyro-primary)]/10 ${action.className || ""}`}
149
+ >
150
+ {action.icon && <action.icon className="w-4 h-4" />}
151
+ {action.label}
152
+ </button>
153
+ )}
154
+ </div>
155
+ </div>
156
+ );
157
+ }
158
+
@@ -0,0 +1,61 @@
1
+ import { Button } from "./Button";
2
+
3
+ interface PaginationProps {
4
+ page: number;
5
+ totalPages: number;
6
+ totalDocs?: number;
7
+ limit?: number;
8
+ onPageChange: (page: number) => void;
9
+ onLimitChange?: (limit: number) => void;
10
+ }
11
+
12
+ export function Pagination({ page, totalPages, totalDocs, limit, onPageChange, onLimitChange }: PaginationProps) {
13
+ if (totalPages <= 1) return null;
14
+
15
+ return (
16
+ <div className="flex items-center justify-between px-4 py-3 border-t border-[var(--kyro-border)]">
17
+ {totalDocs !== undefined && limit ? (
18
+ <span className="text-xs text-[var(--kyro-text-secondary)] font-medium">
19
+ Showing {(page - 1) * limit + 1} to {Math.min(page * limit, totalDocs)} of {totalDocs}
20
+ </span>
21
+ ) : (
22
+ <span />
23
+ )}
24
+ <div className="flex items-center gap-2">
25
+ {onLimitChange && (
26
+ <select
27
+ value={limit}
28
+ onChange={(e) => onLimitChange(Number(e.target.value))}
29
+ className="text-xs border border-[var(--kyro-border)] rounded-lg px-2 py-1 bg-[var(--kyro-bg)] text-[var(--kyro-text-secondary)]"
30
+ >
31
+ <option value={10}>10/page</option>
32
+ <option value={25}>25/page</option>
33
+ <option value={50}>50/page</option>
34
+ <option value={100}>100/page</option>
35
+ </select>
36
+ )}
37
+ <span className="text-xs text-[var(--kyro-text-secondary)] font-medium">
38
+ Page {page} of {totalPages}
39
+ </span>
40
+ <div className="flex gap-1">
41
+ <Button
42
+ variant="ghost"
43
+ size="sm"
44
+ disabled={page <= 1}
45
+ onClick={() => onPageChange(page - 1)}
46
+ >
47
+ ← Previous
48
+ </Button>
49
+ <Button
50
+ variant="ghost"
51
+ size="sm"
52
+ disabled={page >= totalPages}
53
+ onClick={() => onPageChange(page + 1)}
54
+ >
55
+ Next →
56
+ </Button>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ );
61
+ }
@@ -37,7 +37,7 @@ export function PromptModal({
37
37
  className="absolute inset-0 bg-black/50 backdrop-blur-sm"
38
38
  onClick={onClose}
39
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)]">
40
+ <div className="relative w-full max-w-lg 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
41
  <div className="flex items-center justify-between px-6 py-4 border-b border-[var(--kyro-border)]">
42
42
  <h2 className="text-lg font-semibold text-[var(--kyro-text-primary)]">
43
43
  {title}
@@ -0,0 +1,57 @@
1
+ import { IconSearch, IconLoader2 } from "./icons";
2
+ import type { ReactNode, KeyboardEvent, FocusEvent, RefObject } from "react";
3
+
4
+ interface SearchInputProps {
5
+ value: string;
6
+ onChange: (value: string) => void;
7
+ placeholder?: string;
8
+ className?: string;
9
+ loading?: boolean;
10
+ disabled?: boolean;
11
+ onKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void;
12
+ onFocus?: (e: FocusEvent<HTMLInputElement>) => void;
13
+ onBlur?: (e: FocusEvent<HTMLInputElement>) => void;
14
+ inputRef?: RefObject<HTMLInputElement | null>;
15
+ rightElement?: ReactNode;
16
+ }
17
+
18
+ export function SearchInput({
19
+ value,
20
+ onChange,
21
+ placeholder = "Search...",
22
+ className = "",
23
+ loading,
24
+ disabled,
25
+ onKeyDown,
26
+ onFocus,
27
+ onBlur,
28
+ inputRef,
29
+ rightElement,
30
+ }: SearchInputProps) {
31
+ return (
32
+ <div className={`relative flex-1 group ${className}`}>
33
+ {loading ? (
34
+ <IconLoader2 className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--kyro-text-secondary)] animate-spin opacity-40 group-focus-within:opacity-100 transition-opacity" />
35
+ ) : (
36
+ <IconSearch className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--kyro-text-secondary)] opacity-40 group-focus-within:opacity-100 transition-opacity" />
37
+ )}
38
+ <input
39
+ ref={inputRef}
40
+ type="text"
41
+ placeholder={placeholder}
42
+ value={value}
43
+ onChange={(e) => onChange(e.target.value)}
44
+ onKeyDown={onKeyDown}
45
+ onFocus={onFocus}
46
+ onBlur={onBlur}
47
+ disabled={disabled}
48
+ className="w-full pl-10 pr-4 py-2.5 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-transparent disabled:opacity-50"
49
+ />
50
+ {rightElement && (
51
+ <div className="absolute right-3 top-1/2 -translate-y-1/2">
52
+ {rightElement}
53
+ </div>
54
+ )}
55
+ </div>
56
+ );
57
+ }
@@ -0,0 +1,31 @@
1
+ import React from "react";
2
+
3
+ interface SeoPreviewProps {
4
+ title: string;
5
+ description: string;
6
+ slug: string;
7
+ }
8
+
9
+ export const SeoPreview = ({ title, description, slug }: SeoPreviewProps) => (
10
+ <div className="bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-lg p-6 max-w-2xl shadow-sm transition-colors duration-300">
11
+ <div className="flex items-center gap-2 mb-2">
12
+ <div className="w-7 h-7 bg-[var(--kyro-bg-secondary)] rounded-full flex items-center justify-center text-[10px] text-[var(--kyro-text-primary)] font-medium border border-[var(--kyro-border)]">
13
+ K
14
+ </div>
15
+ <div className="flex flex-col">
16
+ <span className="text-sm font-medium text-[var(--kyro-text-primary)] leading-tight">
17
+ kyro-cms.com
18
+ </span>
19
+ <span className="text-[12px] text-[var(--kyro-text-secondary)] leading-tight opacity-80">
20
+ https://kyro-cms.com › posts › {slug}
21
+ </span>
22
+ </div>
23
+ </div>
24
+ <h3 className="text-[20px] text-[#2563eb] dark:text-[#60a5fa] font-medium hover:underline cursor-pointer mb-1 leading-tight transition-colors">
25
+ {title}
26
+ </h3>
27
+ <p className="text-[14px] text-[var(--kyro-text-secondary)] leading-relaxed line-clamp-2">
28
+ {description}
29
+ </p>
30
+ </div>
31
+ );
File without changes
@@ -5,6 +5,8 @@ interface SlidePanelProps {
5
5
  open: boolean;
6
6
  onClose: () => void;
7
7
  title: string;
8
+ subtitle?: string;
9
+ size?: "sm" | "md" | "lg" | "xl";
8
10
  children: ReactNode;
9
11
  width?: "sm" | "md" | "lg" | "xl";
10
12
  showOverlay?: boolean;
@@ -1,144 +1,87 @@
1
- import {
2
- createContext,
3
- useContext,
4
- useState,
5
- type ReactNode,
6
- useCallback,
7
- } from "react";
1
+ import React, { createContext, useContext, type ReactNode } from "react";
2
+ import {
3
+ CheckCircle2,
4
+ AlertTriangle,
5
+ Info,
6
+ X,
7
+ ShieldAlert
8
+ } from "./icons";
8
9
 
9
- interface Toast {
10
- id: string;
11
- type: "success" | "error" | "info" | "warning";
12
- message: string;
13
- }
14
-
15
- interface ToastContextType {
16
- toasts: Toast[];
17
- addToast: (type: Toast["type"], message: string) => void;
18
- removeToast: (id: string) => void;
19
- }
20
-
21
- const ToastContext = createContext<ToastContextType | null>(null);
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
- }, []);
10
+ import { useToastStore } from "../../lib/stores";
30
11
 
31
- const removeToast = useCallback((id: string) => {
32
- setToasts((prev) => prev.filter((t) => t.id !== id));
33
- }, []);
34
-
35
- return { toasts, addToast, removeToast };
36
- }
12
+ type ToastType = "success" | "error" | "warning" | "info";
37
13
 
38
14
  interface ToastProps {
39
- type: Toast["type"];
15
+ type: ToastType;
40
16
  message: string;
41
17
  onClose: () => void;
42
18
  }
43
19
 
44
20
  export function Toast({ type, message, onClose }: ToastProps) {
45
- const icons = {
46
- success: (
47
- <svg
48
- width="20"
49
- height="20"
50
- viewBox="0 0 24 24"
51
- fill="none"
52
- stroke="currentColor"
53
- strokeWidth="2"
54
- >
55
- <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
56
- <path d="M22 4L12 14.01l-3-3" />
57
- </svg>
58
- ),
59
- error: (
60
- <svg
61
- width="20"
62
- height="20"
63
- viewBox="0 0 24 24"
64
- fill="none"
65
- stroke="currentColor"
66
- strokeWidth="2"
67
- >
68
- <circle cx="12" cy="12" r="10" />
69
- <path d="M15 9l-6 6M9 9l6 6" />
70
- </svg>
71
- ),
72
- warning: (
73
- <svg
74
- width="20"
75
- height="20"
76
- viewBox="0 0 24 24"
77
- fill="none"
78
- stroke="currentColor"
79
- strokeWidth="2"
80
- >
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" />
82
- <path d="M12 9v4M12 17h.01" />
83
- </svg>
84
- ),
85
- info: (
86
- <svg
87
- width="20"
88
- height="20"
89
- viewBox="0 0 24 24"
90
- fill="none"
91
- stroke="currentColor"
92
- strokeWidth="2"
93
- >
94
- <circle cx="12" cy="12" r="10" />
95
- <path d="M12 16v-4M12 8h.01" />
96
- </svg>
97
- ),
21
+ const [isPaused, setIsPaused] = React.useState(false);
22
+ const timerRef = React.useRef<NodeJS.Timeout | null>(null);
23
+
24
+ const startTimer = () => {
25
+ if (timerRef.current) clearTimeout(timerRef.current);
26
+ timerRef.current = setTimeout(onClose, 5000);
27
+ };
28
+
29
+ const clearTimer = () => {
30
+ if (timerRef.current) clearTimeout(timerRef.current);
98
31
  };
99
32
 
33
+ React.useEffect(() => {
34
+ if (!isPaused) {
35
+ startTimer();
36
+ } else {
37
+ clearTimer();
38
+ }
39
+ return clearTimer;
40
+ }, [isPaused, onClose]);
41
+
42
+ const Icon = {
43
+ success: CheckCircle2,
44
+ error: ShieldAlert,
45
+ warning: AlertTriangle,
46
+ info: Info,
47
+ }[type];
48
+
100
49
  return (
101
- <div className={`kyro-toast kyro-toast-${type}`}>
102
- <span className="kyro-toast-icon">{icons[type]}</span>
103
- <span className="kyro-toast-message">{message}</span>
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
- >
113
- <path d="M18 6L6 18M6 6l12 12" />
114
- </svg>
50
+ <div
51
+ className={`kyro-toast kyro-toast-${type} group animate-in fade-in slide-in-from-right-4 duration-300`}
52
+ onMouseEnter={() => setIsPaused(true)}
53
+ onMouseLeave={() => setIsPaused(false)}
54
+ >
55
+ <div className="kyro-toast-accent" />
56
+ <div className="kyro-toast-icon-container">
57
+ <Icon className="w-4 h-4" />
58
+ </div>
59
+ <div className="kyro-toast-content">
60
+ <p className="kyro-toast-message">{message}</p>
61
+ </div>
62
+ <button
63
+ type="button"
64
+ className="kyro-toast-close group-hover:opacity-100 opacity-40 transition-opacity"
65
+ onClick={onClose}
66
+ >
67
+ <X className="w-3.5 h-3.5" />
115
68
  </button>
116
69
  </div>
117
70
  );
118
71
  }
119
72
 
120
- export function ToastProvider({
121
- children,
122
- toasts = [],
123
- addToast = () => {},
124
- removeToast = () => {},
125
- }: {
73
+ interface ToastProviderProps {
126
74
  children: ReactNode;
127
- toasts?: Toast[];
128
- addToast?: (type: Toast["type"], message: string) => void;
129
- removeToast?: (id: string) => void;
130
- }) {
131
- return (
132
- <ToastContext.Provider value={{ toasts, addToast, removeToast }}>
133
- {children}
134
- </ToastContext.Provider>
135
- );
75
+ }
76
+
77
+ export function ToastProvider({ children }: ToastProviderProps) {
78
+ return <>{children}</>;
136
79
  }
137
80
 
138
81
  export function useToast() {
139
- const context = useContext(ToastContext);
140
- if (!context) {
141
- throw new Error("useToast must be used within a ToastProvider");
142
- }
143
- return context;
82
+ const addToast = useToastStore((state) => state.addToast);
83
+ const removeToast = useToastStore((state) => state.removeToast);
84
+ const toasts = useToastStore((state) => state.toasts);
85
+
86
+ return { toasts, addToast, removeToast };
144
87
  }
@@ -0,0 +1,18 @@
1
+ import { useToastStore } from "../../lib/stores";
2
+ import { Toast } from "./Toast";
3
+
4
+ export function Toaster() {
5
+ console.log('Toaster mounted');
6
+ const toasts = useToastStore((state) => state.toasts);
7
+ const removeToast = useToastStore((state) => state.removeToast);
8
+
9
+ return (
10
+ <div className="kyro-toasts-container" style={{ position: "fixed", bottom: "24px", right: "24px", display: "flex", flexDirection: "column", gap: "12px", zIndex: 9999, pointerEvents: "none" }}>
11
+ {toasts.map((t) => (
12
+ <div key={t.id} style={{ pointerEvents: "auto" }}>
13
+ <Toast type={t.type} message={t.message} onClose={() => removeToast(t.id)} />
14
+ </div>
15
+ ))}
16
+ </div>
17
+ );
18
+ }