diff --git a/package-lock.json b/package-lock.json
index 04eecab..c5761e1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,8 @@
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
+ "@types/lodash": "^4.17.5",
+ "lodash": "^4.17.21",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.1"
@@ -1976,6 +1978,11 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
+ "node_modules/@types/lodash": {
+ "version": "4.17.5",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz",
+ "integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw=="
+ },
"node_modules/@types/node": {
"version": "20.14.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.6.tgz",
@@ -5149,8 +5156,7 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
- "dev": true
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.merge": {
"version": "4.6.2",
diff --git a/package.json b/package.json
index 766c09b..d5bbb0e 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,8 @@
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
+ "@types/lodash": "^4.17.5",
+ "lodash": "^4.17.21",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.1"
diff --git a/src/assets/text.png b/src/assets/text.png
new file mode 100644
index 0000000..c2044a1
Binary files /dev/null and b/src/assets/text.png differ
diff --git a/src/components/App.tsx b/src/components/App.tsx
index 9097790..e8b24c4 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -1,6 +1,10 @@
import {BrowserRouter, useRoutes} from "react-router-dom";
import routesConfig from "../config/routesConfig";
import Navbar from "./Navbar";
+import {Suspense} from "react";
+import Loading from "./Loading";
+import {ThemeProvider} from "@mui/material";
+import theme from "../config/muiConfig";
const AppRoutes = () => {
return useRoutes(routesConfig);
@@ -9,10 +13,14 @@ const AppRoutes = () => {
function App() {
return (
-
-
-
-
+
+
+
+ }>
+
+
+
+
)
}
diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx
new file mode 100644
index 0000000..b4e7c97
--- /dev/null
+++ b/src/components/Loading.tsx
@@ -0,0 +1,53 @@
+import Typography from "@mui/material/Typography";
+import {useState} from "react";
+import clsx from "clsx";
+import Box from "@mui/material/Box";
+import {useTimeout} from "../hooks";
+
+export type FuseLoadingProps = {
+ delay?: number;
+ className?: string;
+};
+
+/**
+ * FuseLoading displays a loading state with an optional delay
+ */
+function FuseLoading(props: FuseLoadingProps) {
+ const {delay = 0, className} = props;
+ const [showLoading, setShowLoading] = useState(!delay);
+
+ useTimeout(() => {
+ setShowLoading(true);
+ }, delay);
+
+ return (
+
+
+ Chargement
+
+
div": {
+ backgroundColor: "palette.secondary.main",
+ },
+ }}
+ >
+
+
+
+
+
+ );
+}
+
+export default FuseLoading;
diff --git a/src/components/ToolHeader.tsx b/src/components/ToolHeader.tsx
new file mode 100644
index 0000000..09cbf94
--- /dev/null
+++ b/src/components/ToolHeader.tsx
@@ -0,0 +1,18 @@
+import {Box, Stack} from "@mui/material";
+import Typography from "@mui/material/Typography";
+import textImage from '../assets/text.png'
+
+interface ToolHeaderProps {
+ title: string;
+ description: string;
+}
+
+export default function ToolHeader({title, description}: ToolHeaderProps) {
+ return (
+
+ {title}
+ {description}
+
+
+ )
+}
diff --git a/src/components/ToolLayout.tsx b/src/components/ToolLayout.tsx
new file mode 100644
index 0000000..4612fcb
--- /dev/null
+++ b/src/components/ToolLayout.tsx
@@ -0,0 +1,7 @@
+import {Box} from "@mui/material";
+import {ReactNode} from "react";
+
+export default function ToolLayout({children}: { children: ReactNode }) {
+ return ({children})
+}
diff --git a/src/config/muiConfig.ts b/src/config/muiConfig.ts
new file mode 100644
index 0000000..53308fc
--- /dev/null
+++ b/src/config/muiConfig.ts
@@ -0,0 +1,11 @@
+import {createTheme} from "@mui/material";
+
+const theme = createTheme({
+ typography: {
+ button: {
+ textTransform: 'none'
+ }
+ }
+});
+
+export default theme;
diff --git a/src/config/routesConfig.tsx b/src/config/routesConfig.tsx
index f68f5a1..f62024a 100644
--- a/src/config/routesConfig.tsx
+++ b/src/config/routesConfig.tsx
@@ -2,6 +2,7 @@ import {RouteObject} from "react-router-dom";
import {Navigate} from "react-router-dom";
import {ImagesConfig} from "../pages/images/ImagesConfig";
import {lazy} from "react";
+import {StringConfig} from "../pages/string/StringConfig";
const Home = lazy(() => import("../pages/home"));
@@ -14,6 +15,10 @@ const routes: RouteObject[] = [
path: "images",
children: ImagesConfig
},
+ {
+ path: "string",
+ children: StringConfig
+ },
{
path: "*",
element: ,
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
new file mode 100644
index 0000000..cc9ab3f
--- /dev/null
+++ b/src/hooks/index.ts
@@ -0,0 +1,4 @@
+export {default as useDebounce} from "./useDebounce";
+export {default as useTimeout} from "./useTimeout";
+export {default as usePrevious} from "./usePrevious";
+export {default as useUpdateEffect} from "./useUpdateEffect";
diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts
new file mode 100644
index 0000000..4bb06ad
--- /dev/null
+++ b/src/hooks/useDebounce.ts
@@ -0,0 +1,38 @@
+import {useCallback, useEffect, useRef} from "react";
+import _ from "lodash";
+
+/**
+ * Debounce hook.
+ * @param {T} callback
+ * @param {number} delay
+ * @returns {T}
+ */
+function useDebounce void>(
+ callback: T,
+ delay: number,
+): T {
+ const callbackRef = useRef(callback);
+
+ // Update the current callback each time it changes.
+ useEffect(() => {
+ callbackRef.current = callback;
+ }, [callback]);
+
+ const debouncedFn = useCallback(
+ _.debounce((...args: never[]) => {
+ callbackRef.current(...args);
+ }, delay),
+ [delay],
+ );
+
+ useEffect(() => {
+ // Cleanup function to cancel any pending debounced calls
+ return () => {
+ debouncedFn.cancel();
+ };
+ }, [debouncedFn]);
+
+ return debouncedFn as unknown as T;
+}
+
+export default useDebounce;
diff --git a/src/hooks/usePrevious.ts b/src/hooks/usePrevious.ts
new file mode 100644
index 0000000..1fec16f
--- /dev/null
+++ b/src/hooks/usePrevious.ts
@@ -0,0 +1,19 @@
+import { useEffect, useRef } from "react";
+
+/**
+ * The usePrevious function is a custom hook that returns the previous value of a variable.
+ * It takes in a value as a parameter and returns the previous value.
+ */
+function usePrevious(value: T): T | undefined {
+ const ref = useRef();
+
+ // Store current value in ref
+ useEffect(() => {
+ ref.current = value;
+ }, [value]);
+
+ // Return previous value (happens before update in useEffect above)
+ return ref.current;
+}
+
+export default usePrevious;
diff --git a/src/hooks/useTimeout.ts b/src/hooks/useTimeout.ts
new file mode 100644
index 0000000..bb2ffd2
--- /dev/null
+++ b/src/hooks/useTimeout.ts
@@ -0,0 +1,30 @@
+import { useEffect, useRef } from "react";
+
+/**
+ * The useTimeout function is a custom hook that sets a timeout for a given callback function.
+ * It takes in a callback function and a delay time in milliseconds as parameters.
+ * It returns nothing.
+ */
+function useTimeout(callback: () => void, delay: number) {
+ const callbackRef = useRef(callback);
+
+ useEffect(() => {
+ callbackRef.current = callback;
+ }, [callback]);
+
+ useEffect(() => {
+ let timer: NodeJS.Timeout | undefined;
+
+ if (delay !== null && callback && typeof callback === "function") {
+ timer = setTimeout(callbackRef.current, delay);
+ }
+
+ return () => {
+ if (timer) {
+ clearTimeout(timer);
+ }
+ };
+ }, [callback, delay]);
+}
+
+export default useTimeout;
diff --git a/src/hooks/useUpdateEffect.ts b/src/hooks/useUpdateEffect.ts
new file mode 100644
index 0000000..d172f8a
--- /dev/null
+++ b/src/hooks/useUpdateEffect.ts
@@ -0,0 +1,19 @@
+import { DependencyList, EffectCallback, useEffect, useRef } from "react";
+
+/**
+ * The useUpdateEffect function is a custom hook that behaves like useEffect, but only runs on updates and not on initial mount.
+ * It takes in an effect function and an optional dependency list as parameters.
+ * It returns nothing.
+ */
+const useUpdateEffect = (effect: EffectCallback, deps?: DependencyList) => {
+ const isInitialMount = useRef(true);
+
+ useEffect(() => {
+ if (isInitialMount.current) {
+ isInitialMount.current = false;
+ }
+ return effect();
+ }, deps);
+};
+
+export default useUpdateEffect;
diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx
index b3fdbfb..07f74c4 100644
--- a/src/pages/home/index.tsx
+++ b/src/pages/home/index.tsx
@@ -1,21 +1,25 @@
-import {Box, Grid, Icon, Input, Stack, TextField} from "@mui/material";
+import {Box, Icon, Input, Stack, TextField} from "@mui/material";
+import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";
import SearchIcon from '@mui/icons-material/Search';
+import {useNavigate} from "react-router-dom";
+const exampleTools: { label: string; url: string }[] = [{
+ label: 'Create a transparent image',
+ url: ''
+},
+ {label: 'Convert text to morse code', url: ''},
+ {label: 'Change GIF speed', url: ''},
+ {label: 'Pick a random item', url: ''},
+ {label: 'Find and replace text', url: ''},
+ {label: 'Convert emoji to image', url: ''},
+ {label: 'Split a string', url: '/string/split'},
+ {label: 'Calculate number sum', url: ''},
+ {label: 'Pixelate an image', url: ''},
+]
export default function Home() {
- const exampleTools: { label: string; url: string }[] = [{
- label: 'Create a transparent image',
- url: ''
- },
- {label: 'Convert text to morse code', url: ''},
- {label: 'Change GIF speed', url: ''},
- {label: 'Pick a random item', url: ''},
- {label: 'Find and replace text', url: ''},
- {label: 'Convert emoji to image', url: ''},
- {label: 'Split a string', url: ''},
- {label: 'Calculate number sum', url: ''},
- {label: 'Pixelate an image', url: ''},
- ]
+ const navigate = useNavigate()
+
return (
@@ -38,6 +42,7 @@ export default function Home() {
{exampleTools.map((tool) => (
navigate(tool.url)}
item
xs={4}
key={tool.label}
diff --git a/src/pages/string/StringConfig.tsx b/src/pages/string/StringConfig.tsx
new file mode 100644
index 0000000..dc70628
--- /dev/null
+++ b/src/pages/string/StringConfig.tsx
@@ -0,0 +1,10 @@
+import {RouteObject} from "react-router-dom";
+import {lazy} from "react";
+
+const StringHome = lazy(() => import("./index"));
+const StringSplit = lazy(() => import("./split"));
+
+export const StringConfig: RouteObject[] = [
+ {path: '', element: },
+ {path: 'split', element: },
+]
diff --git a/src/pages/string/index.tsx b/src/pages/string/index.tsx
new file mode 100644
index 0000000..bf9ff71
--- /dev/null
+++ b/src/pages/string/index.tsx
@@ -0,0 +1,5 @@
+import {Box} from "@mui/material";
+
+export default function StringHome() {
+ return ()
+}
diff --git a/src/pages/string/split/index.tsx b/src/pages/string/split/index.tsx
new file mode 100644
index 0000000..4b590a2
--- /dev/null
+++ b/src/pages/string/split/index.tsx
@@ -0,0 +1,45 @@
+import ToolHeader from "../../../components/ToolHeader";
+import ToolLayout from "../../../components/ToolLayout";
+import {Box, Stack, TextField} from "@mui/material";
+import Grid from "@mui/material/Grid";
+import Typography from "@mui/material/Typography";
+import Button from "@mui/material/Button";
+import PublishIcon from '@mui/icons-material/Publish';
+import ContentPasteIcon from '@mui/icons-material/ContentPaste';
+import DownloadIcon from '@mui/icons-material/Download';
+import React, {useEffect, useState} from "react";
+
+export default function SplitText() {
+ const [input, setInput] = useState('');
+ const [result, setResult] = useState('')
+ useEffect(() => {
+ setResult(input.split(' ').join('\n'))
+ }, [input]);
+ return (
+
+
+
+
+
+ Input text
+ setInput(event.target.value)} fullWidth multiline rows={10}/>
+
+
+
+
+
+
+ Text pieces
+
+
+
+
+
+
+
+
+
+
+ )
+}