Responsive Sidenav
This commit is contained in:
parent
887d3f6841
commit
16252ee5a2
12 changed files with 216 additions and 48 deletions
15
app/(auth)/sign-in/page.tsx
Normal file
15
app/(auth)/sign-in/page.tsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import AppLink from '@/ui/AppLink';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen w-full">
|
||||||
|
<div className="m-auto flex flex-col">
|
||||||
|
<h1 className="text-center text-4xl font-bold">
|
||||||
|
Amuse toi avec <span className="rounded-md bg-white p-1 text-black ">Next.js</span> et{' '}
|
||||||
|
<span className="text-blue-500">Tailwindcss</span> !
|
||||||
|
</h1>
|
||||||
|
<AppLink href="/dashboard">Dashboard</AppLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
15
app/(auth)/sign-up/page.tsx
Normal file
15
app/(auth)/sign-up/page.tsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import AppLink from '@/ui/AppLink';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen w-full">
|
||||||
|
<div className="m-auto flex flex-col">
|
||||||
|
<h1 className="text-center text-4xl font-bold">
|
||||||
|
Amuse toi avec <span className="rounded-md bg-white p-1 text-black ">Next.js</span> et{' '}
|
||||||
|
<span className="text-blue-500">Tailwindcss</span> !
|
||||||
|
</h1>
|
||||||
|
<AppLink href="/dashboard">Dashboard</AppLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,11 +1,6 @@
|
||||||
import Sidenav from '@/ui/dashboard/Sidenav';
|
import Wrapper from '@/ui/dashboard/Wrapper';
|
||||||
import { type ReactNode } from 'react';
|
import { type ReactNode } from 'react';
|
||||||
|
|
||||||
export default function Layout({ children }: { children: ReactNode }) {
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
return (
|
return <Wrapper>{children}</Wrapper>;
|
||||||
<div className="mx-4 min-h-screen md:pl-60 xl:mx-0">
|
|
||||||
<Sidenav />
|
|
||||||
<div className="mx-auto h-screen max-w-5xl py-8">{children}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default function Page() {
|
||||||
label="Code"
|
label="Code"
|
||||||
name="code_file"
|
name="code_file"
|
||||||
type="file"
|
type="file"
|
||||||
accept=".py,.js,.ts,.java,.rust"
|
accept=".py,.js,.ts,.java,.rust,.c"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button className="mt-6" type="submit">
|
<Button className="mt-6" type="submit">
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import '@/styles/globals.css';
|
import '@/styles/globals.css';
|
||||||
|
import 'remixicon/fonts/remixicon.css';
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { type ReactNode } from 'react';
|
import { type ReactNode } from 'react';
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
export type NavItem = {
|
export type NavItem = {
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
icon: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,26 +20,31 @@ export const navItems: NavItem[] = [
|
||||||
{
|
{
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
slug: '',
|
slug: '',
|
||||||
|
icon: 'dashboard-line',
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Classement',
|
name: 'Classement',
|
||||||
slug: 'ranking',
|
slug: 'ranking',
|
||||||
|
icon: 'line-chart-line',
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Puzzles',
|
name: 'Puzzles',
|
||||||
slug: 'puzzles',
|
slug: 'puzzles',
|
||||||
|
icon: 'code-s-slash-line',
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Badges',
|
name: 'Badges',
|
||||||
slug: 'badges',
|
slug: 'badges',
|
||||||
|
icon: 'award-fill',
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Paramètres',
|
name: 'Paramètres',
|
||||||
slug: 'settings',
|
slug: 'settings',
|
||||||
|
icon: 'equalizer-line',
|
||||||
disabled: false
|
disabled: false
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
|
reactStrictMode: true, // Recommended for the `pages` directory, default in `app`.
|
||||||
|
swcMinify: true,
|
||||||
experimental: {
|
experimental: {
|
||||||
appDir: true
|
appDir: true
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbo",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
|
@ -22,11 +22,11 @@
|
||||||
"homepage": "https://github.com/Peer-at-Code/peer-at-code#readme",
|
"homepage": "https://github.com/Peer-at-Code/peer-at-code#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/font": "13.1.6",
|
"@next/font": "13.1.6",
|
||||||
"@tabler/icons": "^2.2.0",
|
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"next": "13.1.6",
|
"next": "13.1.6",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"remixicon": "^2.5.0",
|
||||||
"swr": "^2.0.3",
|
"swr": "^2.0.3",
|
||||||
"tailwind-merge": "^1.9.0",
|
"tailwind-merge": "^1.9.0",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2"
|
||||||
|
|
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
|
@ -2,7 +2,6 @@ lockfileVersion: 5.4
|
||||||
|
|
||||||
specifiers:
|
specifiers:
|
||||||
'@next/font': 13.1.6
|
'@next/font': 13.1.6
|
||||||
'@tabler/icons': ^2.2.0
|
|
||||||
'@tailwindcss/forms': ^0.5.3
|
'@tailwindcss/forms': ^0.5.3
|
||||||
'@types/node': 18.11.18
|
'@types/node': 18.11.18
|
||||||
'@types/react': 18.0.27
|
'@types/react': 18.0.27
|
||||||
|
@ -21,6 +20,7 @@ specifiers:
|
||||||
prettier-plugin-tailwindcss: ^0.2.2
|
prettier-plugin-tailwindcss: ^0.2.2
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0
|
react-dom: 18.2.0
|
||||||
|
remixicon: ^2.5.0
|
||||||
swr: ^2.0.3
|
swr: ^2.0.3
|
||||||
tailwind-merge: ^1.9.0
|
tailwind-merge: ^1.9.0
|
||||||
tailwindcss: ^3.2.4
|
tailwindcss: ^3.2.4
|
||||||
|
@ -29,11 +29,11 @@ specifiers:
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/font': 13.1.6
|
'@next/font': 13.1.6
|
||||||
'@tabler/icons': 2.2.0
|
|
||||||
clsx: 1.2.1
|
clsx: 1.2.1
|
||||||
next: 13.1.6_biqbaboplfbrettd7655fr4n2y
|
next: 13.1.6_biqbaboplfbrettd7655fr4n2y
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
remixicon: 2.5.0
|
||||||
swr: 2.0.3_react@18.2.0
|
swr: 2.0.3_react@18.2.0
|
||||||
tailwind-merge: 1.9.0
|
tailwind-merge: 1.9.0
|
||||||
zod: 3.20.2
|
zod: 3.20.2
|
||||||
|
@ -276,10 +276,6 @@ packages:
|
||||||
tslib: 2.5.0
|
tslib: 2.5.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@tabler/icons/2.2.0:
|
|
||||||
resolution: {integrity: sha512-s2mm+7JqmLObKdU89Dtiy+USmUpOlACsoXZZPykjAJZC4pK3wMYxLsclJxViWLeLTb6Bc0oga92V7R+9nrj1ZQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@tailwindcss/forms/0.5.3_tailwindcss@3.2.4:
|
/@tailwindcss/forms/0.5.3_tailwindcss@3.2.4:
|
||||||
resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==}
|
resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -2314,6 +2310,10 @@ packages:
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/remixicon/2.5.0:
|
||||||
|
resolution: {integrity: sha512-q54ra2QutYDZpuSnFjmeagmEiN9IMo56/zz5dDNitzKD23oFRw77cWo4TsrAdmdkPiEn8mxlrTqxnkujDbEGww==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/resolve-from/4.0.0:
|
/resolve-from/4.0.0:
|
||||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
5
ui/Icon.tsx
Normal file
5
ui/Icon.tsx
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
export default function Icon({ name, className }: { name: string; className?: string }) {
|
||||||
|
return <i className={cn(`ri-${name}`, className)} role="img" />;
|
||||||
|
}
|
|
@ -1,53 +1,139 @@
|
||||||
'use client';
|
|
||||||
|
|
||||||
import { NavItem, navItems } from '@/lib/nav-items';
|
import { NavItem, navItems } from '@/lib/nav-items';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useSelectedLayoutSegment } from 'next/navigation';
|
import { useSelectedLayoutSegment } from 'next/navigation';
|
||||||
import AppLink from '../AppLink';
|
import AppLink from '../AppLink';
|
||||||
|
import Icon from '../Icon';
|
||||||
|
|
||||||
export default function Sidenav() {
|
export default function Sidenav({
|
||||||
|
onClick,
|
||||||
|
width,
|
||||||
|
isOpen
|
||||||
|
}: {
|
||||||
|
onClick: () => void;
|
||||||
|
width: number;
|
||||||
|
isOpen: boolean;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="fixed top-0 left-0 hidden h-full w-60 space-y-4 bg-dark shadow-md md:block">
|
(width >= 640 && (
|
||||||
<div className="px-6 pt-4">
|
<aside
|
||||||
<div className="flex items-center">
|
className={cn(
|
||||||
<div className="mx-auto">
|
'hidden fixed top-0 left-0 z-10 h-full space-y-4 bg-dark shadow-md transition-all duration-300 ease-in-out sm:block',
|
||||||
<AppLink href="/">
|
isOpen ? 'w-60' : 'w-24 '
|
||||||
<h1 className="text-2xl font-bold">Peer-at Code</h1>
|
)}
|
||||||
</AppLink>
|
>
|
||||||
</div>
|
<div className="px-8 pt-4">
|
||||||
</div>
|
<div className="mx-auto items-center">
|
||||||
</div>
|
<button onClick={onClick} className="flex h-10 w-full items-center space-x-2">
|
||||||
<hr className="mx-auto w-1/2 pb-4" />
|
<Icon
|
||||||
<ul className="relative flex flex-col space-y-4 px-8">
|
className={cn(
|
||||||
{navItems.map((item) => (
|
'text-xl transition-transform duration-300 ease-in-out',
|
||||||
<li className="relative" key={item.slug}>
|
isOpen ? 'rotate-180' : 'rotate-0'
|
||||||
<NavItem item={item} />
|
)}
|
||||||
</li>
|
name="arrow-right-s-line"
|
||||||
))}
|
/>
|
||||||
</ul>
|
<span
|
||||||
<div className="absolute bottom-5 flex w-full items-center text-center">
|
className={cn(
|
||||||
<button className="mx-auto rounded-md bg-light-dark py-3 px-8 hover:bg-light-dark/60">
|
'text-xl font-semibold transition',
|
||||||
<span className="text-sm font-semibold">Session ici plus tard</span>
|
isOpen ? 'duration-400 opacity-100 delay-150' : 'opacity-0'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Peer-at-Code
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr className="mx-auto w-1/2 pb-4" />
|
||||||
|
<ul
|
||||||
|
className={cn(
|
||||||
|
'relative flex flex-col space-y-4 transition-all duration-300 ease-in-out',
|
||||||
|
isOpen ? ' px-8' : 'px-4'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<li className="relative" key={item.slug}>
|
||||||
|
<NavItem item={item} isOpen={isOpen} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'absolute bottom-5 flex w-full items-center text-center',
|
||||||
|
isOpen ? 'px-8' : 'px-4'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'mx-auto w-full truncate rounded-md bg-light-dark py-3 transition-all duration-300 ease-in-out hover:bg-light-dark/60'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="text-sm font-semibold">Session ici</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
)) || (
|
||||||
|
<aside
|
||||||
|
className={cn(
|
||||||
|
'fixed top-0 z-10 w-full bg-dark shadow-md transition-all duration-300 ease-in-out sm:block md:hidden'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-end px-4 py-4">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<button onClick={onClick} className="flex h-10 w-full items-center space-x-2">
|
||||||
|
<span className={cn('text-xl font-semibold')}>Peer-at-Code</span>
|
||||||
|
<Icon
|
||||||
|
className={cn(
|
||||||
|
'text-xl transition-transform duration-300 ease-in-out',
|
||||||
|
isOpen ? 'rotate-180' : 'rotate-0'
|
||||||
|
)}
|
||||||
|
name="arrow-up-s-line"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex h-screen transform flex-col duration-100',
|
||||||
|
isOpen ? 'block' : 'hidden'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
className={cn(
|
||||||
|
'relative flex flex-col space-y-4 px-8 transition-all duration-300 ease-in-out'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<li className="relative" key={item.slug} onClick={onClick}>
|
||||||
|
<NavItem item={item} isOpen={isOpen} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NavItem({ item }: { item: NavItem }) {
|
function NavItem({ item, isOpen }: { item: NavItem; isOpen: boolean }) {
|
||||||
const segment = useSelectedLayoutSegment();
|
const segment = useSelectedLayoutSegment();
|
||||||
const isActive = segment?.split('/').pop() === item.slug || (item.slug === '' && !segment);
|
const isActive = segment?.split('/').pop() === item.slug || (item.slug === '' && !segment);
|
||||||
return (
|
return (
|
||||||
<AppLink
|
<AppLink
|
||||||
href={item.disabled ? '/dashboard' : `/dashboard/${item.slug}`}
|
href={item.disabled ? '/dashboard' : `/dashboard/${item.slug}`}
|
||||||
className={cn('block rounded-md px-3 py-3 text-sm font-medium', {
|
className={cn(
|
||||||
|
'flex rounded-md px-3 py-3 text-sm font-medium transition duration-300 ease-in-out',
|
||||||
|
{
|
||||||
'bg-light-dark text-gray-400 hover:bg-light-dark/60': !isActive,
|
'bg-light-dark text-gray-400 hover:bg-light-dark/60': !isActive,
|
||||||
'bg-blue-500 text-white': isActive,
|
'bg-blue-500 text-white': isActive,
|
||||||
'text-gray-600 hover:text-gray-600': item.disabled
|
'text-gray-600 hover:text-gray-600': item.disabled,
|
||||||
})}
|
'justify-center': !isOpen
|
||||||
|
}
|
||||||
|
)}
|
||||||
passHref
|
passHref
|
||||||
>
|
>
|
||||||
{item.name}
|
<div className={cn('flex items-center', isOpen && 'space-x-4')}>
|
||||||
|
<Icon className="text-2xl" name={item.icon} />
|
||||||
|
<span className={cn(isOpen ? 'block' : 'hidden')}>{item.name}</span>
|
||||||
|
</div>
|
||||||
</AppLink>
|
</AppLink>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
43
ui/dashboard/Wrapper.tsx
Normal file
43
ui/dashboard/Wrapper.tsx
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { useCallback, useEffect, useState, type ReactNode } from 'react';
|
||||||
|
import Sidenav from './Sidenav';
|
||||||
|
|
||||||
|
export default function Wrapper({ children }: { children: ReactNode }) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [width, setWidth] = useState(0);
|
||||||
|
|
||||||
|
function toggleSidenav() {
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
function useWidth() {
|
||||||
|
const handleResize = useCallback(() => setWidth(window.innerWidth), []);
|
||||||
|
useEffect(() => {
|
||||||
|
handleResize();
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, [handleResize]);
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'relative min-h-screen transition-all duration-300 ease-in-out',
|
||||||
|
isOpen ? 'sm:pl-60' : 'sm:pl-24'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Sidenav onClick={toggleSidenav} width={useWidth()} isOpen={isOpen} />
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'mx-auto h-screen w-full transform px-2 pt-24 pb-8 duration-300 ease-in-out sm:px-0 sm:py-8',
|
||||||
|
isOpen ? 'md:max-w-6xl' : 'md:max-w-7xl'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue