@kyro-cms/admin 0.9.3 → 0.9.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.
@@ -27,6 +27,104 @@ interface PageHeaderProps {
27
27
  children?: ReactNode;
28
28
  }
29
29
 
30
+ function BackButton({ back }: { back: NonNullable<PageHeaderProps["back"]> }) {
31
+ if (back.href) {
32
+ return (
33
+ <a
34
+ href={back.href}
35
+ onClick={(e) => {
36
+ if (back.onClick) {
37
+ e.preventDefault();
38
+ back.onClick();
39
+ }
40
+ }}
41
+ className="p-1.5 rounded-lg hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] transition-all"
42
+ >
43
+ <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
44
+ <path d="M19 12H5M12 19l-7-7 7-7" />
45
+ </svg>
46
+ </a>
47
+ );
48
+ }
49
+ return (
50
+ <button
51
+ type="button"
52
+ onClick={back.onClick}
53
+ className="p-1.5 rounded-lg hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] transition-all"
54
+ >
55
+ <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
56
+ <path d="M19 12H5M12 19l-7-7 7-7" />
57
+ </svg>
58
+ </button>
59
+ );
60
+ }
61
+
62
+ function DesktopBreadcrumbs({ breadcrumbs }: { breadcrumbs: Breadcrumb[] }) {
63
+ return 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
+ }
87
+
88
+ function ActionsSlot({ actions }: { actions: NonNullable<PageHeaderProps["actions"]> }) {
89
+ if (Array.isArray(actions)) {
90
+ return (
91
+ <div className="flex items-center gap-3">
92
+ {actions.map((act, i) => (
93
+ <button
94
+ key={i}
95
+ type="button"
96
+ onClick={act.onClick}
97
+ 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 ${
98
+ act.variant === "outline"
99
+ ? "border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)]"
100
+ : act.variant === "ghost"
101
+ ? "text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] shadow-none"
102
+ : "kyro-btn-primary hover:opacity-90"
103
+ } ${act.className || ""}`}
104
+ >
105
+ {act.icon && <act.icon className="w-4 h-4" />}
106
+ {act.label}
107
+ </button>
108
+ ))}
109
+ </div>
110
+ );
111
+ }
112
+ return <>{actions}</>;
113
+ }
114
+
115
+ function SingleAction({ action }: { action: NonNullable<PageHeaderProps["action"]> }) {
116
+ return (
117
+ <button
118
+ type="button"
119
+ onClick={action.onClick}
120
+ className={`kyro-btn kyro-btn-primary flex items-center gap-2 px-6 py-2.5 rounded-xl font-bold text-sm hover:opacity-90 transition-all shadow-lg shadow-[var(--kyro-primary)]/10 w-full lg:w-auto justify-center ${action.className || ""}`}
121
+ >
122
+ {action.icon && <action.icon className="w-4 h-4" />}
123
+ {action.label}
124
+ </button>
125
+ );
126
+ }
127
+
30
128
  export function PageHeader({
31
129
  title,
32
130
  description,
@@ -38,66 +136,75 @@ export function PageHeader({
38
136
  actions,
39
137
  children,
40
138
  }: PageHeaderProps) {
139
+ const lastBreadcrumb = breadcrumbs?.[breadcrumbs.length - 1];
140
+
41
141
  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 */}
142
+ <div className="surface-tile px-3 md:px-6 py-3 md:pt-4 mb-4 md:mb-8">
143
+ {/* ─── MOBILE ─── */}
144
+ <div className="md:hidden space-y-2">
45
145
  {(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" />
146
+ <div className="flex items-center gap-2">
147
+ {back && <BackButton back={back} />}
148
+ <details className="group [&::-webkit-details-marker]:hidden flex-1 min-w-0">
149
+ <summary className="flex items-center gap-2 cursor-pointer list-none">
150
+ <span className="flex-1 text-[10px] font-bold tracking-widest text-[var(--kyro-text-secondary)] truncate">
151
+ {lastBreadcrumb?.label || ""}
152
+ </span>
153
+ <svg className="w-3 h-3 text-[var(--kyro-text-secondary)] opacity-40 group-open:rotate-180 transition-transform" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
154
+ <path d="M6 9l6 6 6-6" />
60
155
  </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>
156
+ </summary>
157
+ <div className="mt-2 pt-2 border-t border-[var(--kyro-border)] space-y-2">
158
+ {breadcrumbs && (
159
+ <div className="flex items-center gap-2">
160
+ {breadcrumbs.map((crumb: Breadcrumb, i: number) => (
161
+ <React.Fragment key={i}>
162
+ {i > 0 && <span className="opacity-20 text-[10px]">/</span>}
163
+ {crumb.href || crumb.onClick ? (
164
+ <a
165
+ href={crumb.href}
166
+ onClick={(e) => {
167
+ if (crumb.onClick) { e.preventDefault(); crumb.onClick(); }
168
+ }}
169
+ className="text-[10px] font-bold tracking-widest text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-primary)] transition-all"
170
+ >
171
+ {crumb.label}
172
+ </a>
173
+ ) : (
174
+ <span className="text-[10px] font-bold tracking-widest opacity-40">{crumb.label}</span>
175
+ )}
176
+ </React.Fragment>
177
+ ))}
178
+ </div>
83
179
  )}
84
- </React.Fragment>
85
- ))}
180
+ {metadata && (
181
+ <div className="flex items-center gap-2 flex-wrap">
182
+ {metadata.map((item, i) => (
183
+ <React.Fragment key={i}>{item}</React.Fragment>
184
+ ))}
185
+ </div>
186
+ )}
187
+ {children}
188
+ </div>
189
+ </details>
86
190
  </div>
87
191
  )}
88
192
 
89
- <div className="flex items-center gap-3">
90
- {Icon && <Icon className="w-6 h-6 text-[var(--kyro-primary)]" />}
193
+ <div className="flex items-center gap-2">
194
+ {Icon && <Icon className="w-5 h-5 text-[var(--kyro-primary)] shrink-0" />}
91
195
  {title && (
92
- <h1 className="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)] truncate">
196
+ <h1 className="text-lg font-bold tracking-tighter text-[var(--kyro-text-primary)] truncate">
93
197
  {title}
94
198
  </h1>
95
199
  )}
200
+ {metadata && !description && (
201
+ <span className="h-2 w-2 rounded-full bg-[var(--kyro-primary)] shrink-0" />
202
+ )}
96
203
  </div>
97
204
 
98
205
  {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">
206
+ <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
207
+ <p className="text-[var(--kyro-text-secondary)] font-medium opacity-60 line-clamp-1 min-w-0 text-xs">
101
208
  {description}
102
209
  </p>
103
210
  {metadata && (
@@ -110,49 +217,64 @@ export function PageHeader({
110
217
  ))}
111
218
  </div>
112
219
  )}
113
- {children}
114
220
  </div>
115
221
  )}
116
222
  </div>
117
223
 
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
- : "kyro-btn-primary hover:opacity-90"
133
- } ${act.className || ""}`}
134
- >
135
- {act.icon && <act.icon className="w-4 h-4" />}
136
- {act.label}
137
- </button>
138
- ))}
224
+ {/* ─── DESKTOP ─── */}
225
+ <div className="hidden md:flex md:flex-row md:items-center justify-between gap-6">
226
+ <div className="min-w-0 flex-1">
227
+ {(breadcrumbs || back) && (
228
+ <div className="flex items-center gap-2 mb-3">
229
+ {back && <BackButton back={back} />}
230
+ {breadcrumbs && <DesktopBreadcrumbs breadcrumbs={breadcrumbs} />}
139
231
  </div>
140
- ) : (
141
- actions
142
- )
143
- )}
144
- {action && (
145
- <button
146
- type="button"
147
- onClick={action.onClick}
148
- className={`kyro-btn kyro-btn-primary flex items-center gap-2 px-6 py-2.5 rounded-xl font-bold text-sm 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
- )}
232
+ )}
233
+
234
+ <div className="flex items-center gap-3">
235
+ {Icon && <Icon className="w-6 h-6 text-[var(--kyro-primary)]" />}
236
+ {title && (
237
+ <h1 className="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)] truncate">
238
+ {title}
239
+ </h1>
240
+ )}
241
+ </div>
242
+
243
+ {(description || metadata) && (
244
+ <div className="flex flex-wrap items-center gap-x-2 gap-y-1 mt-1">
245
+ {description && (
246
+ <p className="text-[var(--kyro-text-secondary)] font-medium opacity-60 line-clamp-1 min-w-0">
247
+ {description}
248
+ </p>
249
+ )}
250
+ {metadata && (
251
+ <div className="flex items-center gap-2">
252
+ {metadata.map((item: ReactNode, i: number) => (
253
+ <React.Fragment key={i}>
254
+ {i === 0 && (description || i > 0) && <span className="opacity-20 ml-1">·</span>}
255
+ {item}
256
+ </React.Fragment>
257
+ ))}
258
+ </div>
259
+ )}
260
+ {children}
261
+ </div>
262
+ )}
263
+ </div>
264
+
265
+ <div className="flex items-center gap-3 flex-wrap shrink-0">
266
+ {actions && <ActionsSlot actions={actions} />}
267
+ {action && <SingleAction action={action} />}
268
+ </div>
154
269
  </div>
270
+
271
+ {/* mobile actions */}
272
+ {(actions || action) && (
273
+ <div className="md:hidden flex items-center gap-2 mt-3 pt-3 border-t border-[var(--kyro-border)]">
274
+ {action && <SingleAction action={action} />}
275
+ {actions && <ActionsSlot actions={actions} />}
276
+ </div>
277
+ )}
155
278
  </div>
156
279
  );
157
280
  }
158
-
@@ -13,7 +13,7 @@ export function Pagination({ page, totalPages, totalDocs, limit, onPageChange, o
13
13
  if (totalPages <= 1) return null;
14
14
 
15
15
  return (
16
- <div className="flex items-center justify-between px-4 py-3 border-t border-[var(--kyro-border)]">
16
+ <div className="flex flex-col sm:flex-row items-center justify-between gap-3 px-4 py-3 border-t border-[var(--kyro-border)]">
17
17
  {totalDocs !== undefined && limit ? (
18
18
  <span className="text-xs text-[var(--kyro-text-secondary)] font-medium">
19
19
  Showing {(page - 1) * limit + 1} to {Math.min(page * limit, totalDocs)} of {totalDocs}
@@ -21,7 +21,7 @@ export function Pagination({ page, totalPages, totalDocs, limit, onPageChange, o
21
21
  ) : (
22
22
  <span />
23
23
  )}
24
- <div className="flex items-center gap-2">
24
+ <div className="flex items-center gap-2 flex-wrap justify-center">
25
25
  {onLimitChange && (
26
26
  <select
27
27
  value={limit}
@@ -47,10 +47,10 @@ export function SlidePanel({
47
47
  }, [open, onClose]);
48
48
 
49
49
  const widthClasses = {
50
- sm: "w-[320px]",
51
- md: "w-[400px]",
52
- lg: "w-[550px]",
53
- xl: "w-[700px]",
50
+ sm: "w-full sm:w-[320px]",
51
+ md: "w-full sm:w-[400px]",
52
+ lg: "w-full sm:w-[550px]",
53
+ xl: "w-full sm:w-[700px]",
54
54
  };
55
55
 
56
56
  if (!open || !hydrated) return null;
@@ -52,7 +52,6 @@ export function Toast({ type, message, onClose }: ToastProps) {
52
52
  onMouseEnter={() => setIsPaused(true)}
53
53
  onMouseLeave={() => setIsPaused(false)}
54
54
  >
55
- <div className="kyro-toast-accent" />
56
55
  <div className="kyro-toast-icon-container">
57
56
  <Icon className="w-4 h-4" />
58
57
  </div>
@@ -61,7 +60,7 @@ export function Toast({ type, message, onClose }: ToastProps) {
61
60
  </div>
62
61
  <button
63
62
  type="button"
64
- className="kyro-toast-close group-hover:opacity-100 opacity-40 transition-opacity"
63
+ className="kyro-toast-close"
65
64
  onClick={onClose}
66
65
  >
67
66
  <X className="w-3.5 h-3.5" />
@@ -243,10 +243,6 @@ export default debug;
243
243
  { pattern: "/webhooks", entrypoint: "./pages/webhooks.astro" },
244
244
  { pattern: "/plugins", entrypoint: "./pages/plugins.astro" },
245
245
  { pattern: "/marketplace", entrypoint: "./pages/marketplace.astro" },
246
- {
247
- pattern: "/api-explorer",
248
- entrypoint: "./pages/api-explorer.astro",
249
- },
250
246
  { pattern: "/graphql", entrypoint: "./pages/graphql.astro" },
251
247
  {
252
248
  pattern: "/rest-playground",
@@ -177,17 +177,62 @@ if (includeSiteName) {
177
177
  })();
178
178
  </script>
179
179
  </head>
180
- <body class="bg-[var(--kyro-bg)] antialiased text-[var(--kyro-text-primary)]">
180
+ <body class="bg-[var(--kyro-bg)] antialiased text-[var(--kyro-text-primary)] overflow-x-hidden overflow-y-auto">
181
181
  <div id="kyro-user-data" data-user=""></div>
182
- <div class="flex h-screen p-6 gap-6 overflow-hidden">
182
+
183
+ <!-- Mobile Sidebar Backdrop -->
184
+ <div id="mobile-sidebar-backdrop" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-40 hidden md:hidden transition-opacity opacity-0 duration-300"></div>
185
+
186
+ <div class="flex h-[100dvh] md:h-screen p-0 md:p-6 gap-0 md:gap-6 overflow-hidden w-full relative">
183
187
  <Sidebar title={title} />
184
188
 
185
189
  <!-- Main Content Column -->
186
- <main class="flex-1 flex flex-col gap-6 overflow-hidden">
187
- <slot />
190
+ <main class="flex-1 flex flex-col gap-0 md:gap-6 overflow-hidden relative w-full h-full max-w-full">
191
+ <!-- Mobile Header -->
192
+ <header class="md:hidden flex items-center justify-between p-4 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface)] shrink-0 z-30 shadow-sm">
193
+ <div class="flex items-center gap-3">
194
+ <button id="mobile-menu-btn" class="p-2 -ml-2 rounded-lg text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] transition-colors">
195
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-menu"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>
196
+ </button>
197
+ <span class="font-bold text-lg">{title}</span>
198
+ </div>
199
+ </header>
200
+
201
+ <div class="flex-1 overflow-y-auto p-4 md:p-0">
202
+ <slot />
203
+ </div>
188
204
  </main>
189
205
  </div>
190
206
 
207
+ <!-- Mobile Sidebar Logic -->
208
+ <script is:inline>
209
+ const menuBtn = document.getElementById("mobile-menu-btn");
210
+ const closeBtn = document.getElementById("mobile-close-btn");
211
+ const backdrop = document.getElementById("mobile-sidebar-backdrop");
212
+ const sidebar = document.getElementById("kyro-sidebar");
213
+
214
+ const toggleSidebar = () => {
215
+ if (!sidebar) return;
216
+ const isOpen = sidebar.classList.contains("translate-x-0");
217
+ if (isOpen) {
218
+ sidebar.classList.remove("translate-x-0");
219
+ sidebar.classList.add("-translate-x-full");
220
+ backdrop?.classList.remove("opacity-100");
221
+ setTimeout(() => backdrop?.classList.add("hidden"), 300);
222
+ } else {
223
+ sidebar.classList.remove("-translate-x-full");
224
+ sidebar.classList.add("translate-x-0");
225
+ backdrop?.classList.remove("hidden");
226
+ // small delay to allow display:block to apply before opacity transition
227
+ setTimeout(() => backdrop?.classList.add("opacity-100"), 10);
228
+ }
229
+ };
230
+
231
+ menuBtn?.addEventListener("click", toggleSidebar);
232
+ closeBtn?.addEventListener("click", toggleSidebar);
233
+ backdrop?.addEventListener("click", toggleSidebar);
234
+ </script>
235
+
191
236
  <!-- Logout Confirmation Modal -->
192
237
  <div
193
238
  id="logout-modal"
@@ -50,6 +50,7 @@ export function useResourceManager<T extends { id: string }>(
50
50
  await apiDelete(`${options.endpoint}/${id}`);
51
51
  setItems((prev) => prev.filter((item) => item.id !== id));
52
52
  options.onSuccess?.("delete", id);
53
+ toast.success(`${resourceName} deleted`);
53
54
  } catch (e: unknown) {
54
55
  const message = e instanceof Error ? e.message : `Failed to delete ${resourceName}`;
55
56
  toast.error(message);
@@ -29,25 +29,6 @@ const collections = collectionsData.collections || [];
29
29
  </p>
30
30
  </div>
31
31
  <div class="flex items-center gap-4">
32
- <a
33
- href={`${adminPath}/api-explorer`}
34
- class="flex items-center gap-2 px-4 py-2 bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-primary)] rounded-lg font-bold text-sm hover:bg-[var(--kyro-surface)] transition-all border border-[var(--kyro-border)]"
35
- >
36
- <svg
37
- class="w-4 h-4"
38
- fill="none"
39
- stroke="currentColor"
40
- viewBox="0 0 24 24"
41
- >
42
- <path
43
- stroke-linecap="round"
44
- stroke-linejoin="round"
45
- stroke-width="2"
46
- d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
47
- ></path>
48
- </svg>
49
- Explorer
50
- </a>
51
32
  </div>
52
33
  </div>
53
34
  </div>
@@ -1393,14 +1393,14 @@
1393
1393
  color: var(--kyro-text-secondary);
1394
1394
  }
1395
1395
 
1396
- /* Toast — Colored Flat */
1396
+ /* Toast — Subtle Modern */
1397
1397
  .kyro-toasts-container {
1398
1398
  position: fixed;
1399
1399
  bottom: 16px;
1400
1400
  right: 16px;
1401
1401
  display: flex;
1402
1402
  flex-direction: column;
1403
- gap: 6px;
1403
+ gap: 8px;
1404
1404
  z-index: 9999;
1405
1405
  pointer-events: none;
1406
1406
  }
@@ -1408,25 +1408,34 @@
1408
1408
  .kyro-toast {
1409
1409
  display: flex;
1410
1410
  align-items: center;
1411
- gap: 8px;
1411
+ gap: 10px;
1412
1412
  min-width: 240px;
1413
1413
  max-width: 340px;
1414
- padding: 8px 10px;
1415
- color: white;
1416
- border-radius: 8px;
1414
+ padding: 12px 14px;
1415
+ border-radius: 12px;
1417
1416
  box-shadow:
1418
- 0 2px 4px -1px rgba(0, 0, 0, 0.08),
1419
- 0 8px 16px -4px rgba(0, 0, 0, 0.15),
1420
- 0 16px 24px -8px rgba(0, 0, 0, 0.2);
1417
+ 0 4px 12px rgba(0, 0, 0, 0.06),
1418
+ 0 8px 24px rgba(0, 0, 0, 0.04);
1421
1419
  pointer-events: auto;
1422
1420
  position: relative;
1423
- transition: box-shadow 0.2s ease;
1421
+ overflow: hidden;
1422
+ border: 1px solid var(--kyro-border);
1423
+ background: var(--kyro-surface);
1424
1424
  }
1425
1425
 
1426
- .kyro-toast-success { background: #16a34a; }
1427
- .kyro-toast-error { background: #dc2626; }
1428
- .kyro-toast-warning { background: #d97706; }
1429
- .kyro-toast-info { background: #4f46e5; }
1426
+ .kyro-toast::before {
1427
+ content: '';
1428
+ position: absolute;
1429
+ left: 0;
1430
+ top: 0;
1431
+ bottom: 0;
1432
+ width: 3px;
1433
+ }
1434
+
1435
+ .kyro-toast-success::before { background: var(--kyro-success); }
1436
+ .kyro-toast-error::before { background: var(--kyro-danger); }
1437
+ .kyro-toast-warning::before { background: var(--kyro-warning); }
1438
+ .kyro-toast-info::before { background: #6366f1; }
1430
1439
 
1431
1440
  .kyro-toast-icon-container {
1432
1441
  flex-shrink: 0;
@@ -1435,9 +1444,13 @@
1435
1444
  display: flex;
1436
1445
  align-items: center;
1437
1446
  justify-content: center;
1438
- color: rgba(255, 255, 255, 0.85);
1439
1447
  }
1440
1448
 
1449
+ .kyro-toast-success .kyro-toast-icon-container { color: var(--kyro-success); }
1450
+ .kyro-toast-error .kyro-toast-icon-container { color: var(--kyro-danger); }
1451
+ .kyro-toast-warning .kyro-toast-icon-container { color: var(--kyro-warning); }
1452
+ .kyro-toast-info .kyro-toast-icon-container { color: #6366f1; }
1453
+
1441
1454
  .kyro-toast-content {
1442
1455
  flex: 1;
1443
1456
  min-width: 0;
@@ -1447,21 +1460,23 @@
1447
1460
  font-size: 12px;
1448
1461
  font-weight: 500;
1449
1462
  letter-spacing: -0.01em;
1450
- color: rgba(255, 255, 255, 0.92);
1463
+ color: var(--kyro-text-primary);
1451
1464
  line-height: 1.3;
1452
1465
  }
1453
1466
 
1454
1467
  .kyro-toast-close {
1455
1468
  padding: 3px;
1456
1469
  border-radius: 4px;
1457
- color: rgba(255, 255, 255, 0.35);
1470
+ color: var(--kyro-text-muted);
1458
1471
  transition: all 0.15s ease;
1459
1472
  flex-shrink: 0;
1473
+ opacity: 0.4;
1460
1474
  }
1461
1475
 
1462
1476
  .kyro-toast-close:hover {
1463
- background: rgba(255, 255, 255, 0.15);
1464
- color: white;
1477
+ background: var(--kyro-surface-accent);
1478
+ opacity: 1;
1479
+ color: var(--kyro-text-primary);
1465
1480
  }
1466
1481
 
1467
1482
  /* Spinner — Monochrome */