vyrn 1.0.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/.eslintrc.js +25 -0
- package/.prettierrc +8 -0
- package/README.md +20 -0
- package/dist/components/Toast.d.ts +3 -0
- package/dist/components/ToastContainer.d.ts +7 -0
- package/dist/components/ToastProvider.d.ts +2 -0
- package/dist/context/ToastContext.d.ts +4 -0
- package/dist/hooks/useToast.d.ts +8 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/utils/animations.d.ts +2 -0
- package/dist/utils/constants.d.ts +3 -0
- package/jest.config.js +9 -0
- package/package.json +51 -0
- package/rollup.config.js +38 -0
- package/src/components/Toast.tsx +26 -0
- package/src/components/ToastContainer.tsx +18 -0
- package/src/components/ToastProvider.tsx +39 -0
- package/src/context/ToastContext.tsx +13 -0
- package/src/hooks/useToast.ts +23 -0
- package/src/index.ts +4 -0
- package/src/styles/toast.css +71 -0
- package/src/types/index.ts +14 -0
- package/src/utils/animations.ts +14 -0
- package/src/utils/constants.ts +4 -0
- package/tests/Toast.test.tsx +32 -0
- package/tsconfig.json +20 -0
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
parser: '@typescript-eslint/parser',
|
|
3
|
+
extends: [
|
|
4
|
+
'plugin:react/recommended',
|
|
5
|
+
'plugin:@typescript-eslint/recommended',
|
|
6
|
+
'prettier',
|
|
7
|
+
'plugin:react-hooks/recommended',
|
|
8
|
+
],
|
|
9
|
+
parserOptions: {
|
|
10
|
+
ecmaVersion: 2020,
|
|
11
|
+
sourceType: 'module',
|
|
12
|
+
ecmaFeatures: {
|
|
13
|
+
jsx: true,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
rules: {
|
|
17
|
+
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
|
|
18
|
+
},
|
|
19
|
+
settings: {
|
|
20
|
+
react: {
|
|
21
|
+
version: 'detect',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Vyrn Toast
|
|
2
|
+
|
|
3
|
+
Vyrn is a modern, customizable toast library for React and Next.js applications. It provides an easy-to-use API for displaying toast notifications with various styles and animations.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚀 Easy to use
|
|
8
|
+
- 🎨 Customizable styles
|
|
9
|
+
- ♿ Accessible
|
|
10
|
+
- 🔧 TypeScript support
|
|
11
|
+
- âš¡ Lightweight
|
|
12
|
+
- 🔄 Smooth animations
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install vyrn
|
|
18
|
+
# or
|
|
19
|
+
yarn add vyrn
|
|
20
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ToastType } from '../types';
|
|
2
|
+
export declare const useToast: () => {
|
|
3
|
+
toast: (message: string, type?: ToastType, duration?: number) => void;
|
|
4
|
+
info: (message: string, duration?: number) => void;
|
|
5
|
+
success: (message: string, duration?: number) => void;
|
|
6
|
+
warning: (message: string, duration?: number) => void;
|
|
7
|
+
error: (message: string, duration?: number) => void;
|
|
8
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import t,{createContext as r,useContext as e,useEffect as n,useState as o,useCallback as a}from"react";var i=function(){return i=Object.assign||function(t){for(var r,e=1,n=arguments.length;e<n;e++)for(var o in r=arguments[e])Object.prototype.hasOwnProperty.call(r,o)&&(t[o]=r[o]);return t},i.apply(this,arguments)};function u(t,r,e){if(e||2===arguments.length)for(var n,o=0,a=r.length;o<a;o++)!n&&o in r||(n||(n=Array.prototype.slice.call(r,0,o)),n[o]=r[o]);return t.concat(n||Array.prototype.slice.call(r))}"function"==typeof SuppressedError&&SuppressedError;var c=r(void 0),s=function(){var t=e(c);if(!t)throw new Error("useToastContext must be used within a ToastProvider");return t},l=function(r){var e=r.id,o=r.message,a=r.type,i=r.duration,u=void 0===i?3e3:i,c=s().removeToast;return n((function(){var t=setTimeout((function(){c(e)}),u);return function(){return clearTimeout(t)}}),[e,u,c]),t.createElement("div",{className:"vyrn-toast vyrn-toast-".concat(a),role:"alert"},t.createElement("div",{className:"vyrn-toast-message"},o),t.createElement("button",{className:"vyrn-toast-close",onClick:function(){return c(e)},"aria-label":"Close"},"×"))},f=function(r){var e=r.toasts;return t.createElement("div",{className:"vyrn-toast-container","aria-live":"polite","aria-atomic":"true"},e.map((function(r){return t.createElement(l,i({key:r.id},r))})))},v=function(r){var e=r.children,s=o([]),l=s[0],v=s[1],m=o([]),d=m[0],p=m[1],y=a((function(t){var r=Math.random().toString(36).substr(2,9);l.length<5?v((function(e){return u(u([],e,!0),[i(i({},t),{id:r})],!1)})):p((function(r){return u(u([],r,!0),[t],!1)}))}),[l]),g=a((function(t){v((function(r){return r.filter((function(r){return r.id!==t}))}))}),[]);return n((function(){if(l.length<5&&d.length>0){var t=d[0],r=d.slice(1);y(t),p(r)}}),[l,d,y]),t.createElement(c.Provider,{value:{addToast:y,removeToast:g}},e,t.createElement(f,{toasts:l}))},m=function(){var t=s().addToast,r=a((function(r,e,n){void 0===e&&(e="info"),t({message:r,type:e,duration:n})}),[t]);return{toast:r,info:function(t,e){return r(t,"info",e)},success:function(t,e){return r(t,"success",e)},warning:function(t,e){return r(t,"warning",e)},error:function(t,e){return r(t,"error",e)}}};export{v as ToastProvider,m as useToast};
|
|
2
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t=require("react");function e(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var r=e(t),n=function(){return n=Object.assign||function(t){for(var e,r=1,n=arguments.length;r<n;r++)for(var a in e=arguments[r])Object.prototype.hasOwnProperty.call(e,a)&&(t[a]=e[a]);return t},n.apply(this,arguments)};function a(t,e,r){if(r||2===arguments.length)for(var n,a=0,o=e.length;a<o;a++)!n&&a in e||(n||(n=Array.prototype.slice.call(e,0,a)),n[a]=e[a]);return t.concat(n||Array.prototype.slice.call(e))}"function"==typeof SuppressedError&&SuppressedError;var o=t.createContext(void 0),u=function(){var e=t.useContext(o);if(!e)throw new Error("useToastContext must be used within a ToastProvider");return e},i=function(e){var n=e.id,a=e.message,o=e.type,i=e.duration,s=void 0===i?3e3:i,c=u().removeToast;return t.useEffect((function(){var t=setTimeout((function(){c(n)}),s);return function(){return clearTimeout(t)}}),[n,s,c]),r.default.createElement("div",{className:"vyrn-toast vyrn-toast-".concat(o),role:"alert"},r.default.createElement("div",{className:"vyrn-toast-message"},a),r.default.createElement("button",{className:"vyrn-toast-close",onClick:function(){return c(n)},"aria-label":"Close"},"×"))},s=function(t){var e=t.toasts;return r.default.createElement("div",{className:"vyrn-toast-container","aria-live":"polite","aria-atomic":"true"},e.map((function(t){return r.default.createElement(i,n({key:t.id},t))})))};exports.ToastProvider=function(e){var u=e.children,i=t.useState([]),c=i[0],l=i[1],f=t.useState([]),d=f[0],v=f[1],p=t.useCallback((function(t){var e=Math.random().toString(36).substr(2,9);c.length<5?l((function(r){return a(a([],r,!0),[n(n({},t),{id:e})],!1)})):v((function(e){return a(a([],e,!0),[t],!1)}))}),[c]),m=t.useCallback((function(t){l((function(e){return e.filter((function(e){return e.id!==t}))}))}),[]);return t.useEffect((function(){if(c.length<5&&d.length>0){var t=d[0],e=d.slice(1);p(t),v(e)}}),[c,d,p]),r.default.createElement(o.Provider,{value:{addToast:p,removeToast:m}},u,r.default.createElement(s,{toasts:c}))},exports.useToast=function(){var e=u().addToast,r=t.useCallback((function(t,r,n){void 0===r&&(r="info"),e({message:t,type:r,duration:n})}),[e]);return{toast:r,info:function(t,e){return r(t,"info",e)},success:function(t,e){return r(t,"success",e)},warning:function(t,e){return r(t,"warning",e)},error:function(t,e){return r(t,"error",e)}}};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type ToastType = 'info' | 'success' | 'warning' | 'error';
|
|
2
|
+
export interface ToastProps {
|
|
3
|
+
id: string;
|
|
4
|
+
message: string;
|
|
5
|
+
type: ToastType;
|
|
6
|
+
duration?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ToastContextValue {
|
|
9
|
+
addToast: (toast: Omit<ToastProps, 'id'>) => void;
|
|
10
|
+
removeToast: (id: string) => void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const fadeIn = "\n @keyframes vyrnFadeIn {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n";
|
|
2
|
+
export declare const fadeOut = "\n @keyframes vyrnFadeOut {\n from { opacity: 1; transform: translateY(0); }\n to { opacity: 0; transform: translateY(-20px); }\n }\n";
|
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vyrn",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A modern, customizable toast library for React and Next.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "rollup -c",
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"lint": "eslint 'src/**/*.{js,ts,tsx}'",
|
|
12
|
+
"lint:fix": "eslint 'src/**/*.{js,ts,tsx}' --fix",
|
|
13
|
+
"format": "prettier --write 'src/**/*.{js,ts,tsx}'",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"react",
|
|
18
|
+
"nextjs",
|
|
19
|
+
"toast",
|
|
20
|
+
"notification"
|
|
21
|
+
],
|
|
22
|
+
"author": "Your Name",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"react": "^17.0.0 || ^18.0.0",
|
|
26
|
+
"react-dom": "^17.0.0 || ^18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
30
|
+
"@testing-library/react": "^13.0.0",
|
|
31
|
+
"@testing-library/react-hooks": "^7.0.2",
|
|
32
|
+
"@types/jest": "^27.4.1",
|
|
33
|
+
"@types/react": "^18.0.0",
|
|
34
|
+
"@types/react-dom": "^18.0.0",
|
|
35
|
+
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
|
36
|
+
"@typescript-eslint/parser": "^5.13.0",
|
|
37
|
+
"eslint": "^8.10.0",
|
|
38
|
+
"eslint-config-prettier": "^8.5.0",
|
|
39
|
+
"eslint-plugin-react": "^7.29.3",
|
|
40
|
+
"eslint-plugin-react-hooks": "^4.3.0",
|
|
41
|
+
"jest": "^27.5.1",
|
|
42
|
+
"postcss": "^8.4.49",
|
|
43
|
+
"prettier": "^2.5.1",
|
|
44
|
+
"rollup": "^2.70.0",
|
|
45
|
+
"rollup-plugin-postcss": "^4.0.0",
|
|
46
|
+
"rollup-plugin-terser": "^7.0.2",
|
|
47
|
+
"rollup-plugin-typescript2": "^0.31.0",
|
|
48
|
+
"ts-jest": "^27.1.3",
|
|
49
|
+
"typescript": "^4.5.0"
|
|
50
|
+
}
|
|
51
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import typescript from 'rollup-plugin-typescript2';
|
|
2
|
+
import postcss from 'rollup-plugin-postcss';
|
|
3
|
+
import { terser } from 'rollup-plugin-terser';
|
|
4
|
+
import pkg from './package.json';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
input: 'src/index.ts',
|
|
8
|
+
output: [
|
|
9
|
+
{
|
|
10
|
+
file: pkg.main,
|
|
11
|
+
format: 'cjs',
|
|
12
|
+
exports: 'named',
|
|
13
|
+
sourcemap: true,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
file: pkg.module,
|
|
17
|
+
format: 'es',
|
|
18
|
+
exports: 'named',
|
|
19
|
+
sourcemap: true,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
plugins: [
|
|
23
|
+
typescript({
|
|
24
|
+
tsconfig: 'tsconfig.json',
|
|
25
|
+
clean: true,
|
|
26
|
+
}),
|
|
27
|
+
postcss({
|
|
28
|
+
extensions: ['.css'],
|
|
29
|
+
minimize: true,
|
|
30
|
+
inject: {
|
|
31
|
+
insertAt: 'top',
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
terser(),
|
|
35
|
+
],
|
|
36
|
+
external: [...Object.keys(pkg.peerDependencies || {})],
|
|
37
|
+
};
|
|
38
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { ToastProps } from '../types';
|
|
3
|
+
import { useToastContext } from '../context/ToastContext';
|
|
4
|
+
import { DEFAULT_DURATION } from '../utils/constants';
|
|
5
|
+
|
|
6
|
+
export const Toast: React.FC<ToastProps> = ({ id, message, type, duration = DEFAULT_DURATION }) => {
|
|
7
|
+
const { removeToast } = useToastContext();
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const timer = setTimeout(() => {
|
|
11
|
+
removeToast(id);
|
|
12
|
+
}, duration);
|
|
13
|
+
|
|
14
|
+
return () => clearTimeout(timer);
|
|
15
|
+
}, [id, duration, removeToast]);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className={`vyrn-toast vyrn-toast-${type}`} role="alert">
|
|
19
|
+
<div className="vyrn-toast-message">{message}</div>
|
|
20
|
+
<button className="vyrn-toast-close" onClick={() => removeToast(id)} aria-label="Close">
|
|
21
|
+
×
|
|
22
|
+
</button>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Toast } from './Toast';
|
|
3
|
+
import { ToastProps } from '../types';
|
|
4
|
+
|
|
5
|
+
interface ToastContainerProps {
|
|
6
|
+
toasts: ToastProps[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const ToastContainer: React.FC<ToastContainerProps> = ({ toasts }) => {
|
|
10
|
+
return (
|
|
11
|
+
<div className="vyrn-toast-container" aria-live="polite" aria-atomic="true">
|
|
12
|
+
{toasts.map((toast) => (
|
|
13
|
+
<Toast key={toast.id} {...toast} />
|
|
14
|
+
))}
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React, { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { ToastContext } from '../context/ToastContext';
|
|
3
|
+
import { ToastContainer } from './ToastContainer';
|
|
4
|
+
import { ToastProps } from '../types';
|
|
5
|
+
import { MAX_TOASTS } from '../utils/constants';
|
|
6
|
+
|
|
7
|
+
export const ToastProvider: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
|
|
8
|
+
const [toasts, setToasts] = useState<ToastProps[]>([]);
|
|
9
|
+
const [queue, setQueue] = useState<Omit<ToastProps, 'id'>[]>([]);
|
|
10
|
+
|
|
11
|
+
const addToast = useCallback((toast: Omit<ToastProps, 'id'>) => {
|
|
12
|
+
const id = Math.random().toString(36).substr(2, 9);
|
|
13
|
+
if (toasts.length < MAX_TOASTS) {
|
|
14
|
+
setToasts((prevToasts) => [...prevToasts, { ...toast, id }]);
|
|
15
|
+
} else {
|
|
16
|
+
setQueue((prevQueue) => [...prevQueue, toast]);
|
|
17
|
+
}
|
|
18
|
+
}, [toasts]);
|
|
19
|
+
|
|
20
|
+
const removeToast = useCallback((id: string) => {
|
|
21
|
+
setToasts((prevToasts) => prevToasts.filter((toast) => toast.id !== id));
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (toasts.length < MAX_TOASTS && queue.length > 0) {
|
|
26
|
+
const [nextToast, ...restQueue] = queue;
|
|
27
|
+
addToast(nextToast);
|
|
28
|
+
setQueue(restQueue);
|
|
29
|
+
}
|
|
30
|
+
}, [toasts, queue, addToast]);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<ToastContext.Provider value={{ addToast, removeToast }}>
|
|
34
|
+
{children}
|
|
35
|
+
<ToastContainer toasts={toasts} />
|
|
36
|
+
</ToastContext.Provider>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { createContext, useContext } from 'react';
|
|
2
|
+
import { ToastContextValue } from '../types';
|
|
3
|
+
|
|
4
|
+
export const ToastContext = createContext<ToastContextValue | undefined>(undefined);
|
|
5
|
+
|
|
6
|
+
export const useToastContext = () => {
|
|
7
|
+
const context = useContext(ToastContext);
|
|
8
|
+
if (!context) {
|
|
9
|
+
throw new Error('useToastContext must be used within a ToastProvider');
|
|
10
|
+
}
|
|
11
|
+
return context;
|
|
12
|
+
};
|
|
13
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useToastContext } from '../context/ToastContext';
|
|
3
|
+
import { ToastType } from '../types';
|
|
4
|
+
|
|
5
|
+
export const useToast = () => {
|
|
6
|
+
const { addToast } = useToastContext();
|
|
7
|
+
|
|
8
|
+
const toast = useCallback(
|
|
9
|
+
(message: string, type: ToastType = 'info', duration?: number) => {
|
|
10
|
+
addToast({ message, type, duration });
|
|
11
|
+
},
|
|
12
|
+
[addToast]
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
toast,
|
|
17
|
+
info: (message: string, duration?: number) => toast(message, 'info', duration),
|
|
18
|
+
success: (message: string, duration?: number) => toast(message, 'success', duration),
|
|
19
|
+
warning: (message: string, duration?: number) => toast(message, 'warning', duration),
|
|
20
|
+
error: (message: string, duration?: number) => toast(message, 'error', duration),
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
.vyrn-toast-container {
|
|
2
|
+
position: fixed;
|
|
3
|
+
top: 1rem;
|
|
4
|
+
right: 1rem;
|
|
5
|
+
z-index: 9999;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.vyrn-toast {
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: space-between;
|
|
12
|
+
padding: 0.75rem 1rem;
|
|
13
|
+
border-radius: 4px;
|
|
14
|
+
margin-bottom: 0.5rem;
|
|
15
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
16
|
+
animation: vyrnFadeIn 0.3s ease-out;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.vyrn-toast-info {
|
|
20
|
+
background-color: #3498db;
|
|
21
|
+
color: #fff;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.vyrn-toast-success {
|
|
25
|
+
background-color: #2ecc71;
|
|
26
|
+
color: #fff;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.vyrn-toast-warning {
|
|
30
|
+
background-color: #f39c12;
|
|
31
|
+
color: #fff;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.vyrn-toast-error {
|
|
35
|
+
background-color: #e74c3c;
|
|
36
|
+
color: #fff;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.vyrn-toast-message {
|
|
40
|
+
flex-grow: 1;
|
|
41
|
+
margin-right: 1rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.vyrn-toast-close {
|
|
45
|
+
background: none;
|
|
46
|
+
border: none;
|
|
47
|
+
color: inherit;
|
|
48
|
+
font-size: 1.2rem;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
opacity: 0.7;
|
|
51
|
+
transition: opacity 0.2s;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.vyrn-toast-close:hover {
|
|
55
|
+
opacity: 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@keyframes vyrnFadeIn {
|
|
59
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
60
|
+
to { opacity: 1; transform: translateY(0); }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@keyframes vyrnFadeOut {
|
|
64
|
+
from { opacity: 1; transform: translateY(0); }
|
|
65
|
+
to { opacity: 0; transform: translateY(-20px); }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.vyrn-toast-exit {
|
|
69
|
+
animation: vyrnFadeOut 0.3s ease-in forwards;
|
|
70
|
+
}
|
|
71
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type ToastType = 'info' | 'success' | 'warning' | 'error';
|
|
2
|
+
|
|
3
|
+
export interface ToastProps {
|
|
4
|
+
id: string;
|
|
5
|
+
message: string;
|
|
6
|
+
type: ToastType;
|
|
7
|
+
duration?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ToastContextValue {
|
|
11
|
+
addToast: (toast: Omit<ToastProps, 'id'>) => void;
|
|
12
|
+
removeToast: (id: string) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const fadeIn = `
|
|
2
|
+
@keyframes vyrnFadeIn {
|
|
3
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
4
|
+
to { opacity: 1; transform: translateY(0); }
|
|
5
|
+
}
|
|
6
|
+
`;
|
|
7
|
+
|
|
8
|
+
export const fadeOut = `
|
|
9
|
+
@keyframes vyrnFadeOut {
|
|
10
|
+
from { opacity: 1; transform: translateY(0); }
|
|
11
|
+
to { opacity: 0; transform: translateY(-20px); }
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { Toast } from '../src/components/Toast';
|
|
4
|
+
import { ToastContext } from '../src/context/ToastContext';
|
|
5
|
+
|
|
6
|
+
const mockRemoveToast = jest.fn();
|
|
7
|
+
|
|
8
|
+
const renderToast = (props: any) =>
|
|
9
|
+
render(
|
|
10
|
+
<ToastContext.Provider value={{ removeToast: mockRemoveToast } as any}>
|
|
11
|
+
<Toast {...props} />
|
|
12
|
+
</ToastContext.Provider>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
describe('Toast', () => {
|
|
16
|
+
it('renders the message', () => {
|
|
17
|
+
renderToast({ id: '1', message: 'Test message', type: 'info' });
|
|
18
|
+
expect(screen.getByText('Test message')).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('applies the correct CSS class based on type', () => {
|
|
22
|
+
renderToast({ id: '1', message: 'Test message', type: 'success' });
|
|
23
|
+
expect(screen.getByRole('alert')).toHaveClass('vyrn-toast-success');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('calls removeToast when close button is clicked', () => {
|
|
27
|
+
renderToast({ id: '1', message: 'Test message', type: 'info' });
|
|
28
|
+
screen.getByRole('button').click();
|
|
29
|
+
expect(mockRemoveToast).toHaveBeenCalledWith('1');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es5",
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"lib": ["dom", "esnext"],
|
|
6
|
+
"jsx": "react",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationDir": "dist",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"moduleResolution": "node",
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"outDir": "dist",
|
|
15
|
+
"rootDir": "src"
|
|
16
|
+
},
|
|
17
|
+
"include": ["src"],
|
|
18
|
+
"exclude": ["node_modules", "dist", "tests"]
|
|
19
|
+
}
|
|
20
|
+
|