@hexdspace/react 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
2
  import { QueryClient, QueryKey } from '@tanstack/react-query';
3
3
  import { ResultOk, ResultError } from '@hexdspace/util';
4
- import React from 'react';
4
+ import React, { CSSProperties } from 'react';
5
5
 
6
6
  type NotificationVariant = 'success' | 'warning' | 'error' | 'info';
7
7
  declare const DEFAULT_NOTIFICATION_CHANNEL = "app.notifications";
@@ -39,17 +39,87 @@ declare class NotifierController {
39
39
  }
40
40
  declare const notifierController: NotifierController;
41
41
 
42
+ type ToastTransition = {
43
+ enter: string;
44
+ exit: string;
45
+ collapseDuration?: number;
46
+ };
47
+ type ToastTheme = {
48
+ lightBg?: string;
49
+ lightText?: string;
50
+ lightProgress?: string;
51
+ darkBg?: string;
52
+ darkText?: string;
53
+ darkProgress?: string;
54
+ progressInfo?: string;
55
+ progressSuccess?: string;
56
+ progressWarning?: string;
57
+ progressError?: string;
58
+ width?: string;
59
+ contentPadding?: string;
60
+ bodyColumnGap?: string;
61
+ bodyRowGap?: string;
62
+ transition?: ToastTransition;
63
+ action?: Partial<ToastActionTheme>;
64
+ iconColors?: Partial<Record<NotificationVariant, string>>;
65
+ };
66
+ type ToastifyCSSVars = CSSProperties & {
67
+ '--toastify-color-light'?: string;
68
+ '--toastify-text-color-light'?: string;
69
+ '--toastify-color-progress-light'?: string;
70
+ '--toastify-color-dark'?: string;
71
+ '--toastify-text-color-dark'?: string;
72
+ '--toastify-color-progress-dark'?: string;
73
+ '--toastify-color-progress-info'?: string;
74
+ '--toastify-color-progress-success'?: string;
75
+ '--toastify-color-progress-warning'?: string;
76
+ '--toastify-color-progress-error'?: string;
77
+ '--toastify-toast-width'?: string;
78
+ };
79
+ type ToastActionTheme = {
80
+ padding: string;
81
+ borderRadius: string;
82
+ background: string;
83
+ hoverBackground: string;
84
+ focusOutline: string;
85
+ focusOutlineOffset: string;
86
+ fontWeight: number | string;
87
+ transition: string;
88
+ focusTransform: string;
89
+ activeTransform: string;
90
+ color?: string;
91
+ hoverColor?: string;
92
+ marginLeft?: string;
93
+ marginTop?: string;
94
+ };
95
+ type ResolvedToastTheme = {
96
+ lightBg: string;
97
+ lightText: string;
98
+ lightProgress: string;
99
+ darkBg: string;
100
+ darkText: string;
101
+ darkProgress: string;
102
+ progressInfo: string;
103
+ progressSuccess: string;
104
+ progressWarning: string;
105
+ progressError: string;
106
+ width: string;
107
+ contentPadding: string;
108
+ bodyColumnGap: string;
109
+ bodyRowGap: string;
110
+ transition?: ToastTransition;
111
+ action: ToastActionTheme;
112
+ iconColors: Record<NotificationVariant, string>;
113
+ };
114
+ declare function resolveToastTheme(theme?: ToastTheme): ResolvedToastTheme;
115
+
42
116
  type NotificationHostProps = {
43
117
  channel?: string;
44
118
  isDark?: () => boolean;
119
+ theme?: ToastTheme;
45
120
  };
46
121
  declare const NotificationHost: React.FC<NotificationHostProps>;
47
122
 
48
- declare const NotifierProvider: React.FC<{
49
- children: React.ReactNode;
50
- }>;
51
- declare function useNotifierController(): NotifierController;
52
-
53
123
  type InstructionContext = {
54
124
  queryClient: QueryClient;
55
125
  };
@@ -93,4 +163,4 @@ type ResponsiveMutation<Args, Res> = {
93
163
 
94
164
  declare function useResponsiveMutation<Args, Res>(responsiveMutation: ResponsiveMutation<Args, Res>, queryClient: QueryClient): _tanstack_react_query.UseMutationResult<UIOk<Res>, UIFail, Args, OptimisticSnapshot | undefined>;
95
165
 
96
- export { type CacheInstruction, type CustomInstruction, DEFAULT_NOTIFICATION_CHANNEL, type Instruction, type InstructionContext, type Notification, type NotificationAction, NotificationHost, type NotificationInstruction, type NotificationVariant, NotifierController, NotifierProvider, type OptimisticSnapshot, type ResponsiveMutation, type UIFail, type UIOk, type UIResult, notifierController, ui, useNotifierController, useResponsiveMutation };
166
+ export { type CacheInstruction, type CustomInstruction, DEFAULT_NOTIFICATION_CHANNEL, type Instruction, type InstructionContext, type Notification, type NotificationAction, NotificationHost, type NotificationInstruction, type NotificationVariant, NotifierController, type OptimisticSnapshot, type ResolvedToastTheme, type ResponsiveMutation, type ToastActionTheme, type ToastTheme, type ToastTransition, type ToastifyCSSVars, type UIFail, type UIOk, type UIResult, notifierController, resolveToastTheme, ui, useResponsiveMutation };
package/dist/index.js CHANGED
@@ -102,50 +102,272 @@ var subscribe = new SubscribeUseCase(bus);
102
102
  var notifierController = new NotifierController(sendNotification, subscribe);
103
103
 
104
104
  // src/feature/notifier/infra/web/react/NotificationHost.tsx
105
- import { useEffect, useMemo as useMemo2 } from "react";
106
- import { ToastContainer, toast } from "react-toastify";
107
- import { AlertTriangleIcon, CheckCircleIcon, InfoIcon, XCircleIcon } from "lucide-react";
105
+ import { useCallback, useEffect, useMemo as useMemo2 } from "react";
106
+ import { toast as toast2, ToastContainer } from "react-toastify";
108
107
 
109
108
  // src/feature/notifier/entity/notification.ts
110
109
  var DEFAULT_NOTIFICATION_CHANNEL = "app.notifications";
111
110
 
112
- // src/feature/notifier/infra/web/react/NotifierProvider.tsx
113
- import { createContext, useContext, useMemo } from "react";
114
- import { jsx } from "react/jsx-runtime";
115
- var NotifierContext = createContext(null);
116
- var NotifierProvider = ({ children }) => {
117
- const controller = useMemo(() => notifierController, []);
118
- return /* @__PURE__ */ jsx(NotifierContext.Provider, { value: { controller }, children });
119
- };
120
- function useNotifierController() {
121
- const ctx = useContext(NotifierContext);
122
- if (!ctx) throw new Error("useNotifierController must be used within <NotifierProvider>");
123
- return ctx.controller;
124
- }
125
-
126
111
  // src/feature/notifier/infra/web/react/CustomToastTransition.tsx
127
112
  import { cssTransition } from "react-toastify";
128
113
  var SlideUp = cssTransition({
129
114
  enter: "slideIn",
130
115
  exit: "slideOut",
131
- collapseDuration: 300,
132
- appendPosition: false,
133
- collapse: true
116
+ collapseDuration: 300
134
117
  });
118
+ var buildToastTransition = (transition) => {
119
+ if (!transition) return SlideUp;
120
+ return cssTransition(transition);
121
+ };
122
+
123
+ // src/feature/notifier/infra/web/react/ToastContent.tsx
124
+ import { useMemo, useState } from "react";
125
+ import { toast } from "react-toastify";
126
+ import { AlertTriangleIcon, CheckCircleIcon, InfoIcon, XCircleIcon } from "lucide-react";
127
+ import { jsx, jsxs } from "react/jsx-runtime";
128
+ var DEFAULT_NOTIFICATION_VARIANT = "info";
129
+ var TOAST_ICON_BY_VARIANT = {
130
+ success: CheckCircleIcon,
131
+ warning: AlertTriangleIcon,
132
+ error: XCircleIcon,
133
+ info: InfoIcon
134
+ };
135
+ var ToastContent = ({ notification, theme, mode }) => {
136
+ const { title, content, action } = notification;
137
+ const variant = notification.variant ?? DEFAULT_NOTIFICATION_VARIANT;
138
+ const Icon = TOAST_ICON_BY_VARIANT[variant];
139
+ const iconColor = theme.iconColors[variant];
140
+ const [isHovered, setIsHovered] = useState(false);
141
+ const [isFocused, setIsFocused] = useState(false);
142
+ const [isActive, setIsActive] = useState(false);
143
+ const textColor = mode === "dark" ? theme.darkText : theme.lightText;
144
+ const actionTextColor = theme.action.color ?? textColor;
145
+ const actionHoverColor = theme.action.hoverColor ?? actionTextColor;
146
+ const actionStyle = useMemo(() => {
147
+ const background = isHovered ? theme.action.hoverBackground : theme.action.background;
148
+ const color = isHovered ? actionHoverColor : actionTextColor;
149
+ const transform = isActive ? theme.action.activeTransform : isFocused ? theme.action.focusTransform : void 0;
150
+ return {
151
+ alignSelf: "flex-start",
152
+ padding: theme.action.padding,
153
+ borderRadius: theme.action.borderRadius,
154
+ background,
155
+ color,
156
+ font: "inherit",
157
+ fontWeight: theme.action.fontWeight,
158
+ cursor: "pointer",
159
+ transition: theme.action.transition,
160
+ outline: isFocused ? theme.action.focusOutline : "none",
161
+ outlineOffset: isFocused ? theme.action.focusOutlineOffset : void 0,
162
+ transform,
163
+ marginLeft: theme.action.marginLeft
164
+ };
165
+ }, [actionHoverColor, actionTextColor, isActive, isFocused, isHovered, theme.action]);
166
+ const handleActionClick = async () => {
167
+ if (!action) return;
168
+ try {
169
+ await action.onClick?.();
170
+ } catch (error) {
171
+ console.error("Notification action failed", error);
172
+ } finally {
173
+ toast.dismiss(notification.id);
174
+ }
175
+ };
176
+ return /* @__PURE__ */ jsxs(
177
+ "div",
178
+ {
179
+ style: {
180
+ padding: theme.contentPadding,
181
+ display: "flex",
182
+ flexDirection: "column",
183
+ gap: "0.5rem",
184
+ width: "100%",
185
+ textAlign: "left"
186
+ },
187
+ children: [
188
+ /* @__PURE__ */ jsxs(
189
+ "div",
190
+ {
191
+ style: {
192
+ display: "flex",
193
+ alignItems: "center",
194
+ gap: "0.5rem"
195
+ },
196
+ children: [
197
+ /* @__PURE__ */ jsx(
198
+ Icon,
199
+ {
200
+ style: { color: iconColor, padding: "0.125rem", width: "1.25rem", height: "1.25rem", flexShrink: 0 },
201
+ "aria-hidden": "true"
202
+ }
203
+ ),
204
+ /* @__PURE__ */ jsx("span", { className: "font-semibold leading-tight", children: title })
205
+ ]
206
+ }
207
+ ),
208
+ (content || action) && /* @__PURE__ */ jsxs(
209
+ "div",
210
+ {
211
+ style: {
212
+ display: "grid",
213
+ gridTemplateColumns: action ? "1fr auto" : "1fr",
214
+ columnGap: theme.bodyColumnGap,
215
+ rowGap: theme.bodyRowGap,
216
+ alignItems: "center",
217
+ width: "100%"
218
+ },
219
+ children: [
220
+ content && /* @__PURE__ */ jsx(
221
+ "span",
222
+ {
223
+ style: {
224
+ minWidth: 0,
225
+ textAlign: "left",
226
+ fontSize: "0.875rem",
227
+ color: textColor,
228
+ opacity: 0.9
229
+ },
230
+ children: content
231
+ }
232
+ ),
233
+ action && /* @__PURE__ */ jsx(
234
+ "button",
235
+ {
236
+ type: "button",
237
+ style: {
238
+ ...actionStyle,
239
+ marginTop: theme.action.marginTop,
240
+ fontSize: "0.875rem"
241
+ },
242
+ onMouseEnter: () => setIsHovered(true),
243
+ onMouseLeave: () => {
244
+ setIsHovered(false);
245
+ setIsActive(false);
246
+ },
247
+ onFocus: () => setIsFocused(true),
248
+ onBlur: () => {
249
+ setIsFocused(false);
250
+ setIsActive(false);
251
+ },
252
+ onMouseDown: () => setIsActive(true),
253
+ onMouseUp: () => setIsActive(false),
254
+ onClick: handleActionClick,
255
+ children: action.label
256
+ }
257
+ )
258
+ ]
259
+ }
260
+ )
261
+ ]
262
+ }
263
+ );
264
+ };
265
+
266
+ // src/feature/notifier/entity/toast-theme.ts
267
+ var DEFAULT_ACTION_THEME = {
268
+ padding: "0.35rem 0.75rem",
269
+ borderRadius: "0.5rem",
270
+ background: "rgba(79, 70, 229, 0.12)",
271
+ hoverBackground: "rgba(79, 70, 229, 0.2)",
272
+ focusOutline: "2px solid #4338ca",
273
+ focusOutlineOffset: "2px",
274
+ fontWeight: 600,
275
+ transition: "background-color 0.2s ease, color 0.2s ease, transform 0.2s ease",
276
+ focusTransform: "translateY(-1px)",
277
+ activeTransform: "translateY(0)",
278
+ marginLeft: "auto",
279
+ marginTop: "0.15rem"
280
+ };
281
+ var DEFAULT_ICON_COLORS = {
282
+ info: "#2563eb",
283
+ success: "#16a34a",
284
+ warning: "#f59e0b",
285
+ error: "#dc2626"
286
+ };
287
+ var DEFAULT_THEME_BASE = {
288
+ lightBg: "#f8fafc",
289
+ lightText: "#0f172a",
290
+ lightProgress: "#4f46e5",
291
+ darkBg: "#111827",
292
+ darkText: "#e5e7eb",
293
+ darkProgress: "#a5b4fc",
294
+ progressInfo: "#60a5fa",
295
+ progressSuccess: "#34d399",
296
+ progressWarning: "#fbbf24",
297
+ progressError: "#f87171",
298
+ width: "min(24rem, calc(100vw - 2rem))",
299
+ contentPadding: "0.5rem 0.75rem 1rem 0.5rem",
300
+ bodyColumnGap: "0.75rem",
301
+ bodyRowGap: "0.25rem",
302
+ transition: {
303
+ enter: "slideIn",
304
+ exit: "slideOut",
305
+ collapseDuration: 300
306
+ }
307
+ };
308
+ function resolveToastTheme(theme) {
309
+ return {
310
+ lightBg: theme?.lightBg ?? DEFAULT_THEME_BASE.lightBg,
311
+ lightText: theme?.lightText ?? DEFAULT_THEME_BASE.lightText,
312
+ lightProgress: theme?.lightProgress ?? DEFAULT_THEME_BASE.lightProgress,
313
+ darkBg: theme?.darkBg ?? DEFAULT_THEME_BASE.darkBg,
314
+ darkText: theme?.darkText ?? DEFAULT_THEME_BASE.darkText,
315
+ darkProgress: theme?.darkProgress ?? DEFAULT_THEME_BASE.darkProgress,
316
+ progressInfo: theme?.progressInfo ?? DEFAULT_THEME_BASE.progressInfo,
317
+ progressSuccess: theme?.progressSuccess ?? DEFAULT_THEME_BASE.progressSuccess,
318
+ progressWarning: theme?.progressWarning ?? DEFAULT_THEME_BASE.progressWarning,
319
+ progressError: theme?.progressError ?? DEFAULT_THEME_BASE.progressError,
320
+ width: theme?.width ?? DEFAULT_THEME_BASE.width,
321
+ contentPadding: theme?.contentPadding ?? DEFAULT_THEME_BASE.contentPadding,
322
+ bodyColumnGap: theme?.bodyColumnGap ?? DEFAULT_THEME_BASE.bodyColumnGap,
323
+ bodyRowGap: theme?.bodyRowGap ?? DEFAULT_THEME_BASE.bodyRowGap,
324
+ transition: theme?.transition ?? DEFAULT_THEME_BASE.transition,
325
+ action: {
326
+ ...DEFAULT_ACTION_THEME,
327
+ ...theme?.action ?? {}
328
+ },
329
+ iconColors: {
330
+ ...DEFAULT_ICON_COLORS,
331
+ ...theme?.iconColors ?? {}
332
+ }
333
+ };
334
+ }
135
335
 
136
336
  // src/feature/notifier/infra/web/react/NotificationHost.tsx
137
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
138
- var NotificationHost = ({ channel = DEFAULT_NOTIFICATION_CHANNEL, isDark }) => {
139
- const controller = useNotifierController();
337
+ import { jsx as jsx2 } from "react/jsx-runtime";
338
+ var NotificationHost = ({ channel = DEFAULT_NOTIFICATION_CHANNEL, isDark, theme }) => {
339
+ const resolvedTheme = useMemo2(() => resolveToastTheme(theme), [theme]);
340
+ const mode = useMemo2(() => {
341
+ if (!isDark) {
342
+ return "light";
343
+ }
344
+ return isDark() ? "dark" : "light";
345
+ }, [isDark]);
346
+ const style = useMemo2(() => ({
347
+ "--toastify-color-light": resolvedTheme.lightBg,
348
+ "--toastify-text-color-light": resolvedTheme.lightText,
349
+ "--toastify-color-progress-light": resolvedTheme.lightProgress,
350
+ "--toastify-color-dark": resolvedTheme.darkBg,
351
+ "--toastify-text-color-dark": resolvedTheme.darkText,
352
+ "--toastify-color-progress-dark": resolvedTheme.darkProgress,
353
+ "--toastify-color-progress-info": resolvedTheme.progressInfo,
354
+ "--toastify-color-progress-success": resolvedTheme.progressSuccess,
355
+ "--toastify-color-progress-warning": resolvedTheme.progressWarning,
356
+ "--toastify-color-progress-error": resolvedTheme.progressError,
357
+ "--toastify-toast-width": resolvedTheme.width
358
+ }), [resolvedTheme]);
359
+ const renderToast = useCallback((notification) => {
360
+ toast2(/* @__PURE__ */ jsx2(ToastContent, { notification, theme: resolvedTheme, mode }), {
361
+ toastId: notification.id,
362
+ icon: false
363
+ });
364
+ }, [mode, resolvedTheme]);
365
+ const transition = useMemo2(() => buildToastTransition(resolvedTheme.transition), [resolvedTheme.transition]);
140
366
  useEffect(() => {
141
367
  let unsub;
142
368
  let disposed = false;
143
- const listener = {
144
- notify(notification) {
145
- renderToast(notification);
146
- }
147
- };
148
- controller.handleSubscribe(channel, listener).then((unsubHandler) => {
369
+ const listener = { notify: renderToast };
370
+ notifierController.handleSubscribe(channel, listener).then((unsubHandler) => {
149
371
  if (disposed) {
150
372
  unsubHandler();
151
373
  return;
@@ -158,84 +380,23 @@ var NotificationHost = ({ channel = DEFAULT_NOTIFICATION_CHANNEL, isDark }) => {
158
380
  disposed = true;
159
381
  if (unsub) unsub();
160
382
  };
161
- }, [channel, controller]);
162
- const theme = useMemo2(() => {
163
- if (!isDark) {
164
- return "light";
165
- }
166
- return isDark() ? "dark" : "light";
167
- }, []);
383
+ }, [channel, renderToast]);
168
384
  return /* @__PURE__ */ jsx2(
169
385
  ToastContainer,
170
386
  {
387
+ style,
171
388
  position: "bottom-right",
172
- transition: SlideUp,
389
+ transition,
173
390
  newestOnTop: true,
174
391
  draggable: true,
175
392
  pauseOnFocusLoss: false,
176
393
  icon: false,
177
394
  pauseOnHover: true,
178
395
  hideProgressBar: false,
179
- className: "custom-toast-container",
180
- theme
396
+ theme: mode
181
397
  }
182
398
  );
183
399
  };
184
- var DEFAULT_NOTIFICATION_VARIANT = "info";
185
- var TOAST_ICON_BY_VARIANT = {
186
- success: { Icon: CheckCircleIcon, color: "var(--success)" },
187
- warning: { Icon: AlertTriangleIcon, color: "var(--warning)" },
188
- error: { Icon: XCircleIcon, color: "var(--danger)" },
189
- info: { Icon: InfoIcon, color: "var(--accent)" }
190
- };
191
- function renderToast(notification) {
192
- const variant = notification.variant ?? DEFAULT_NOTIFICATION_VARIANT;
193
- toast(/* @__PURE__ */ jsx2(ToastContent, { notification }), {
194
- type: variant,
195
- toastId: notification.id,
196
- icon: false
197
- });
198
- }
199
- var ToastContent = ({ notification }) => {
200
- const { title, content, action } = notification;
201
- const variant = notification.variant ?? DEFAULT_NOTIFICATION_VARIANT;
202
- const { Icon, color } = TOAST_ICON_BY_VARIANT[variant];
203
- const handleActionClick = async () => {
204
- if (!action) return;
205
- try {
206
- await action.onClick?.();
207
- } catch (error) {
208
- console.error("Notification action failed", error);
209
- } finally {
210
- toast.dismiss(notification.id);
211
- }
212
- };
213
- return /* @__PURE__ */ jsxs("div", { className: "toast-content flex w-full flex-col gap-2", children: [
214
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
215
- /* @__PURE__ */ jsx2(
216
- Icon,
217
- {
218
- className: "h-4 w-4 shrink-0",
219
- style: { color },
220
- "aria-hidden": "true"
221
- }
222
- ),
223
- /* @__PURE__ */ jsx2("span", { className: "font-semibold", children: title })
224
- ] }),
225
- (content || action) && /* @__PURE__ */ jsxs("div", { className: "flex w-full items-start gap-3", children: [
226
- content && /* @__PURE__ */ jsx2("span", { className: "flex-1 text-sm text-muted-foreground", children: content }),
227
- action && /* @__PURE__ */ jsx2(
228
- "button",
229
- {
230
- type: "button",
231
- className: "toast-action text-sm font-medium ml-auto shrink-0 self-end",
232
- onClick: handleActionClick,
233
- children: action.label
234
- }
235
- )
236
- ] })
237
- ] });
238
- };
239
400
 
240
401
  // src/util/responsive-query/use-responsive-mutation.ts
241
402
  function useResponsiveMutation(responsiveMutation, queryClient) {
@@ -281,9 +442,8 @@ export {
281
442
  DEFAULT_NOTIFICATION_CHANNEL,
282
443
  NotificationHost,
283
444
  NotifierController,
284
- NotifierProvider,
285
445
  notifierController,
446
+ resolveToastTheme,
286
447
  ui,
287
- useNotifierController,
288
448
  useResponsiveMutation
289
449
  };
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@hexdspace/react",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
8
- ".": "./dist/index.js"
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
9
12
  },
10
13
  "files": [
11
14
  "dist"
package/dist/index.css DELETED
@@ -1,79 +0,0 @@
1
- /* src/feature/notifier/infra/web/react/notification-host.css */
2
- @layer base {
3
- .custom-toast-container {
4
- --toastify-color-light: var(--surface-3);
5
- --toastify-text-color-light: var(--text-1);
6
- --toastify-color-progress-light: color-mix(in oklab, var(--accent-high), transparent 30%);
7
- --toastify-color-dark: var(--surface-1);
8
- --toastify-text-color-dark: var(--text-1);
9
- --toastify-color-progress-dark: color-mix(in oklab, var(--accent-high), transparent 30%);
10
- --toastify-color-progress-info: color-mix(in oklab, var(--accent), transparent 30%);
11
- --toastify-color-progress-success: color-mix(in oklab, var(--success), transparent 30%);
12
- --toastify-color-progress-warning: color-mix(in oklab, var(--warning), transparent 30%);
13
- --toastify-color-progress-error: color-mix(in oklab, var(--danger), transparent 30%);
14
- --toastify-toast-width: min(24rem, calc(100vw - 2rem));
15
- }
16
- }
17
- @supports (color-mix(in oklab, white, transparent)) {
18
- @layer base {
19
- .custom-toast-container {
20
- }
21
- }
22
- }
23
- @layer components {
24
- .toast-content {
25
- padding: 0.5rem 0.75rem 1rem 0.5rem;
26
- }
27
- .toast-action {
28
- align-self: flex-start;
29
- padding: 0.35rem 0.75rem;
30
- border-radius: var(--shape-radius-btn);
31
- background: color-mix(in oklab, var(--secondary-3), transparent 30%);
32
- color: var(--color-contrast-inverted);
33
- font: inherit;
34
- font-weight: 600;
35
- cursor: pointer;
36
- transition:
37
- background-color 0.2s ease,
38
- color 0.2s ease,
39
- transform 0.2s ease;
40
- }
41
- .toast-action:hover {
42
- background: var(--secondary-3);
43
- color: var(--contrast-inverted);
44
- }
45
- .toast-action:focus-visible {
46
- outline: 2px solid var(--focus);
47
- outline-offset: 2px;
48
- transform: translateY(-1px);
49
- }
50
- .toast-action:active {
51
- transform: translateY(0);
52
- }
53
- }
54
- @keyframes slideIn {
55
- from {
56
- transform: translateY(-20px);
57
- opacity: 0;
58
- }
59
- to {
60
- transform: translateY(0);
61
- opacity: 1;
62
- }
63
- }
64
- @keyframes slideOut {
65
- from {
66
- transform: translateY(0);
67
- opacity: 1;
68
- }
69
- to {
70
- transform: translateY(-20px);
71
- opacity: 0;
72
- }
73
- }
74
- .slideIn {
75
- animation: slideIn 0.35s forwards;
76
- }
77
- .slideOut {
78
- animation: slideOut 0.25s forwards;
79
- }