feat(global) project init
This commit is contained in:
commit
3556eaed5e
22 files changed
+637
No files matched your search
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { IoLogoGithub } from 'react-icons/io5';
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<div className={ 'min-w-full flex justify-center' }>
|
||||
<div className={ 'w-full md:w-[80%]' }>
|
||||
<div className={ `p-5 mb-4` }>
|
||||
<div className={ `h-1 w-20 bg-zinc-500 dark:bg-zinc-400 mb-3` }></div>
|
||||
<p>
|
||||
© 2024-{ new Date().getFullYear() } Lumither Tao
|
||||
</p>
|
||||
<p>
|
||||
Powered by Next.js, built with passion and love
|
||||
</p>
|
||||
<Link
|
||||
href={ `https://github.com/Lumither/` }
|
||||
aria-label={ `repo link` }
|
||||
>
|
||||
<IoLogoGithub className={ `mt-1` } />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
@@ -0,0 +1,27 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import ThemeSwitcher from '@/Components/ThemeSwitcher';
|
||||
|
||||
const Header = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={ 'h-16 fixed backdrop-blur w-full z-50 transition-colors border-b-[1px] border-neutral-200 dark:border-neutral-700' }
|
||||
>
|
||||
|
||||
<div className={ 'p-3 px-5' }>
|
||||
<div className={ 'flex flex-row items-center justify-between align-baseline' }>
|
||||
<div className={ 'font-bold text-2xl' }>
|
||||
LMTLab
|
||||
</div>
|
||||
<ThemeSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Header;
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,26 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html.light {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
html {
|
||||
--background: #282830;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
transition: background 0.3s, color 0.3s;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import MonacoEditor from '@/app/public/MonacoEditor';
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={ 'p-2 lg:p-20 h-full w-full' }
|
||||
>
|
||||
<MonacoEditor defaultValue={ '{}' } language={ 'json' } />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Page;
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { Metadata } from 'next';
|
||||
import './globals.css';
|
||||
import React from 'react';
|
||||
import Footer from '@/app/Footer';
|
||||
import Header from '@/app/Header';
|
||||
import { ThemeProvider } from '@/app/theme-provider';
|
||||
import NextUiProvider from '@/app/nextui-provider';
|
||||
import ToastProvider from '@/app/toastify-provider';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'LMTLab',
|
||||
description: 'A little toy box'
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={ `antialiased` }
|
||||
>
|
||||
<ThemeProvider
|
||||
attribute={ 'class' }
|
||||
defaultTheme={ 'system' }
|
||||
>
|
||||
<NextUiProvider>
|
||||
<ToastProvider>
|
||||
<div className={ 'min-h-screen relative' }>
|
||||
<Header />
|
||||
<div className={ 'pt-16 h-full w-full' }>
|
||||
{ children }
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</ToastProvider>
|
||||
</NextUiProvider>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { NextUIProvider } from '@nextui-org/react';
|
||||
import React from 'react';
|
||||
|
||||
export default function NextuiProvider({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<NextUIProvider>
|
||||
{ children }
|
||||
</NextUIProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Card, CardBody } from '@nextui-org/card';
|
||||
import Link from 'next/link';
|
||||
|
||||
const urlList = [
|
||||
{
|
||||
name: 'Json Parser',
|
||||
desc: 'Parse a Json file, with formatter and syntax highlighting',
|
||||
url: '/jsonparser'
|
||||
}
|
||||
];
|
||||
|
||||
export default function Home() {
|
||||
|
||||
const listLength = urlList.length;
|
||||
const listMaxIdx = listLength - 1;
|
||||
const listIsOdd = listLength % 2 !== 0;
|
||||
|
||||
return (
|
||||
<div className={ 'flex w-full items-center justify-center' }>
|
||||
<div className={ 'p-5 md:p-16 max-w-[1536px] w-full' }>
|
||||
<div className={ 'w-full' }>
|
||||
<ul className={ 'grid grid-cols-2 gap-x-6 gap-y-3 ' }>
|
||||
{ urlList.map((entry, id) => (
|
||||
<li key={ id }
|
||||
className={ (id === listMaxIdx && listIsOdd) ? 'col-span-2 flex justify-center' : 'col-span-2 md:col-span-1' }
|
||||
>
|
||||
<Card
|
||||
isPressable
|
||||
isHoverable
|
||||
as={ Link }
|
||||
href={ entry.url }
|
||||
className={ 'w-full h-full' }
|
||||
>
|
||||
<CardBody className={ 'p-6 h-full' }>
|
||||
<div className={ 'flex flex-col h-full' }>
|
||||
<div className={ 'text-2xl font-bold' }>{ entry.name }</div>
|
||||
<div className={ '' }>{ entry.desc }</div>
|
||||
<div className={ 'relative h-full mt-8' }>
|
||||
<div
|
||||
className={ 'text-default-500 dark:text-default-500 absolute bottom-0' }>
|
||||
{ `https://lab.lmt.moe${ entry.url }` }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</li>
|
||||
)) }
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Editor as Monaco } from '@monaco-editor/react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { Button } from '@nextui-org/react';
|
||||
import { HiOutlineClipboardDocumentList } from 'react-icons/hi2';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
type Props = {
|
||||
language: string,
|
||||
defaultValue: string
|
||||
}
|
||||
|
||||
const MonacoEditor = (props: Props) => {
|
||||
const { theme } = useTheme();
|
||||
const [ editorValue, setEditorValue ] = useState(props.defaultValue);
|
||||
console.log(editorValue);
|
||||
return (
|
||||
<div className={ 'rounded-2xl overflow-hidden shadow-2xl relative' }>
|
||||
<Monaco
|
||||
height="75vh"
|
||||
defaultLanguage={ props.language }
|
||||
theme={ theme === 'light' ? 'light' : 'vs-dark' }
|
||||
defaultValue={ props.defaultValue }
|
||||
onChange={ (val) => setEditorValue(val as string) }
|
||||
/>
|
||||
<div className={ 'absolute bottom-4 right-4 z-10' }>
|
||||
<Button
|
||||
variant={ 'light' }
|
||||
isIconOnly
|
||||
onClick={ () => {
|
||||
navigator.clipboard.writeText(editorValue);
|
||||
toast.success('Copied to Clipboard');
|
||||
} }
|
||||
>
|
||||
<HiOutlineClipboardDocumentList size={ 20 } />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default MonacoEditor;
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const screenSize = new Map<string, number>(
|
||||
[
|
||||
[ 'sm', 640 ],
|
||||
[ 'md', 768 ],
|
||||
[ 'lg', 1024 ],
|
||||
[ 'xl', 1280 ],
|
||||
[ '2xl', 2536 ]
|
||||
]
|
||||
);
|
||||
|
||||
export const useScreenSizeTrigger = (size: number | string) => {
|
||||
const criticalSize = (typeof size === 'string') ? (screenSize.get(size) ?? 768) : size;
|
||||
|
||||
const [ isCritical, setIsCritical ] = useState(false);
|
||||
useEffect(() => {
|
||||
const updateMobileWidth = () => {
|
||||
if (window.innerWidth < criticalSize) {
|
||||
setIsCritical(true);
|
||||
} else {
|
||||
setIsCritical(false);
|
||||
}
|
||||
};
|
||||
updateMobileWidth();
|
||||
window.addEventListener('resize', updateMobileWidth);
|
||||
return () => {
|
||||
window.removeEventListener('resize', updateMobileWidth);
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
return isCritical;
|
||||
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const useScroll = () => {
|
||||
const [ isScrolledX, setIsScrolledX ] = useState(false);
|
||||
const [ isScrolledY, setIsScrolledY ] = useState(false);
|
||||
const [ scrollX, setScrollX ] = useState(0);
|
||||
const [ scrollY, setScrollY ] = useState(0);
|
||||
|
||||
const handleScroll = () => {
|
||||
const x = window.scrollX;
|
||||
const y = window.scrollY;
|
||||
|
||||
setScrollX(x);
|
||||
setScrollY(y);
|
||||
setIsScrolledX(x > 0);
|
||||
setIsScrolledY(y > 0);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
|
||||
// Cleanup listener on unmount
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return [ isScrolledX, isScrolledY, scrollX, scrollY ];
|
||||
};
|
||||
|
||||
export default useScroll;
|
||||
@@ -0,0 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { ThemeProvider as NextThemesProvider } from 'next-themes';
|
||||
import { type ThemeProviderProps } from 'next-themes/dist/types';
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return <NextThemesProvider { ...props }>{ children }</NextThemesProvider>;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
'use client';
|
||||
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
// import '../../app/globals.css';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import React from 'react';
|
||||
|
||||
interface ToastProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function ToastProvider({ children }: ToastProviderProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{ children }
|
||||
<ToastContainer
|
||||
position="bottom-right"
|
||||
autoClose={ 2000 }
|
||||
hideProgressBar={ false }
|
||||
newestOnTop={ false }
|
||||
closeOnClick
|
||||
rtl={ false }
|
||||
pauseOnFocusLoss
|
||||
draggable
|
||||
pauseOnHover
|
||||
theme="dark"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user