@jant/core 0.2.17 → 0.2.19
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/app.d.ts +1 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +307 -137
- package/dist/client.js +1 -0
- package/dist/i18n/context.d.ts +2 -2
- package/dist/i18n/context.js +1 -1
- package/dist/i18n/i18n.d.ts +1 -1
- package/dist/i18n/i18n.js +1 -1
- package/dist/i18n/index.d.ts +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.d.ts.map +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.d.ts.map +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/lib/config.d.ts +44 -10
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +69 -44
- package/dist/lib/constants.d.ts +2 -1
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +5 -2
- package/dist/lib/image-processor.js +0 -4
- package/dist/lib/media-upload.js +104 -0
- package/dist/lib/sse.d.ts +82 -13
- package/dist/lib/sse.d.ts.map +1 -1
- package/dist/lib/sse.js +115 -17
- package/dist/lib/theme.d.ts +44 -0
- package/dist/lib/theme.d.ts.map +1 -0
- package/dist/lib/theme.js +65 -0
- package/dist/routes/api/upload.js +16 -18
- package/dist/routes/dash/appearance.d.ts +13 -0
- package/dist/routes/dash/appearance.d.ts.map +1 -0
- package/dist/routes/dash/appearance.js +160 -0
- package/dist/routes/dash/collections.js +5 -13
- package/dist/routes/dash/media.js +17 -167
- package/dist/routes/dash/pages.js +4 -10
- package/dist/routes/dash/posts.js +4 -10
- package/dist/routes/dash/redirects.js +3 -7
- package/dist/routes/dash/settings.d.ts.map +1 -1
- package/dist/routes/dash/settings.js +52 -42
- package/dist/services/settings.d.ts +1 -0
- package/dist/services/settings.d.ts.map +1 -1
- package/dist/services/settings.js +3 -0
- package/dist/theme/color-themes.d.ts +30 -0
- package/dist/theme/color-themes.d.ts.map +1 -0
- package/dist/theme/color-themes.js +268 -0
- package/dist/theme/layouts/BaseLayout.d.ts +5 -0
- package/dist/theme/layouts/BaseLayout.d.ts.map +1 -1
- package/dist/theme/layouts/BaseLayout.js +70 -3
- package/dist/theme/layouts/DashLayout.d.ts +2 -0
- package/dist/theme/layouts/DashLayout.d.ts.map +1 -1
- package/dist/theme/layouts/DashLayout.js +11 -1
- package/dist/theme/layouts/index.d.ts +1 -1
- package/dist/theme/layouts/index.d.ts.map +1 -1
- package/dist/types.d.ts +53 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +52 -0
- package/package.json +1 -1
- package/src/app.tsx +260 -81
- package/src/client.ts +1 -0
- package/src/db/migrations/{0000_solid_moon_knight.sql → 0000_square_wallflower.sql} +3 -3
- package/src/db/migrations/meta/0000_snapshot.json +9 -9
- package/src/db/migrations/meta/_journal.json +2 -30
- package/src/i18n/context.tsx +2 -2
- package/src/i18n/i18n.ts +1 -1
- package/src/i18n/index.ts +1 -1
- package/src/i18n/locales/en.po +328 -252
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +315 -278
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +315 -278
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/lib/config.ts +73 -47
- package/src/lib/constants.ts +3 -0
- package/src/lib/image-processor.ts +0 -7
- package/src/lib/media-upload.ts +148 -0
- package/src/lib/sse.ts +156 -16
- package/src/lib/theme.ts +86 -0
- package/src/preset.css +9 -0
- package/src/routes/api/upload.ts +12 -18
- package/src/routes/dash/appearance.tsx +176 -0
- package/src/routes/dash/collections.tsx +5 -13
- package/src/routes/dash/media.tsx +16 -165
- package/src/routes/dash/pages.tsx +4 -10
- package/src/routes/dash/posts.tsx +4 -10
- package/src/routes/dash/redirects.tsx +3 -7
- package/src/routes/dash/settings.tsx +71 -55
- package/src/services/settings.ts +5 -0
- package/src/styles/components.css +93 -0
- package/src/theme/color-themes.ts +321 -0
- package/src/theme/layouts/BaseLayout.tsx +61 -1
- package/src/theme/layouts/DashLayout.tsx +14 -3
- package/src/theme/layouts/index.ts +5 -1
- package/src/types.ts +62 -1
- package/src/db/migrations/0001_add_search_fts.sql +0 -40
- package/src/db/migrations/0002_collection_path.sql +0 -2
- package/src/db/migrations/0003_collection_path_nullable.sql +0 -21
- package/src/db/migrations/0004_media_uuid.sql +0 -35
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
ActionButtons,
|
|
16
16
|
} from "../../theme/components/index.js";
|
|
17
17
|
import * as sqid from "../../lib/sqid.js";
|
|
18
|
-
import {
|
|
18
|
+
import { dsRedirect } from "../../lib/sse.js";
|
|
19
19
|
|
|
20
20
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
21
21
|
|
|
@@ -105,9 +105,7 @@ postsRoutes.post("/", async (c) => {
|
|
|
105
105
|
path: body.path || undefined,
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
return
|
|
109
|
-
await stream.redirect(`/dash/posts/${sqid.encode(post.id)}`);
|
|
110
|
-
});
|
|
108
|
+
return dsRedirect(`/dash/posts/${sqid.encode(post.id)}`);
|
|
111
109
|
});
|
|
112
110
|
|
|
113
111
|
function ViewPostContent({ post }: { post: Post }) {
|
|
@@ -227,9 +225,7 @@ postsRoutes.post("/:id", async (c) => {
|
|
|
227
225
|
path: body.path || null,
|
|
228
226
|
});
|
|
229
227
|
|
|
230
|
-
return
|
|
231
|
-
await stream.redirect(`/dash/posts/${sqid.encode(id)}`);
|
|
232
|
-
});
|
|
228
|
+
return dsRedirect(`/dash/posts/${sqid.encode(id)}`);
|
|
233
229
|
});
|
|
234
230
|
|
|
235
231
|
// Delete post
|
|
@@ -239,7 +235,5 @@ postsRoutes.post("/:id/delete", async (c) => {
|
|
|
239
235
|
|
|
240
236
|
await c.var.services.posts.delete(id);
|
|
241
237
|
|
|
242
|
-
return
|
|
243
|
-
await stream.redirect("/dash/posts");
|
|
244
|
-
});
|
|
238
|
+
return dsRedirect("/dash/posts");
|
|
245
239
|
});
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
ActionButtons,
|
|
15
15
|
CrudPageHeader,
|
|
16
16
|
} from "../../theme/components/index.js";
|
|
17
|
-
import {
|
|
17
|
+
import { dsRedirect } from "../../lib/sse.js";
|
|
18
18
|
|
|
19
19
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
20
20
|
|
|
@@ -219,9 +219,7 @@ redirectsRoutes.post("/", async (c) => {
|
|
|
219
219
|
const type = parseInt(body.type, 10) as 301 | 302;
|
|
220
220
|
await c.var.services.redirects.create(body.fromPath, body.toPath, type);
|
|
221
221
|
|
|
222
|
-
return
|
|
223
|
-
await stream.redirect("/dash/redirects");
|
|
224
|
-
});
|
|
222
|
+
return dsRedirect("/dash/redirects");
|
|
225
223
|
});
|
|
226
224
|
|
|
227
225
|
// Delete redirect
|
|
@@ -231,7 +229,5 @@ redirectsRoutes.post("/:id/delete", async (c) => {
|
|
|
231
229
|
await c.var.services.redirects.delete(id);
|
|
232
230
|
}
|
|
233
231
|
|
|
234
|
-
return
|
|
235
|
-
await stream.redirect("/dash/redirects");
|
|
236
|
-
});
|
|
232
|
+
return dsRedirect("/dash/redirects");
|
|
237
233
|
});
|
|
@@ -7,12 +7,17 @@ import { useLingui } from "@lingui/react/macro";
|
|
|
7
7
|
import type { Bindings } from "../../types.js";
|
|
8
8
|
import type { AppVariables } from "../../app.js";
|
|
9
9
|
import { DashLayout } from "../../theme/layouts/index.js";
|
|
10
|
-
import { sse } from "../../lib/sse.js";
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
import { sse, dsToast } from "../../lib/sse.js";
|
|
11
|
+
import { getSiteLanguage, getConfigFallback } from "../../lib/config.js";
|
|
12
|
+
|
|
13
|
+
/** Escape HTML special characters for safe insertion into HTML strings */
|
|
14
|
+
function escapeHtml(str: string): string {
|
|
15
|
+
return str
|
|
16
|
+
.replace(/&/g, "&")
|
|
17
|
+
.replace(/</g, "<")
|
|
18
|
+
.replace(/>/g, ">")
|
|
19
|
+
.replace(/"/g, """);
|
|
20
|
+
}
|
|
16
21
|
|
|
17
22
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
18
23
|
|
|
@@ -22,12 +27,14 @@ function SettingsContent({
|
|
|
22
27
|
siteName,
|
|
23
28
|
siteDescription,
|
|
24
29
|
siteLanguage,
|
|
25
|
-
|
|
30
|
+
siteNameFallback,
|
|
31
|
+
siteDescriptionFallback,
|
|
26
32
|
}: {
|
|
27
33
|
siteName: string;
|
|
28
34
|
siteDescription: string;
|
|
29
35
|
siteLanguage: string;
|
|
30
|
-
|
|
36
|
+
siteNameFallback: string;
|
|
37
|
+
siteDescriptionFallback: string;
|
|
31
38
|
}) {
|
|
32
39
|
const { t } = useLingui();
|
|
33
40
|
|
|
@@ -43,27 +50,11 @@ function SettingsContent({
|
|
|
43
50
|
{t({ message: "Settings", comment: "@context: Dashboard heading" })}
|
|
44
51
|
</h1>
|
|
45
52
|
|
|
46
|
-
{saved && (
|
|
47
|
-
<div
|
|
48
|
-
id="settings-saved-toast"
|
|
49
|
-
class="alert mb-4 max-w-lg transition-opacity duration-300"
|
|
50
|
-
data-init={`console.log('[toast] init fired at', Date.now()); history.replaceState({}, '', '/dash/settings'); setTimeout(() => { console.log('[toast] hiding at', Date.now()); const el = document.getElementById('settings-saved-toast'); if (el) { el.style.opacity = '0'; setTimeout(() => el.remove(), 300) } }, 3000)`}
|
|
51
|
-
>
|
|
52
|
-
<h2>
|
|
53
|
-
{t({
|
|
54
|
-
message: "Settings saved successfully.",
|
|
55
|
-
comment: "@context: Toast message after saving settings",
|
|
56
|
-
})}
|
|
57
|
-
</h2>
|
|
58
|
-
</div>
|
|
59
|
-
)}
|
|
60
|
-
|
|
61
53
|
<div class="flex flex-col gap-6 max-w-lg">
|
|
62
54
|
<form
|
|
63
55
|
data-signals={generalSignals}
|
|
64
56
|
data-on:submit__prevent="@post('/dash/settings')"
|
|
65
57
|
>
|
|
66
|
-
<div id="settings-message"></div>
|
|
67
58
|
<div class="card">
|
|
68
59
|
<header>
|
|
69
60
|
<h2>
|
|
@@ -85,7 +76,7 @@ function SettingsContent({
|
|
|
85
76
|
type="text"
|
|
86
77
|
data-bind="siteName"
|
|
87
78
|
class="input"
|
|
88
|
-
|
|
79
|
+
placeholder={siteNameFallback}
|
|
89
80
|
/>
|
|
90
81
|
</div>
|
|
91
82
|
|
|
@@ -96,7 +87,12 @@ function SettingsContent({
|
|
|
96
87
|
comment: "@context: Settings form field",
|
|
97
88
|
})}
|
|
98
89
|
</label>
|
|
99
|
-
<textarea
|
|
90
|
+
<textarea
|
|
91
|
+
data-bind="siteDescription"
|
|
92
|
+
class="textarea"
|
|
93
|
+
rows={3}
|
|
94
|
+
placeholder={siteDescriptionFallback}
|
|
95
|
+
>
|
|
100
96
|
{siteDescription}
|
|
101
97
|
</textarea>
|
|
102
98
|
</div>
|
|
@@ -135,7 +131,6 @@ function SettingsContent({
|
|
|
135
131
|
data-signals="{currentPassword: '', newPassword: '', confirmPassword: ''}"
|
|
136
132
|
data-on:submit__prevent="@post('/dash/settings/password')"
|
|
137
133
|
>
|
|
138
|
-
<div id="password-message"></div>
|
|
139
134
|
<div class="card">
|
|
140
135
|
<header>
|
|
141
136
|
<h2>
|
|
@@ -212,23 +207,33 @@ function SettingsContent({
|
|
|
212
207
|
|
|
213
208
|
// Settings page
|
|
214
209
|
settingsRoutes.get("/", async (c) => {
|
|
215
|
-
const
|
|
216
|
-
|
|
210
|
+
const { settings } = c.var.services;
|
|
211
|
+
|
|
212
|
+
// Fetch raw DB values (null if not set)
|
|
213
|
+
const dbSiteName = await settings.get("SITE_NAME");
|
|
214
|
+
const dbSiteDescription = await settings.get("SITE_DESCRIPTION");
|
|
217
215
|
const siteLanguage = await getSiteLanguage(c);
|
|
216
|
+
|
|
217
|
+
// Fallback values (ENV > Default) for placeholders
|
|
218
|
+
const siteNameFallback = getConfigFallback(c, "SITE_NAME");
|
|
219
|
+
const siteDescriptionFallback = getConfigFallback(c, "SITE_DESCRIPTION");
|
|
220
|
+
|
|
218
221
|
const saved = c.req.query("saved") !== undefined;
|
|
219
222
|
|
|
220
223
|
return c.html(
|
|
221
224
|
<DashLayout
|
|
222
225
|
c={c}
|
|
223
226
|
title="Settings"
|
|
224
|
-
siteName={
|
|
227
|
+
siteName={dbSiteName || siteNameFallback}
|
|
225
228
|
currentPath="/dash/settings"
|
|
229
|
+
toast={saved ? { message: "Settings saved successfully." } : undefined}
|
|
226
230
|
>
|
|
227
231
|
<SettingsContent
|
|
228
|
-
siteName={
|
|
229
|
-
siteDescription={
|
|
232
|
+
siteName={dbSiteName || ""}
|
|
233
|
+
siteDescription={dbSiteDescription || ""}
|
|
230
234
|
siteLanguage={siteLanguage}
|
|
231
|
-
|
|
235
|
+
siteNameFallback={siteNameFallback}
|
|
236
|
+
siteDescriptionFallback={siteDescriptionFallback}
|
|
232
237
|
/>
|
|
233
238
|
</DashLayout>,
|
|
234
239
|
);
|
|
@@ -242,26 +247,47 @@ settingsRoutes.post("/", async (c) => {
|
|
|
242
247
|
siteLanguage: string;
|
|
243
248
|
}>();
|
|
244
249
|
|
|
245
|
-
const
|
|
246
|
-
(await c.var.services.settings.get("SITE_LANGUAGE")) ?? "en";
|
|
250
|
+
const { settings } = c.var.services;
|
|
247
251
|
|
|
248
|
-
await
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
252
|
+
const oldLanguage = (await settings.get("SITE_LANGUAGE")) ?? "en";
|
|
253
|
+
|
|
254
|
+
// For text fields: empty = remove from DB (fall back to ENV > Default)
|
|
255
|
+
if (body.siteName.trim()) {
|
|
256
|
+
await settings.set("SITE_NAME", body.siteName.trim());
|
|
257
|
+
} else {
|
|
258
|
+
await settings.remove("SITE_NAME");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (body.siteDescription.trim()) {
|
|
262
|
+
await settings.set("SITE_DESCRIPTION", body.siteDescription.trim());
|
|
263
|
+
} else {
|
|
264
|
+
await settings.remove("SITE_DESCRIPTION");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Language always has a value from the select
|
|
268
|
+
await settings.set("SITE_LANGUAGE", body.siteLanguage);
|
|
253
269
|
|
|
254
270
|
const languageChanged = oldLanguage !== body.siteLanguage;
|
|
255
271
|
|
|
272
|
+
// Determine the effective display name after save
|
|
273
|
+
const displayName = body.siteName.trim() || getConfigFallback(c, "SITE_NAME");
|
|
274
|
+
|
|
256
275
|
return sse(c, async (stream) => {
|
|
257
276
|
if (languageChanged) {
|
|
258
277
|
// Language changed - full reload needed to update all UI text
|
|
259
278
|
await stream.redirect("/dash/settings?saved");
|
|
260
279
|
} else {
|
|
261
|
-
|
|
280
|
+
const escaped = escapeHtml(displayName);
|
|
281
|
+
// Update header site name
|
|
262
282
|
await stream.patchElements(
|
|
263
|
-
|
|
283
|
+
`<a id="site-name" href="/dash" class="font-semibold">${escaped}</a>`,
|
|
264
284
|
);
|
|
285
|
+
// Update page title
|
|
286
|
+
await stream.patchElements(`Settings - ${escaped}`, {
|
|
287
|
+
mode: "inner",
|
|
288
|
+
selector: "title",
|
|
289
|
+
});
|
|
290
|
+
await stream.toast("Settings saved successfully.");
|
|
265
291
|
}
|
|
266
292
|
});
|
|
267
293
|
});
|
|
@@ -275,11 +301,7 @@ settingsRoutes.post("/password", async (c) => {
|
|
|
275
301
|
}>();
|
|
276
302
|
|
|
277
303
|
if (body.newPassword !== body.confirmPassword) {
|
|
278
|
-
return
|
|
279
|
-
await stream.patchElements(
|
|
280
|
-
'<div id="password-message"><div class="alert-destructive mb-4"><h2>Passwords do not match.</h2></div></div>',
|
|
281
|
-
);
|
|
282
|
-
});
|
|
304
|
+
return dsToast("Passwords do not match.", "error");
|
|
283
305
|
}
|
|
284
306
|
|
|
285
307
|
try {
|
|
@@ -292,17 +314,11 @@ settingsRoutes.post("/password", async (c) => {
|
|
|
292
314
|
headers: c.req.raw.headers,
|
|
293
315
|
});
|
|
294
316
|
} catch {
|
|
295
|
-
return
|
|
296
|
-
await stream.patchElements(
|
|
297
|
-
'<div id="password-message"><div class="alert-destructive mb-4"><h2>Current password is incorrect.</h2></div></div>',
|
|
298
|
-
);
|
|
299
|
-
});
|
|
317
|
+
return dsToast("Current password is incorrect.", "error");
|
|
300
318
|
}
|
|
301
319
|
|
|
302
320
|
return sse(c, async (stream) => {
|
|
303
|
-
await stream.
|
|
304
|
-
'<div id="password-message"><div class="alert mb-4"><h2>Password changed successfully.</h2></div></div>',
|
|
305
|
-
);
|
|
321
|
+
await stream.toast("Password changed successfully.");
|
|
306
322
|
await stream.patchSignals({
|
|
307
323
|
currentPassword: "",
|
|
308
324
|
newPassword: "",
|
package/src/services/settings.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface SettingsService {
|
|
|
19
19
|
getAll(): Promise<Record<string, string>>;
|
|
20
20
|
set(key: SettingsKey, value: string): Promise<void>;
|
|
21
21
|
setMany(entries: Partial<Record<SettingsKey, string>>): Promise<void>;
|
|
22
|
+
remove(key: SettingsKey): Promise<void>;
|
|
22
23
|
isOnboardingComplete(): Promise<boolean>;
|
|
23
24
|
completeOnboarding(): Promise<void>;
|
|
24
25
|
}
|
|
@@ -54,6 +55,10 @@ export function createSettingsService(db: Database): SettingsService {
|
|
|
54
55
|
});
|
|
55
56
|
},
|
|
56
57
|
|
|
58
|
+
async remove(key) {
|
|
59
|
+
await db.delete(settings).where(eq(settings.key, key));
|
|
60
|
+
},
|
|
61
|
+
|
|
57
62
|
async setMany(entries) {
|
|
58
63
|
const timestamp = now();
|
|
59
64
|
const keys = Object.keys(entries) as SettingsKey[];
|
|
@@ -12,6 +12,32 @@
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/* Alert variants */
|
|
16
|
+
@layer components {
|
|
17
|
+
.alert-success {
|
|
18
|
+
@apply relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current;
|
|
19
|
+
@apply text-success bg-card [&>svg]:text-current;
|
|
20
|
+
|
|
21
|
+
> h2,
|
|
22
|
+
> h3,
|
|
23
|
+
> h4,
|
|
24
|
+
> h5,
|
|
25
|
+
> h6,
|
|
26
|
+
> strong,
|
|
27
|
+
> [data-title] {
|
|
28
|
+
@apply col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
> section {
|
|
32
|
+
@apply text-success col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed;
|
|
33
|
+
|
|
34
|
+
ul {
|
|
35
|
+
@apply list-inside list-disc text-sm;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
15
41
|
/* Badge components */
|
|
16
42
|
@layer components {
|
|
17
43
|
.badge {
|
|
@@ -45,3 +71,70 @@
|
|
|
45
71
|
color: white;
|
|
46
72
|
}
|
|
47
73
|
}
|
|
74
|
+
|
|
75
|
+
/* Toast notifications */
|
|
76
|
+
@layer components {
|
|
77
|
+
.toast-container {
|
|
78
|
+
@apply fixed top-4 right-4 z-50 flex flex-col gap-2 pointer-events-none;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.toast {
|
|
82
|
+
@apply pointer-events-auto rounded-lg border px-4 py-3 text-sm shadow-lg;
|
|
83
|
+
@apply flex items-start gap-2.5 min-w-64 max-w-sm;
|
|
84
|
+
background-color: var(--color-card);
|
|
85
|
+
animation: toast-in 0.3s ease-out;
|
|
86
|
+
|
|
87
|
+
> svg:first-child {
|
|
88
|
+
@apply size-4 shrink-0 translate-y-0.5;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
> span {
|
|
92
|
+
@apply flex-1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.toast-success {
|
|
97
|
+
color: var(--color-success);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.toast-error {
|
|
101
|
+
color: var(--color-destructive);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.toast-close {
|
|
105
|
+
@apply shrink-0 translate-y-0.5 cursor-pointer rounded-sm p-0 border-0 bg-transparent;
|
|
106
|
+
color: var(--color-muted-foreground);
|
|
107
|
+
|
|
108
|
+
&:hover {
|
|
109
|
+
color: var(--color-foreground);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
> svg {
|
|
113
|
+
@apply size-3.5;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.toast-out {
|
|
118
|
+
animation: toast-out 0.3s ease-in forwards;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@keyframes toast-in {
|
|
123
|
+
from {
|
|
124
|
+
opacity: 0;
|
|
125
|
+
transform: translateY(-0.5rem);
|
|
126
|
+
}
|
|
127
|
+
to {
|
|
128
|
+
opacity: 1;
|
|
129
|
+
transform: translateY(0);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@keyframes toast-out {
|
|
134
|
+
from {
|
|
135
|
+
opacity: 1;
|
|
136
|
+
}
|
|
137
|
+
to {
|
|
138
|
+
opacity: 0;
|
|
139
|
+
}
|
|
140
|
+
}
|