mirror of
https://bitbucket.org/Mattrixwv/mattrixwvreactcomponents.git
synced 2025-12-06 13:43:59 -05:00
Toaster component created
This commit is contained in:
70
README.md
70
README.md
@@ -1,69 +1 @@
|
|||||||
# React + TypeScript + Vite
|
Under Construction
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
|
||||||
|
|
||||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
||||||
|
|
||||||
```js
|
|
||||||
export default tseslint.config([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
|
|
||||||
// Remove tseslint.configs.recommended and replace with this
|
|
||||||
...tseslint.configs.recommendedTypeChecked,
|
|
||||||
// Alternatively, use this for stricter rules
|
|
||||||
...tseslint.configs.strictTypeChecked,
|
|
||||||
// Optionally, add this for stylistic rules
|
|
||||||
...tseslint.configs.stylisticTypeChecked,
|
|
||||||
|
|
||||||
// Other configs...
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// eslint.config.js
|
|
||||||
import reactX from 'eslint-plugin-react-x'
|
|
||||||
import reactDom from 'eslint-plugin-react-dom'
|
|
||||||
|
|
||||||
export default tseslint.config([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
// Enable lint rules for React
|
|
||||||
reactX.configs['recommended-typescript'],
|
|
||||||
// Enable lint rules for React DOM
|
|
||||||
reactDom.configs.recommended,
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
|
||||||
|
|||||||
3
TODO.txt
3
TODO.txt
@@ -1,5 +1,2 @@
|
|||||||
Inputs:
|
Inputs:
|
||||||
slider, multi-value slider
|
slider, multi-value slider
|
||||||
|
|
||||||
|
|
||||||
Toaster
|
|
||||||
|
|||||||
32
lib/component/toaster/Toaster.tsx
Normal file
32
lib/component/toaster/Toaster.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import type { ToasterProps } from "$/types/Toaster";
|
||||||
|
import { Transition } from "@headlessui/react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
|
||||||
|
export default function Toaster({
|
||||||
|
toast,
|
||||||
|
className
|
||||||
|
}: ToasterProps){
|
||||||
|
return (
|
||||||
|
<Transition
|
||||||
|
show={toast.length > 1 || (toast.length === 1 && toast[0].hideTime > new Date())}
|
||||||
|
enter="transform transition duration-500"
|
||||||
|
enterFrom="-translate-y-[25vh]"
|
||||||
|
enterTo="translate-y-0 ease-out"
|
||||||
|
leave="transform transition duration-500"
|
||||||
|
leaveFrom="translate-y-0 ease-in"
|
||||||
|
leaveTo="-translate-y-[25vh]"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"fixed top-16 left-1/2 -translate-x-1/2 z-100",
|
||||||
|
"flex flex-col items-center justify-center",
|
||||||
|
"shadow-lg shadow-black/40",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{ toast.map((toast) => (<div key={toast.id}>{toast.message}</div>)) }
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
);
|
||||||
|
}
|
||||||
95
lib/providers/toaster/ToasterProvider.tsx
Normal file
95
lib/providers/toaster/ToasterProvider.tsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { DangerMessageBlock, SuccessMessageBlock, WarningMessageBlock } from "$/component/message";
|
||||||
|
import Toaster from "$/component/toaster/Toaster";
|
||||||
|
import type { Toast, ToastProviderProps, ToastProviderState } from "$/types/Toaster";
|
||||||
|
import moment from "moment";
|
||||||
|
import { createContext, useCallback, useContext, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
const toastInitialState: ToastProviderState = {
|
||||||
|
toast: [],
|
||||||
|
hideToast: () => {},
|
||||||
|
addToast: () => "",
|
||||||
|
addSuccess: () => "",
|
||||||
|
addWarning: () => "",
|
||||||
|
addDanger: () => ""
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ToasterProviderContext = createContext<ToastProviderState>(toastInitialState);
|
||||||
|
|
||||||
|
|
||||||
|
export default function ToasterProvider({
|
||||||
|
className,
|
||||||
|
children
|
||||||
|
}: ToastProviderProps){
|
||||||
|
const [ toast, setToast ] = useState<Toast[]>([]);
|
||||||
|
|
||||||
|
|
||||||
|
const hideToast = useCallback((id: string) => {
|
||||||
|
setToast((prev) => {
|
||||||
|
if(prev.length === 1 && prev[0].id === id){
|
||||||
|
const current = prev[0].hideTime > moment(new Date()).subtract(600, "ms").toDate() ? [...prev] : [];
|
||||||
|
if(current.length > 0){
|
||||||
|
setTimeout(() => hideToast(id), 600);
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return prev.filter((toast) => toast.id !== id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [ toast ]);
|
||||||
|
|
||||||
|
const addToast = useCallback((message: React.ReactNode, duration?: number) => {
|
||||||
|
if(!duration){
|
||||||
|
duration = 5000;
|
||||||
|
}
|
||||||
|
const id = crypto.randomUUID();
|
||||||
|
|
||||||
|
setToast((prev) => [ ...prev, { id, message, duration, hideTime: moment(new Date()).add(duration, "ms").toDate() } ]);
|
||||||
|
setTimeout(() => hideToast(id), duration);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}, [ toast ]);
|
||||||
|
|
||||||
|
const addSuccess = useCallback((message: React.ReactNode, duration?: number) => {
|
||||||
|
return addToast(<SuccessMessageBlock>{message}</SuccessMessageBlock>, duration);
|
||||||
|
}, [ addToast ]);
|
||||||
|
|
||||||
|
const addWarning = useCallback((message: React.ReactNode, duration?: number) => {
|
||||||
|
return addToast(<WarningMessageBlock>{message}</WarningMessageBlock>, duration);
|
||||||
|
}, [ addToast ]);
|
||||||
|
|
||||||
|
const addDanger = useCallback((message: React.ReactNode, duration?: number) => {
|
||||||
|
return addToast(<DangerMessageBlock>{message}</DangerMessageBlock>, duration);
|
||||||
|
}, [ addToast ]);
|
||||||
|
|
||||||
|
const value: ToastProviderState = useMemo(() => ({
|
||||||
|
toast,
|
||||||
|
hideToast,
|
||||||
|
addToast,
|
||||||
|
addSuccess,
|
||||||
|
addWarning,
|
||||||
|
addDanger
|
||||||
|
}), [ toast, hideToast, addToast, addSuccess, addWarning, addDanger ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToasterProviderContext.Provider value={value}>
|
||||||
|
<Toaster
|
||||||
|
toast={toast}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</ToasterProviderContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function useToaster(){
|
||||||
|
const context = useContext(ToasterProviderContext);
|
||||||
|
|
||||||
|
if(!context){
|
||||||
|
throw new Error("useToaster must be used within a ToasterProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
24
lib/types/Toaster.d.ts
vendored
Normal file
24
lib/types/Toaster.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export interface Toast {
|
||||||
|
id: string;
|
||||||
|
message: React.ReactNode;
|
||||||
|
hideTime: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToastProviderState {
|
||||||
|
toast: Toast[];
|
||||||
|
hideToast: (id: string) => void;
|
||||||
|
addToast: (message: ReactNode, duration?: number) => string;
|
||||||
|
addSuccess: (message: ReactNode, duration?: number) => string;
|
||||||
|
addWarning: (message: ReactNode, duration?: number) => string;
|
||||||
|
addDanger: (message: ReactNode, duration?: number) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToastProviderProps {
|
||||||
|
className?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToasterProps {
|
||||||
|
toast: Toast[];
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import ThemeProvider from "$/providers/theme/ThemeProvider";
|
import ThemeProvider from "$/providers/theme/ThemeProvider";
|
||||||
|
import ToasterProvider from "$/providers/toaster/ToasterProvider";
|
||||||
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
@@ -17,7 +18,9 @@ declare module "@tanstack/react-router" {
|
|||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<ThemeProvider defaultTheme="dark">
|
<ThemeProvider defaultTheme="dark">
|
||||||
<RouterProvider router={router}/>
|
<ToasterProvider className="bg-zinc-700 text-white px-4 py-2 min-w-32 max-w-128 rounded-lg gap-y-4">
|
||||||
|
<RouterProvider router={router}/>
|
||||||
|
</ToasterProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { DangerButton, PrimaryButton, SuccessButton, WarningButton } from "$/component/button";
|
import { DangerButton, PrimaryButton, SuccessButton, WarningButton } from "$/component/button";
|
||||||
|
import { PrimaryMessageBlock } from "$/component/message";
|
||||||
import { Modal } from "$/component/modal";
|
import { Modal } from "$/component/modal";
|
||||||
|
import { useToaster } from "$/providers/toaster/ToasterProvider";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
@@ -12,6 +14,9 @@ export const Route = createFileRoute("/modal/")({
|
|||||||
function ModalPage(){
|
function ModalPage(){
|
||||||
const [ displayCenteredModal, setDisplayCenteredModal ] = useState(false);
|
const [ displayCenteredModal, setDisplayCenteredModal ] = useState(false);
|
||||||
const [ displayTopModal, setDisplayTopModal ] = useState(false);
|
const [ displayTopModal, setDisplayTopModal ] = useState(false);
|
||||||
|
const [ toasterNumber, setToasterNumber ] = useState(1);
|
||||||
|
|
||||||
|
const { addToast, addSuccess, addWarning, addDanger } = useToaster();
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -36,22 +41,22 @@ function ModalPage(){
|
|||||||
<div
|
<div
|
||||||
className="flex flex-row items-center justify-center gap-x-4">
|
className="flex flex-row items-center justify-center gap-x-4">
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
onClick={() => {}}
|
onClick={() => { addToast(<PrimaryMessageBlock>Toaster {toasterNumber}</PrimaryMessageBlock>); setToasterNumber(toasterNumber + 1); }}
|
||||||
>
|
>
|
||||||
Timed Modal
|
Timed Modal
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
<SuccessButton
|
<SuccessButton
|
||||||
onClick={() => {}}
|
onClick={() => { addSuccess(`Success Toaster ${toasterNumber}`); setToasterNumber(toasterNumber + 1); }}
|
||||||
>
|
>
|
||||||
Timed Modal
|
Timed Modal
|
||||||
</SuccessButton>
|
</SuccessButton>
|
||||||
<WarningButton
|
<WarningButton
|
||||||
onClick={() => {}}
|
onClick={() => { addWarning(`Warning Toaster ${toasterNumber}`); setToasterNumber(toasterNumber + 1); }}
|
||||||
>
|
>
|
||||||
Timed Modal
|
Timed Modal
|
||||||
</WarningButton>
|
</WarningButton>
|
||||||
<DangerButton
|
<DangerButton
|
||||||
onClick={() => {}}
|
onClick={() => { addDanger(`Danger Toaster ${toasterNumber}`); setToasterNumber(toasterNumber + 1); }}
|
||||||
>
|
>
|
||||||
Timed Modal
|
Timed Modal
|
||||||
</DangerButton>
|
</DangerButton>
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import WarningCheckbox from "$/component/input/checkbox/WarningCheckbox";
|
|||||||
import DateInput from "$/component/input/date/DateInput";
|
import DateInput from "$/component/input/date/DateInput";
|
||||||
import DateTimeInput from "$/component/input/date/DateTimeInput";
|
import DateTimeInput from "$/component/input/date/DateTimeInput";
|
||||||
import TimeInput from "$/component/input/date/TimeInput";
|
import TimeInput from "$/component/input/date/TimeInput";
|
||||||
|
import MultiNumberSlider from "$/component/input/number/MultiNumberSlider";
|
||||||
|
import NumberSlider from "$/component/input/number/NumberSlider";
|
||||||
import DangerRadioButton from "$/component/input/radio/DangerRadioButton";
|
import DangerRadioButton from "$/component/input/radio/DangerRadioButton";
|
||||||
import DarkRadioButton from "$/component/input/radio/DarkRadioButton";
|
import DarkRadioButton from "$/component/input/radio/DarkRadioButton";
|
||||||
import InfoRadioButton from "$/component/input/radio/InfoRadioButton";
|
import InfoRadioButton from "$/component/input/radio/InfoRadioButton";
|
||||||
@@ -43,7 +45,7 @@ import { useState } from "react";
|
|||||||
import { BsCheck, BsX } from "react-icons/bs";
|
import { BsCheck, BsX } from "react-icons/bs";
|
||||||
|
|
||||||
|
|
||||||
export function SwitchContent(): React.ReactNode{
|
export function SwitchContent(){
|
||||||
const sizes: MattrixwvSwitchSize[] = [ "xs", "sm", "md", "lg", "xl" ];
|
const sizes: MattrixwvSwitchSize[] = [ "xs", "sm", "md", "lg", "xl" ];
|
||||||
|
|
||||||
|
|
||||||
@@ -761,3 +763,16 @@ export function DateContent(){
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SliderContent(){
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex flex-col items-center justify-center my-8 gap-y-8"
|
||||||
|
>
|
||||||
|
<NumberSlider
|
||||||
|
/>
|
||||||
|
<MultiNumberSlider
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user