@tooee/toasts 0.1.0
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/ToastContainer.d.ts +2 -0
- package/dist/ToastContainer.d.ts.map +1 -0
- package/dist/ToastContainer.js +34 -0
- package/dist/ToastContainer.js.map +1 -0
- package/dist/ToastProvider.d.ts +7 -0
- package/dist/ToastProvider.d.ts.map +1 -0
- package/dist/ToastProvider.js +59 -0
- package/dist/ToastProvider.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +43 -0
- package/src/ToastContainer.tsx +55 -0
- package/src/ToastProvider.tsx +77 -0
- package/src/index.ts +3 -0
- package/src/types.ts +21 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToastContainer.d.ts","sourceRoot":"","sources":["../src/ToastContainer.tsx"],"names":[],"mappings":"AAyBA,wBAAgB,cAAc,8BA6B7B"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@opentui/react/jsx-runtime";
|
|
2
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
3
|
+
import { useTheme } from "@tooee/themes";
|
|
4
|
+
import { useToast } from "./ToastProvider.js";
|
|
5
|
+
const LEVEL_ICONS = {
|
|
6
|
+
info: "ℹ",
|
|
7
|
+
success: "✓",
|
|
8
|
+
warning: "⚠",
|
|
9
|
+
error: "✗",
|
|
10
|
+
};
|
|
11
|
+
function getLevelColor(theme, level) {
|
|
12
|
+
switch (level) {
|
|
13
|
+
case "info":
|
|
14
|
+
return theme.info;
|
|
15
|
+
case "success":
|
|
16
|
+
return theme.success;
|
|
17
|
+
case "warning":
|
|
18
|
+
return theme.warning;
|
|
19
|
+
case "error":
|
|
20
|
+
return theme.error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function ToastContainer() {
|
|
24
|
+
const { currentToast } = useToast();
|
|
25
|
+
const { theme } = useTheme();
|
|
26
|
+
const { width: termWidth } = useTerminalDimensions();
|
|
27
|
+
if (!currentToast)
|
|
28
|
+
return null;
|
|
29
|
+
const maxWidth = Math.min(50, termWidth - 4);
|
|
30
|
+
const borderColor = getLevelColor(theme, currentToast.level);
|
|
31
|
+
const icon = LEVEL_ICONS[currentToast.level];
|
|
32
|
+
return (_jsx("box", { position: "absolute", bottom: 1, right: 1, maxWidth: maxWidth, border: true, borderColor: borderColor, backgroundColor: theme.backgroundPanel, paddingLeft: 1, paddingRight: 1, children: _jsx("text", { content: `${icon} ${currentToast.message}`, fg: theme.text }) }));
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=ToastContainer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToastContainer.js","sourceRoot":"","sources":["../src/ToastContainer.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAG7C,MAAM,WAAW,GAA+B;IAC9C,IAAI,EAAE,GAAG;IACT,OAAO,EAAE,GAAG;IACZ,OAAO,EAAE,GAAG;IACZ,KAAK,EAAE,GAAG;CACX,CAAA;AAED,SAAS,aAAa,CAAC,KAA2C,EAAE,KAAiB;IACnF,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,MAAM;YACT,OAAO,KAAK,CAAC,IAAI,CAAA;QACnB,KAAK,SAAS;YACZ,OAAO,KAAK,CAAC,OAAO,CAAA;QACtB,KAAK,SAAS;YACZ,OAAO,KAAK,CAAC,OAAO,CAAA;QACtB,KAAK,OAAO;YACV,OAAO,KAAK,CAAC,KAAK,CAAA;IACtB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,MAAM,EAAE,YAAY,EAAE,GAAG,QAAQ,EAAE,CAAA;IACnC,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAA;IAC5B,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,qBAAqB,EAAE,CAAA;IAEpD,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAA;IAE9B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,GAAG,CAAC,CAAC,CAAA;IAC5C,MAAM,WAAW,GAAG,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,CAAA;IAC5D,MAAM,IAAI,GAAG,WAAW,CAAC,YAAY,CAAC,KAAK,CAAC,CAAA;IAE5C,OAAO,CACL,cACE,QAAQ,EAAC,UAAU,EACnB,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,QAAQ,EAClB,MAAM,QACN,WAAW,EAAE,WAAW,EACxB,eAAe,EAAE,KAAK,CAAC,eAAe,EACtC,WAAW,EAAE,CAAC,EACd,YAAY,EAAE,CAAC,YAEf,eACE,OAAO,EAAE,GAAG,IAAI,IAAI,YAAY,CAAC,OAAO,EAAE,EAC1C,EAAE,EAAE,KAAK,CAAC,IAAI,GACd,GACE,CACP,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ToastController } from "./types.js";
|
|
3
|
+
export declare function ToastProvider({ children }: {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
}): ReactNode;
|
|
6
|
+
export declare function useToast(): ToastController;
|
|
7
|
+
//# sourceMappingURL=ToastProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToastProvider.d.ts","sourceRoot":"","sources":["../src/ToastProvider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAA4B,eAAe,EAAE,MAAM,YAAY,CAAA;AAa3E,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,aAqDlE;AAED,wBAAgB,QAAQ,IAAI,eAAe,CAM1C"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@opentui/react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useState, useCallback, useRef, useEffect } from "react";
|
|
3
|
+
const DEFAULT_DURATIONS = {
|
|
4
|
+
info: 2000,
|
|
5
|
+
success: 1500,
|
|
6
|
+
warning: 3000,
|
|
7
|
+
error: 5000,
|
|
8
|
+
};
|
|
9
|
+
let nextToastId = 0;
|
|
10
|
+
const ToastContext = createContext(null);
|
|
11
|
+
export function ToastProvider({ children }) {
|
|
12
|
+
const [currentToast, setCurrentToast] = useState(null);
|
|
13
|
+
const timerRef = useRef(null);
|
|
14
|
+
const clearTimer = useCallback(() => {
|
|
15
|
+
if (timerRef.current !== null) {
|
|
16
|
+
clearTimeout(timerRef.current);
|
|
17
|
+
timerRef.current = null;
|
|
18
|
+
}
|
|
19
|
+
}, []);
|
|
20
|
+
const dismiss = useCallback(() => {
|
|
21
|
+
clearTimer();
|
|
22
|
+
setCurrentToast(null);
|
|
23
|
+
}, [clearTimer]);
|
|
24
|
+
const toast = useCallback((options) => {
|
|
25
|
+
clearTimer();
|
|
26
|
+
const level = options.level ?? "info";
|
|
27
|
+
const duration = options.duration ?? DEFAULT_DURATIONS[level] ?? 2000;
|
|
28
|
+
const id = options.id ?? `toast-${nextToastId++}`;
|
|
29
|
+
const entry = {
|
|
30
|
+
id,
|
|
31
|
+
message: options.message,
|
|
32
|
+
level,
|
|
33
|
+
duration,
|
|
34
|
+
};
|
|
35
|
+
setCurrentToast(entry);
|
|
36
|
+
timerRef.current = setTimeout(() => {
|
|
37
|
+
setCurrentToast((current) => (current?.id === id ? null : current));
|
|
38
|
+
timerRef.current = null;
|
|
39
|
+
}, duration);
|
|
40
|
+
}, [clearTimer]);
|
|
41
|
+
// Cleanup on unmount
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
return () => clearTimer();
|
|
44
|
+
}, [clearTimer]);
|
|
45
|
+
const controller = {
|
|
46
|
+
toast,
|
|
47
|
+
dismiss,
|
|
48
|
+
currentToast,
|
|
49
|
+
};
|
|
50
|
+
return _jsx(ToastContext.Provider, { value: controller, children: children });
|
|
51
|
+
}
|
|
52
|
+
export function useToast() {
|
|
53
|
+
const ctx = useContext(ToastContext);
|
|
54
|
+
if (!ctx) {
|
|
55
|
+
throw new Error("useToast must be used within a ToastProvider");
|
|
56
|
+
}
|
|
57
|
+
return ctx;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=ToastProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToastProvider.js","sourceRoot":"","sources":["../src/ToastProvider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAI3F,MAAM,iBAAiB,GAA2B;IAChD,IAAI,EAAE,IAAI;IACV,OAAO,EAAE,IAAI;IACb,OAAO,EAAE,IAAI;IACb,KAAK,EAAE,IAAI;CACZ,CAAA;AAED,IAAI,WAAW,GAAG,CAAC,CAAA;AAEnB,MAAM,YAAY,GAAG,aAAa,CAAyB,IAAI,CAAC,CAAA;AAEhE,MAAM,UAAU,aAAa,CAAC,EAAE,QAAQ,EAA2B;IACjE,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAoB,IAAI,CAAC,CAAA;IACzE,MAAM,QAAQ,GAAG,MAAM,CAAuC,IAAI,CAAC,CAAA;IAEnE,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE;QAClC,IAAI,QAAQ,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC9B,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;YAC9B,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAA;QACzB,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/B,UAAU,EAAE,CAAA;QACZ,eAAe,CAAC,IAAI,CAAC,CAAA;IACvB,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAEhB,MAAM,KAAK,GAAG,WAAW,CACvB,CAAC,OAAqB,EAAE,EAAE;QACxB,UAAU,EAAE,CAAA;QAEZ,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAAA;QACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAC,KAAK,CAAC,IAAI,IAAI,CAAA;QACrE,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,IAAI,SAAS,WAAW,EAAE,EAAE,CAAA;QAEjD,MAAM,KAAK,GAAe;YACxB,EAAE;YACF,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,KAAK;YACL,QAAQ;SACT,CAAA;QAED,eAAe,CAAC,KAAK,CAAC,CAAA;QAEtB,QAAQ,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YACjC,eAAe,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;YACnE,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAA;QACzB,CAAC,EAAE,QAAQ,CAAC,CAAA;IACd,CAAC,EACD,CAAC,UAAU,CAAC,CACb,CAAA;IAED,qBAAqB;IACrB,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE,CAAC,UAAU,EAAE,CAAA;IAC3B,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAEhB,MAAM,UAAU,GAAoB;QAClC,KAAK;QACL,OAAO;QACP,YAAY;KACb,CAAA;IAED,OAAO,KAAC,YAAY,CAAC,QAAQ,IAAC,KAAK,EAAE,UAAU,YAAG,QAAQ,GAAyB,CAAA;AACrF,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,MAAM,GAAG,GAAG,UAAU,CAAC,YAAY,CAAC,CAAA;IACpC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAA;IACjE,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type ToastLevel = "info" | "success" | "warning" | "error";
|
|
2
|
+
export interface ToastOptions {
|
|
3
|
+
message: string;
|
|
4
|
+
level?: ToastLevel;
|
|
5
|
+
duration?: number;
|
|
6
|
+
id?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ToastEntry {
|
|
9
|
+
id: string;
|
|
10
|
+
message: string;
|
|
11
|
+
level: ToastLevel;
|
|
12
|
+
duration: number;
|
|
13
|
+
}
|
|
14
|
+
export interface ToastController {
|
|
15
|
+
toast(options: ToastOptions): void;
|
|
16
|
+
dismiss(): void;
|
|
17
|
+
currentToast: ToastEntry | null;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAEjE,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,UAAU,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;IAClC,OAAO,IAAI,IAAI,CAAA;IACf,YAAY,EAAE,UAAU,GAAG,IAAI,CAAA;CAChC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tooee/toasts",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Toast notification system for Tooee",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Gareth Andrew",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/gingerhendrix/tooee.git",
|
|
10
|
+
"directory": "packages/toasts"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/gingerhendrix/tooee",
|
|
13
|
+
"bugs": "https://github.com/gingerhendrix/tooee/issues",
|
|
14
|
+
"keywords": ["tui", "terminal", "cli", "opentui", "toasts", "notifications"],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"import": {
|
|
19
|
+
"@tooee/source": "./src/index.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": ["dist", "src"],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"typecheck": "tsc --noEmit"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@tooee/themes": "workspace:*"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@opentui/core": "^0.1.67",
|
|
33
|
+
"@opentui/react": "^0.1.67",
|
|
34
|
+
"@types/bun": "^1.3.5",
|
|
35
|
+
"@types/react": "^19.1.10",
|
|
36
|
+
"typescript": "^5.8.3"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@opentui/core": "^0.1.67",
|
|
40
|
+
"@opentui/react": "^0.1.67",
|
|
41
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useTerminalDimensions } from "@opentui/react"
|
|
2
|
+
import { useTheme } from "@tooee/themes"
|
|
3
|
+
import { useToast } from "./ToastProvider.js"
|
|
4
|
+
import type { ToastLevel } from "./types.js"
|
|
5
|
+
|
|
6
|
+
const LEVEL_ICONS: Record<ToastLevel, string> = {
|
|
7
|
+
info: "ℹ",
|
|
8
|
+
success: "✓",
|
|
9
|
+
warning: "⚠",
|
|
10
|
+
error: "✗",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getLevelColor(theme: ReturnType<typeof useTheme>["theme"], level: ToastLevel): string {
|
|
14
|
+
switch (level) {
|
|
15
|
+
case "info":
|
|
16
|
+
return theme.info
|
|
17
|
+
case "success":
|
|
18
|
+
return theme.success
|
|
19
|
+
case "warning":
|
|
20
|
+
return theme.warning
|
|
21
|
+
case "error":
|
|
22
|
+
return theme.error
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function ToastContainer() {
|
|
27
|
+
const { currentToast } = useToast()
|
|
28
|
+
const { theme } = useTheme()
|
|
29
|
+
const { width: termWidth } = useTerminalDimensions()
|
|
30
|
+
|
|
31
|
+
if (!currentToast) return null
|
|
32
|
+
|
|
33
|
+
const maxWidth = Math.min(50, termWidth - 4)
|
|
34
|
+
const borderColor = getLevelColor(theme, currentToast.level)
|
|
35
|
+
const icon = LEVEL_ICONS[currentToast.level]
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<box
|
|
39
|
+
position="absolute"
|
|
40
|
+
bottom={1}
|
|
41
|
+
right={1}
|
|
42
|
+
maxWidth={maxWidth}
|
|
43
|
+
border
|
|
44
|
+
borderColor={borderColor}
|
|
45
|
+
backgroundColor={theme.backgroundPanel}
|
|
46
|
+
paddingLeft={1}
|
|
47
|
+
paddingRight={1}
|
|
48
|
+
>
|
|
49
|
+
<text
|
|
50
|
+
content={`${icon} ${currentToast.message}`}
|
|
51
|
+
fg={theme.text}
|
|
52
|
+
/>
|
|
53
|
+
</box>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createContext, useContext, useState, useCallback, useRef, useEffect } from "react"
|
|
2
|
+
import type { ReactNode } from "react"
|
|
3
|
+
import type { ToastOptions, ToastEntry, ToastController } from "./types.js"
|
|
4
|
+
|
|
5
|
+
const DEFAULT_DURATIONS: Record<string, number> = {
|
|
6
|
+
info: 2000,
|
|
7
|
+
success: 1500,
|
|
8
|
+
warning: 3000,
|
|
9
|
+
error: 5000,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let nextToastId = 0
|
|
13
|
+
|
|
14
|
+
const ToastContext = createContext<ToastController | null>(null)
|
|
15
|
+
|
|
16
|
+
export function ToastProvider({ children }: { children: ReactNode }) {
|
|
17
|
+
const [currentToast, setCurrentToast] = useState<ToastEntry | null>(null)
|
|
18
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
19
|
+
|
|
20
|
+
const clearTimer = useCallback(() => {
|
|
21
|
+
if (timerRef.current !== null) {
|
|
22
|
+
clearTimeout(timerRef.current)
|
|
23
|
+
timerRef.current = null
|
|
24
|
+
}
|
|
25
|
+
}, [])
|
|
26
|
+
|
|
27
|
+
const dismiss = useCallback(() => {
|
|
28
|
+
clearTimer()
|
|
29
|
+
setCurrentToast(null)
|
|
30
|
+
}, [clearTimer])
|
|
31
|
+
|
|
32
|
+
const toast = useCallback(
|
|
33
|
+
(options: ToastOptions) => {
|
|
34
|
+
clearTimer()
|
|
35
|
+
|
|
36
|
+
const level = options.level ?? "info"
|
|
37
|
+
const duration = options.duration ?? DEFAULT_DURATIONS[level] ?? 2000
|
|
38
|
+
const id = options.id ?? `toast-${nextToastId++}`
|
|
39
|
+
|
|
40
|
+
const entry: ToastEntry = {
|
|
41
|
+
id,
|
|
42
|
+
message: options.message,
|
|
43
|
+
level,
|
|
44
|
+
duration,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setCurrentToast(entry)
|
|
48
|
+
|
|
49
|
+
timerRef.current = setTimeout(() => {
|
|
50
|
+
setCurrentToast((current) => (current?.id === id ? null : current))
|
|
51
|
+
timerRef.current = null
|
|
52
|
+
}, duration)
|
|
53
|
+
},
|
|
54
|
+
[clearTimer],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
// Cleanup on unmount
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
return () => clearTimer()
|
|
60
|
+
}, [clearTimer])
|
|
61
|
+
|
|
62
|
+
const controller: ToastController = {
|
|
63
|
+
toast,
|
|
64
|
+
dismiss,
|
|
65
|
+
currentToast,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return <ToastContext.Provider value={controller}>{children}</ToastContext.Provider>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function useToast(): ToastController {
|
|
72
|
+
const ctx = useContext(ToastContext)
|
|
73
|
+
if (!ctx) {
|
|
74
|
+
throw new Error("useToast must be used within a ToastProvider")
|
|
75
|
+
}
|
|
76
|
+
return ctx
|
|
77
|
+
}
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type ToastLevel = "info" | "success" | "warning" | "error"
|
|
2
|
+
|
|
3
|
+
export interface ToastOptions {
|
|
4
|
+
message: string
|
|
5
|
+
level?: ToastLevel
|
|
6
|
+
duration?: number
|
|
7
|
+
id?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ToastEntry {
|
|
11
|
+
id: string
|
|
12
|
+
message: string
|
|
13
|
+
level: ToastLevel
|
|
14
|
+
duration: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ToastController {
|
|
18
|
+
toast(options: ToastOptions): void
|
|
19
|
+
dismiss(): void
|
|
20
|
+
currentToast: ToastEntry | null
|
|
21
|
+
}
|