mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-02-24 04:04:43 +08:00
Initial commit from Create Next App
This commit is contained in:
8
src/__mocks__/svg.tsx
Normal file
8
src/__mocks__/svg.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React, { SVGProps } from 'react';
|
||||
|
||||
const SvgrMock = React.forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(
|
||||
(props, ref) => <svg ref={ref} {...props} />
|
||||
);
|
||||
|
||||
export const ReactComponent = SvgrMock;
|
||||
export default SvgrMock;
|
||||
15
src/__tests__/pages/HomePage.test.tsx
Normal file
15
src/__tests__/pages/HomePage.test.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
// !STARTERCONF You should delete this page
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import HomePage from '@/app/page';
|
||||
|
||||
describe('Homepage', () => {
|
||||
it('renders the Components', () => {
|
||||
render(<HomePage />);
|
||||
|
||||
const heading = screen.getByText(/A starter for Next.js/i);
|
||||
|
||||
expect(heading).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
5
src/app/api/hello/route.ts
Normal file
5
src/app/api/hello/route.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json({ hello: 'Next.js' });
|
||||
}
|
||||
17
src/app/components/layout.tsx
Normal file
17
src/app/components/layout.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Metadata } from 'next';
|
||||
import * as React from 'react';
|
||||
|
||||
import '@/styles/colors.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Components',
|
||||
description: 'Pre-built components with awesome default',
|
||||
};
|
||||
|
||||
export default function ComponentsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
461
src/app/components/page.tsx
Normal file
461
src/app/components/page.tsx
Normal file
@@ -0,0 +1,461 @@
|
||||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
ArrowRight,
|
||||
CreditCard,
|
||||
Laptop,
|
||||
Phone,
|
||||
Plus,
|
||||
Shield,
|
||||
} from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
import Button from '@/components/buttons/Button';
|
||||
import IconButton from '@/components/buttons/IconButton';
|
||||
import TextButton from '@/components/buttons/TextButton';
|
||||
import ArrowLink from '@/components/links/ArrowLink';
|
||||
import ButtonLink from '@/components/links/ButtonLink';
|
||||
import PrimaryLink from '@/components/links/PrimaryLink';
|
||||
import UnderlineLink from '@/components/links/UnderlineLink';
|
||||
import UnstyledLink from '@/components/links/UnstyledLink';
|
||||
import NextImage from '@/components/NextImage';
|
||||
import Skeleton from '@/components/Skeleton';
|
||||
|
||||
type Color = (typeof colorList)[number];
|
||||
|
||||
export default function ComponentPage() {
|
||||
const [mode, setMode] = React.useState<'dark' | 'light'>('light');
|
||||
const [color, setColor] = React.useState<Color>('sky');
|
||||
function toggleMode() {
|
||||
return mode === 'dark' ? setMode('light') : setMode('dark');
|
||||
}
|
||||
|
||||
const textColor = mode === 'dark' ? 'text-gray-300' : 'text-gray-600';
|
||||
|
||||
return (
|
||||
<main>
|
||||
<section
|
||||
className={clsx(mode === 'dark' ? 'bg-dark' : 'bg-white', color)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'layout min-h-screen py-20',
|
||||
mode === 'dark' ? 'text-white' : 'text-black'
|
||||
)}
|
||||
>
|
||||
<h1>Built-in Components</h1>
|
||||
<ArrowLink direction='left' className='mt-2' href='/'>
|
||||
Back to Home
|
||||
</ArrowLink>
|
||||
|
||||
<div className='mt-8 flex flex-wrap gap-2'>
|
||||
<Button
|
||||
onClick={toggleMode}
|
||||
variant={mode === 'dark' ? 'light' : 'dark'}
|
||||
>
|
||||
Set to {mode === 'dark' ? 'light' : 'dark'}
|
||||
</Button>
|
||||
{/* <Button onClick={randomize}>Randomize CSS Variable</Button> */}
|
||||
</div>
|
||||
|
||||
<ol className='mt-8 space-y-6'>
|
||||
<li className='space-y-2'>
|
||||
<h2 className='text-lg md:text-xl'>Customize Colors</h2>
|
||||
<p className={clsx('!mt-1 text-sm', textColor)}>
|
||||
You can change primary color to any Tailwind CSS colors. See
|
||||
globals.css to change your color.
|
||||
</p>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
<select
|
||||
name='color'
|
||||
id='color'
|
||||
value={color}
|
||||
className={clsx(
|
||||
'block max-w-xs rounded',
|
||||
mode === 'dark'
|
||||
? 'bg-dark border border-gray-600'
|
||||
: 'border-gray-300 bg-white',
|
||||
'focus:border-primary-400 focus:ring-primary-400 focus:outline-none focus:ring'
|
||||
)}
|
||||
onChange={(e) => setColor(e.target.value as Color)}
|
||||
>
|
||||
{colorList.map((c) => (
|
||||
<option key={c} value={c}>
|
||||
{c}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<ButtonLink href='https://github.com/theodorusclarence/ts-nextjs-tailwind-starter/blob/main/src/styles/colors.css'>
|
||||
Check list of colors
|
||||
</ButtonLink>
|
||||
</div>
|
||||
<div className='flex flex-wrap gap-2 text-xs font-medium'>
|
||||
<div className='bg-primary-50 flex h-10 w-10 items-center justify-center rounded text-black'>
|
||||
50
|
||||
</div>
|
||||
<div className='bg-primary-100 flex h-10 w-10 items-center justify-center rounded text-black'>
|
||||
100
|
||||
</div>
|
||||
<div className='bg-primary-200 flex h-10 w-10 items-center justify-center rounded text-black'>
|
||||
200
|
||||
</div>
|
||||
<div className='bg-primary-300 flex h-10 w-10 items-center justify-center rounded text-black'>
|
||||
300
|
||||
</div>
|
||||
<div className='bg-primary-400 flex h-10 w-10 items-center justify-center rounded text-black'>
|
||||
400
|
||||
</div>
|
||||
<div className='bg-primary-500 flex h-10 w-10 items-center justify-center rounded text-black'>
|
||||
500
|
||||
</div>
|
||||
<div className='bg-primary-600 flex h-10 w-10 items-center justify-center rounded text-white'>
|
||||
600
|
||||
</div>
|
||||
<div className='bg-primary-700 flex h-10 w-10 items-center justify-center rounded text-white'>
|
||||
700
|
||||
</div>
|
||||
<div className='bg-primary-800 flex h-10 w-10 items-center justify-center rounded text-white'>
|
||||
800
|
||||
</div>
|
||||
<div className='bg-primary-900 flex h-10 w-10 items-center justify-center rounded text-white'>
|
||||
900
|
||||
</div>
|
||||
<div className='bg-primary-950 flex h-10 w-10 items-center justify-center rounded text-white'>
|
||||
950
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li className='space-y-2'>
|
||||
<h2 className='text-lg md:text-xl'>UnstyledLink</h2>
|
||||
<p className={clsx('!mt-1 text-sm', textColor)}>
|
||||
No style applied, differentiate internal and outside links, give
|
||||
custom cursor for outside links.
|
||||
</p>
|
||||
<div className='space-x-2'>
|
||||
<UnstyledLink href='/'>Internal Links</UnstyledLink>
|
||||
<UnstyledLink href='https://theodorusclarence.com'>
|
||||
Outside Links
|
||||
</UnstyledLink>
|
||||
</div>
|
||||
</li>
|
||||
<li className='space-y-2'>
|
||||
<h2 className='text-lg md:text-xl'>PrimaryLink</h2>
|
||||
<p className={clsx('!mt-1 text-sm', textColor)}>
|
||||
Add styling on top of UnstyledLink, giving a primary color to
|
||||
the link.
|
||||
</p>
|
||||
<div className='space-x-2'>
|
||||
<PrimaryLink href='/'>Internal Links</PrimaryLink>
|
||||
<PrimaryLink href='https://theodorusclarence.com'>
|
||||
Outside Links
|
||||
</PrimaryLink>
|
||||
</div>
|
||||
</li>
|
||||
<li className='space-y-2'>
|
||||
<h2 className='text-lg md:text-xl'>UnderlineLink</h2>
|
||||
<p className={clsx('!mt-1 text-sm', textColor)}>
|
||||
Add styling on top of UnstyledLink, giving a dotted and animated
|
||||
underline.
|
||||
</p>
|
||||
<div className='space-x-2'>
|
||||
<UnderlineLink href='/'>Internal Links</UnderlineLink>
|
||||
<UnderlineLink href='https://theodorusclarence.com'>
|
||||
Outside Links
|
||||
</UnderlineLink>
|
||||
</div>
|
||||
</li>
|
||||
<li className='space-y-2'>
|
||||
<h2 className='text-lg md:text-xl'>ArrowLink</h2>
|
||||
<p className={clsx('!mt-1 text-sm', textColor)}>
|
||||
Useful for indicating navigation, I use this quite a lot, so why
|
||||
not build a component with some whimsy touch?
|
||||
</p>
|
||||
<div className='flex flex-wrap items-center gap-4'>
|
||||
<ArrowLink href='/' direction='left'>
|
||||
Direction Left
|
||||
</ArrowLink>
|
||||
<ArrowLink href='/'>Direction Right</ArrowLink>
|
||||
<ArrowLink
|
||||
as={UnstyledLink}
|
||||
className='inline-flex items-center'
|
||||
href='/'
|
||||
>
|
||||
Polymorphic
|
||||
</ArrowLink>
|
||||
<ArrowLink
|
||||
as={ButtonLink}
|
||||
variant='light'
|
||||
className='inline-flex items-center'
|
||||
href='/'
|
||||
>
|
||||
Polymorphic
|
||||
</ArrowLink>
|
||||
</div>
|
||||
</li>
|
||||
<li className='space-y-2'>
|
||||
<h2 className='text-lg md:text-xl'>ButtonLink</h2>
|
||||
<p className={clsx('!mt-1 text-sm', textColor)}>
|
||||
Button styled link with 3 variants.
|
||||
</p>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
<ButtonLink
|
||||
variant='primary'
|
||||
href='https://theodorusclarence.com'
|
||||
>
|
||||
Primary Variant
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
variant='outline'
|
||||
isDarkBg={mode === 'dark'}
|
||||
href='https://theodorusclarence.com'
|
||||
>
|
||||
Outline Variant
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
variant='ghost'
|
||||
isDarkBg={mode === 'dark'}
|
||||
href='https://theodorusclarence.com'
|
||||
>
|
||||
Ghost Variant
|
||||
</ButtonLink>
|
||||
<ButtonLink variant='dark' href='https://theodorusclarence.com'>
|
||||
Dark Variant
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
variant='light'
|
||||
href='https://theodorusclarence.com'
|
||||
>
|
||||
Light Variant
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</li>
|
||||
<li className='space-y-2'>
|
||||
<h2 className='text-lg md:text-xl'>Button</h2>
|
||||
<p className={clsx('!mt-1 text-sm', textColor)}>
|
||||
Ordinary button with style.
|
||||
</p>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
<Button variant='primary'>Primary Variant</Button>
|
||||
<Button variant='outline' isDarkBg={mode === 'dark'}>
|
||||
Outline Variant
|
||||
</Button>
|
||||
<Button variant='ghost' isDarkBg={mode === 'dark'}>
|
||||
Ghost Variant
|
||||
</Button>
|
||||
<Button variant='dark'>Dark Variant</Button>
|
||||
<Button variant='light'>Light Variant</Button>
|
||||
</div>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
<Button
|
||||
variant='primary'
|
||||
leftIcon={Plus}
|
||||
rightIcon={ArrowRight}
|
||||
>
|
||||
Icon
|
||||
</Button>
|
||||
<Button
|
||||
variant='outline'
|
||||
leftIcon={Plus}
|
||||
rightIcon={ArrowRight}
|
||||
isDarkBg={mode === 'dark'}
|
||||
>
|
||||
Icon
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
leftIcon={Plus}
|
||||
rightIcon={ArrowRight}
|
||||
isDarkBg={mode === 'dark'}
|
||||
>
|
||||
Icon
|
||||
</Button>
|
||||
<Button variant='dark' leftIcon={Plus} rightIcon={ArrowRight}>
|
||||
Icon
|
||||
</Button>
|
||||
<Button variant='light' leftIcon={Plus} rightIcon={ArrowRight}>
|
||||
Icon
|
||||
</Button>
|
||||
</div>
|
||||
<div className='!mt-4 flex flex-wrap gap-2'>
|
||||
<Button size='sm' variant='primary'>
|
||||
Small Size
|
||||
</Button>
|
||||
<Button size='sm' variant='outline' isDarkBg={mode === 'dark'}>
|
||||
Small Size
|
||||
</Button>
|
||||
<Button size='sm' variant='ghost' isDarkBg={mode === 'dark'}>
|
||||
Small Size
|
||||
</Button>
|
||||
<Button size='sm' variant='dark'>
|
||||
Small Size
|
||||
</Button>
|
||||
<Button size='sm' variant='light'>
|
||||
Small Size
|
||||
</Button>
|
||||
</div>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='primary'
|
||||
leftIcon={Plus}
|
||||
rightIcon={ArrowRight}
|
||||
>
|
||||
Icon
|
||||
</Button>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='outline'
|
||||
leftIcon={Plus}
|
||||
rightIcon={ArrowRight}
|
||||
isDarkBg={mode === 'dark'}
|
||||
>
|
||||
Icon
|
||||
</Button>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='ghost'
|
||||
leftIcon={Plus}
|
||||
rightIcon={ArrowRight}
|
||||
isDarkBg={mode === 'dark'}
|
||||
>
|
||||
Icon
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
size='sm'
|
||||
variant='dark'
|
||||
leftIcon={Plus}
|
||||
rightIcon={ArrowRight}
|
||||
>
|
||||
Icon
|
||||
</Button>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='light'
|
||||
leftIcon={Plus}
|
||||
rightIcon={ArrowRight}
|
||||
>
|
||||
Icon
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='!mt-4 flex flex-wrap gap-2'>
|
||||
<Button disabled variant='primary'>
|
||||
Disabled
|
||||
</Button>
|
||||
<Button disabled variant='outline' isDarkBg={mode === 'dark'}>
|
||||
Disabled
|
||||
</Button>
|
||||
<Button disabled variant='ghost' isDarkBg={mode === 'dark'}>
|
||||
Disabled
|
||||
</Button>
|
||||
<Button disabled variant='dark'>
|
||||
Disabled
|
||||
</Button>
|
||||
<Button disabled variant='light'>
|
||||
Disabled
|
||||
</Button>
|
||||
</div>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
<Button isLoading variant='primary'>
|
||||
Disabled
|
||||
</Button>
|
||||
<Button isLoading variant='outline' isDarkBg={mode === 'dark'}>
|
||||
Disabled
|
||||
</Button>
|
||||
<Button isLoading variant='ghost' isDarkBg={mode === 'dark'}>
|
||||
Disabled
|
||||
</Button>
|
||||
<Button isLoading variant='dark'>
|
||||
Disabled
|
||||
</Button>
|
||||
<Button isLoading variant='light'>
|
||||
Disabled
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
<li className='space-y-2'>
|
||||
<h2 className='text-lg md:text-xl'>TextButton</h2>
|
||||
<p className={clsx('!mt-1 text-sm', textColor)}>
|
||||
Button with a text style
|
||||
</p>
|
||||
<div className='space-x-2'>
|
||||
<TextButton>Primary Variant</TextButton>
|
||||
<TextButton variant='basic'>Basic Variant</TextButton>
|
||||
</div>
|
||||
</li>
|
||||
<li className='space-y-2'>
|
||||
<h2 className='text-lg md:text-xl'>IconButton</h2>
|
||||
<p className={clsx('!mt-1 text-sm', textColor)}>
|
||||
Button with only icon inside
|
||||
</p>
|
||||
<div className='space-x-2'>
|
||||
<IconButton icon={Plus} />
|
||||
<IconButton variant='outline' icon={Laptop} />
|
||||
<IconButton variant='ghost' icon={Phone} />
|
||||
<IconButton variant='dark' icon={Shield} />
|
||||
<IconButton variant='light' icon={CreditCard} />
|
||||
</div>
|
||||
</li>
|
||||
<li className='space-y-2'>
|
||||
<h2 className='text-lg md:text-xl'>Custom 404 Page</h2>
|
||||
<p className={clsx('!mt-1 text-sm', textColor)}>
|
||||
Styled 404 page with some animation.
|
||||
</p>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
<ButtonLink href='/404'>Visit the 404 page</ButtonLink>
|
||||
</div>
|
||||
</li>
|
||||
<li className='space-y-2'>
|
||||
<h2 className='text-lg md:text-xl'>Next Image</h2>
|
||||
<p className={clsx('!mt-1 text-sm', textColor)}>
|
||||
Next Image with default props and skeleton animation
|
||||
</p>
|
||||
<NextImage
|
||||
useSkeleton
|
||||
className='w-32 md:w-40'
|
||||
src='/favicon/android-chrome-192x192.png'
|
||||
width='180'
|
||||
height='180'
|
||||
alt='Icon'
|
||||
/>
|
||||
</li>
|
||||
<li className='space-y-2'>
|
||||
<h2 className='text-lg md:text-xl'>Skeleton</h2>
|
||||
<p className={clsx('!mt-1 text-sm', textColor)}>
|
||||
Skeleton with shimmer effect
|
||||
</p>
|
||||
<Skeleton className='h-72 w-72' />
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
const colorList = [
|
||||
'slate',
|
||||
'gray',
|
||||
'zinc',
|
||||
'neutral',
|
||||
'stone',
|
||||
'red',
|
||||
'orange',
|
||||
'amber',
|
||||
'yellow',
|
||||
'lime',
|
||||
'green',
|
||||
'emerald',
|
||||
'teal',
|
||||
'cyan',
|
||||
'sky',
|
||||
'blue',
|
||||
'indigo',
|
||||
'violet',
|
||||
'purple',
|
||||
'fuchsia',
|
||||
'pink',
|
||||
'rose',
|
||||
] as const;
|
||||
38
src/app/error.tsx
Normal file
38
src/app/error.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
'use client'; // Error components must be Client Components
|
||||
|
||||
import * as React from 'react';
|
||||
import { RiAlarmWarningFill } from 'react-icons/ri';
|
||||
|
||||
import TextButton from '@/components/buttons/TextButton';
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
React.useEffect(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<main>
|
||||
<section className='bg-white'>
|
||||
<div className='layout flex min-h-screen flex-col items-center justify-center text-center text-black'>
|
||||
<RiAlarmWarningFill
|
||||
size={60}
|
||||
className='drop-shadow-glow animate-flicker text-red-500'
|
||||
/>
|
||||
<h1 className='mt-8 text-4xl md:text-6xl'>
|
||||
Oops, something went wrong!
|
||||
</h1>
|
||||
<TextButton variant='basic' onClick={reset} className='mt-4'>
|
||||
Try again
|
||||
</TextButton>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
62
src/app/layout.tsx
Normal file
62
src/app/layout.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Metadata } from 'next';
|
||||
import * as React from 'react';
|
||||
|
||||
import '@/styles/globals.css';
|
||||
// !STARTERCONF This is for demo purposes, remove @/styles/colors.css import immediately
|
||||
import '@/styles/colors.css';
|
||||
|
||||
import { siteConfig } from '@/constant/config';
|
||||
|
||||
// !STARTERCONF Change these default meta
|
||||
// !STARTERCONF Look at @/constant/config to change them
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(siteConfig.url),
|
||||
title: {
|
||||
default: siteConfig.title,
|
||||
template: `%s | ${siteConfig.title}`,
|
||||
},
|
||||
description: siteConfig.description,
|
||||
robots: { index: true, follow: true },
|
||||
// !STARTERCONF this is the default favicon, you can generate your own from https://realfavicongenerator.net/
|
||||
// ! copy to /favicon folder
|
||||
icons: {
|
||||
icon: '/favicon/favicon.ico',
|
||||
shortcut: '/favicon/favicon-16x16.png',
|
||||
apple: '/favicon/apple-touch-icon.png',
|
||||
},
|
||||
manifest: `/favicon/site.webmanifest`,
|
||||
openGraph: {
|
||||
url: siteConfig.url,
|
||||
title: siteConfig.title,
|
||||
description: siteConfig.description,
|
||||
siteName: siteConfig.title,
|
||||
images: [`${siteConfig.url}/images/og.jpg`],
|
||||
type: 'website',
|
||||
locale: 'en_US',
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: siteConfig.title,
|
||||
description: siteConfig.description,
|
||||
images: [`${siteConfig.url}/images/og.jpg`],
|
||||
// creator: '@th_clarence',
|
||||
},
|
||||
// authors: [
|
||||
// {
|
||||
// name: 'Theodorus Clarence',
|
||||
// url: 'https://theodorusclarence.com',
|
||||
// },
|
||||
// ],
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
24
src/app/not-found.tsx
Normal file
24
src/app/not-found.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Metadata } from 'next';
|
||||
import * as React from 'react';
|
||||
import { RiAlarmWarningFill } from 'react-icons/ri';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Not Found',
|
||||
};
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<main>
|
||||
<section className='bg-white'>
|
||||
<div className='layout flex min-h-screen flex-col items-center justify-center text-center text-black'>
|
||||
<RiAlarmWarningFill
|
||||
size={60}
|
||||
className='drop-shadow-glow animate-flicker text-red-500'
|
||||
/>
|
||||
<h1 className='mt-8 text-4xl md:text-6xl'>Page Not Found</h1>
|
||||
<a href='/'>Back to home</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
72
src/app/page.tsx
Normal file
72
src/app/page.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
'use client';
|
||||
|
||||
import Head from 'next/head';
|
||||
import * as React from 'react';
|
||||
import '@/lib/env';
|
||||
|
||||
import ArrowLink from '@/components/links/ArrowLink';
|
||||
import ButtonLink from '@/components/links/ButtonLink';
|
||||
import UnderlineLink from '@/components/links/UnderlineLink';
|
||||
import UnstyledLink from '@/components/links/UnstyledLink';
|
||||
|
||||
/**
|
||||
* SVGR Support
|
||||
* Caveat: No React Props Type.
|
||||
*
|
||||
* You can override the next-env if the type is important to you
|
||||
* @see https://stackoverflow.com/questions/68103844/how-to-override-next-js-svg-module-declaration
|
||||
*/
|
||||
import Logo from '~/svg/Logo.svg';
|
||||
|
||||
// !STARTERCONF -> Select !STARTERCONF and CMD + SHIFT + F
|
||||
// Before you begin editing, follow all comments with `STARTERCONF`,
|
||||
// to customize the default configuration.
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<main>
|
||||
<Head>
|
||||
<title>Hi</title>
|
||||
</Head>
|
||||
<section className='bg-white'>
|
||||
<div className='layout relative flex min-h-screen flex-col items-center justify-center py-12 text-center'>
|
||||
<Logo className='w-16' />
|
||||
<h1 className='mt-4'>Next.js + Tailwind CSS + TypeScript Starter</h1>
|
||||
<p className='mt-2 text-sm text-gray-800'>
|
||||
A starter for Next.js, Tailwind CSS, and TypeScript with Absolute
|
||||
Import, Seo, Link component, pre-configured with Husky{' '}
|
||||
</p>
|
||||
<p className='mt-2 text-sm text-gray-700'>
|
||||
<ArrowLink href='https://github.com/theodorusclarence/ts-nextjs-tailwind-starter'>
|
||||
See the repository
|
||||
</ArrowLink>
|
||||
</p>
|
||||
|
||||
<ButtonLink className='mt-6' href='/components' variant='light'>
|
||||
See all components
|
||||
</ButtonLink>
|
||||
|
||||
<UnstyledLink
|
||||
href='https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Ftheodorusclarence%2Fts-nextjs-tailwind-starter'
|
||||
className='mt-4'
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
width='92'
|
||||
height='32'
|
||||
src='https://vercel.com/button'
|
||||
alt='Deploy with Vercel'
|
||||
/>
|
||||
</UnstyledLink>
|
||||
|
||||
<footer className='absolute bottom-2 text-gray-700'>
|
||||
© {new Date().getFullYear()} By{' '}
|
||||
<UnderlineLink href='https://theodorusclarence.com?ref=tsnextstarter'>
|
||||
Theodorus Clarence
|
||||
</UnderlineLink>
|
||||
</footer>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
58
src/components/NextImage.tsx
Normal file
58
src/components/NextImage.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import Image, { ImageProps } from 'next/image';
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
type NextImageProps = {
|
||||
useSkeleton?: boolean;
|
||||
classNames?: {
|
||||
image?: string;
|
||||
blur?: string;
|
||||
};
|
||||
alt: string;
|
||||
} & (
|
||||
| { width: string | number; height: string | number }
|
||||
| { layout: 'fill'; width?: string | number; height?: string | number }
|
||||
) &
|
||||
ImageProps;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Must set width using `w-` className
|
||||
* @param useSkeleton add background with pulse animation, don't use it if image is transparent
|
||||
*/
|
||||
export default function NextImage({
|
||||
useSkeleton = false,
|
||||
src,
|
||||
width,
|
||||
height,
|
||||
alt,
|
||||
className,
|
||||
classNames,
|
||||
...rest
|
||||
}: NextImageProps) {
|
||||
const [status, setStatus] = React.useState(
|
||||
useSkeleton ? 'loading' : 'complete'
|
||||
);
|
||||
const widthIsSet = className?.includes('w-') ?? false;
|
||||
|
||||
return (
|
||||
<figure
|
||||
style={!widthIsSet ? { width: `${width}px` } : undefined}
|
||||
className={className}
|
||||
>
|
||||
<Image
|
||||
className={cn(
|
||||
classNames?.image,
|
||||
status === 'loading' && cn('animate-pulse', classNames?.blur)
|
||||
)}
|
||||
src={src}
|
||||
width={width}
|
||||
height={height}
|
||||
alt={alt}
|
||||
onLoadingComplete={() => setStatus('complete')}
|
||||
{...rest}
|
||||
/>
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
20
src/components/Skeleton.tsx
Normal file
20
src/components/Skeleton.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
type SkeletonProps = React.ComponentPropsWithoutRef<'div'>;
|
||||
|
||||
export default function Skeleton({ className, ...rest }: SkeletonProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn('animate-shimmer bg-[#f6f7f8]', className)}
|
||||
style={{
|
||||
backgroundImage:
|
||||
'linear-gradient(to right, #f6f7f8 0%, #edeef1 20%, #f6f7f8 40%, #f6f7f8 100%)',
|
||||
backgroundSize: '700px 100%',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
160
src/components/buttons/Button.tsx
Normal file
160
src/components/buttons/Button.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import * as React from 'react';
|
||||
import { IconType } from 'react-icons';
|
||||
import { ImSpinner2 } from 'react-icons/im';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const ButtonVariant = ['primary', 'outline', 'ghost', 'light', 'dark'] as const;
|
||||
const ButtonSize = ['sm', 'base'] as const;
|
||||
|
||||
type ButtonProps = {
|
||||
isLoading?: boolean;
|
||||
isDarkBg?: boolean;
|
||||
variant?: (typeof ButtonVariant)[number];
|
||||
size?: (typeof ButtonSize)[number];
|
||||
leftIcon?: IconType | LucideIcon;
|
||||
rightIcon?: IconType | LucideIcon;
|
||||
classNames?: {
|
||||
leftIcon?: string;
|
||||
rightIcon?: string;
|
||||
};
|
||||
} & React.ComponentPropsWithRef<'button'>;
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
className,
|
||||
disabled: buttonDisabled,
|
||||
isLoading,
|
||||
variant = 'primary',
|
||||
size = 'base',
|
||||
isDarkBg = false,
|
||||
leftIcon: LeftIcon,
|
||||
rightIcon: RightIcon,
|
||||
classNames,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const disabled = isLoading || buttonDisabled;
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type='button'
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'inline-flex items-center rounded font-medium',
|
||||
'focus-visible:ring-primary-500 focus:outline-none focus-visible:ring',
|
||||
'shadow-sm',
|
||||
'transition-colors duration-75',
|
||||
//#region //*=========== Size ===========
|
||||
[
|
||||
size === 'base' && ['px-3 py-1.5', 'text-sm md:text-base'],
|
||||
size === 'sm' && ['px-2 py-1', 'text-xs md:text-sm'],
|
||||
],
|
||||
//#endregion //*======== Size ===========
|
||||
//#region //*=========== Variants ===========
|
||||
[
|
||||
variant === 'primary' && [
|
||||
'bg-primary-500 text-white',
|
||||
'border-primary-600 border',
|
||||
'hover:bg-primary-600 hover:text-white',
|
||||
'active:bg-primary-700',
|
||||
'disabled:bg-primary-700',
|
||||
],
|
||||
variant === 'outline' && [
|
||||
'text-primary-500',
|
||||
'border-primary-500 border',
|
||||
'hover:bg-primary-50 active:bg-primary-100 disabled:bg-primary-100',
|
||||
isDarkBg &&
|
||||
'hover:bg-gray-900 active:bg-gray-800 disabled:bg-gray-800',
|
||||
],
|
||||
variant === 'ghost' && [
|
||||
'text-primary-500',
|
||||
'shadow-none',
|
||||
'hover:bg-primary-50 active:bg-primary-100 disabled:bg-primary-100',
|
||||
isDarkBg &&
|
||||
'hover:bg-gray-900 active:bg-gray-800 disabled:bg-gray-800',
|
||||
],
|
||||
variant === 'light' && [
|
||||
'bg-white text-gray-700',
|
||||
'border border-gray-300',
|
||||
'hover:text-dark hover:bg-gray-100',
|
||||
'active:bg-white/80 disabled:bg-gray-200',
|
||||
],
|
||||
variant === 'dark' && [
|
||||
'bg-gray-900 text-white',
|
||||
'border border-gray-600',
|
||||
'hover:bg-gray-800 active:bg-gray-700 disabled:bg-gray-700',
|
||||
],
|
||||
],
|
||||
//#endregion //*======== Variants ===========
|
||||
'disabled:cursor-not-allowed',
|
||||
isLoading &&
|
||||
'relative text-transparent transition-none hover:text-transparent disabled:cursor-wait',
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{isLoading && (
|
||||
<div
|
||||
className={cn(
|
||||
'absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2',
|
||||
{
|
||||
'text-white': ['primary', 'dark'].includes(variant),
|
||||
'text-black': ['light'].includes(variant),
|
||||
'text-primary-500': ['outline', 'ghost'].includes(variant),
|
||||
}
|
||||
)}
|
||||
>
|
||||
<ImSpinner2 className='animate-spin' />
|
||||
</div>
|
||||
)}
|
||||
{LeftIcon && (
|
||||
<div
|
||||
className={cn([
|
||||
size === 'base' && 'mr-1',
|
||||
size === 'sm' && 'mr-1.5',
|
||||
])}
|
||||
>
|
||||
<LeftIcon
|
||||
size='1em'
|
||||
className={cn(
|
||||
[
|
||||
size === 'base' && 'md:text-md text-md',
|
||||
size === 'sm' && 'md:text-md text-sm',
|
||||
],
|
||||
classNames?.leftIcon
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
{RightIcon && (
|
||||
<div
|
||||
className={cn([
|
||||
size === 'base' && 'ml-1',
|
||||
size === 'sm' && 'ml-1.5',
|
||||
])}
|
||||
>
|
||||
<RightIcon
|
||||
size='1em'
|
||||
className={cn(
|
||||
[
|
||||
size === 'base' && 'text-md md:text-md',
|
||||
size === 'sm' && 'md:text-md text-sm',
|
||||
],
|
||||
classNames?.rightIcon
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default Button;
|
||||
116
src/components/buttons/IconButton.tsx
Normal file
116
src/components/buttons/IconButton.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import * as React from 'react';
|
||||
import { IconType } from 'react-icons';
|
||||
import { ImSpinner2 } from 'react-icons/im';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const IconButtonVariant = [
|
||||
'primary',
|
||||
'outline',
|
||||
'ghost',
|
||||
'light',
|
||||
'dark',
|
||||
] as const;
|
||||
|
||||
type IconButtonProps = {
|
||||
isLoading?: boolean;
|
||||
isDarkBg?: boolean;
|
||||
variant?: (typeof IconButtonVariant)[number];
|
||||
icon?: IconType | LucideIcon;
|
||||
classNames?: {
|
||||
icon?: string;
|
||||
};
|
||||
} & React.ComponentPropsWithRef<'button'>;
|
||||
|
||||
const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
disabled: buttonDisabled,
|
||||
isLoading,
|
||||
variant = 'primary',
|
||||
isDarkBg = false,
|
||||
icon: Icon,
|
||||
classNames,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const disabled = isLoading || buttonDisabled;
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type='button'
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center rounded font-medium',
|
||||
'focus-visible:ring-primary-500 focus:outline-none focus-visible:ring',
|
||||
'shadow-sm',
|
||||
'transition-colors duration-75',
|
||||
'min-h-[28px] min-w-[28px] p-1 md:min-h-[34px] md:min-w-[34px] md:p-2',
|
||||
//#region //*=========== Variants ===========
|
||||
[
|
||||
variant === 'primary' && [
|
||||
'bg-primary-500 text-white',
|
||||
'border-primary-600 border',
|
||||
'hover:bg-primary-600 hover:text-white',
|
||||
'active:bg-primary-700',
|
||||
'disabled:bg-primary-700',
|
||||
],
|
||||
variant === 'outline' && [
|
||||
'text-primary-500',
|
||||
'border-primary-500 border',
|
||||
'hover:bg-primary-50 active:bg-primary-100 disabled:bg-primary-100',
|
||||
isDarkBg &&
|
||||
'hover:bg-gray-900 active:bg-gray-800 disabled:bg-gray-800',
|
||||
],
|
||||
variant === 'ghost' && [
|
||||
'text-primary-500',
|
||||
'shadow-none',
|
||||
'hover:bg-primary-50 active:bg-primary-100 disabled:bg-primary-100',
|
||||
isDarkBg &&
|
||||
'hover:bg-gray-900 active:bg-gray-800 disabled:bg-gray-800',
|
||||
],
|
||||
variant === 'light' && [
|
||||
'bg-white text-gray-700',
|
||||
'border border-gray-300',
|
||||
'hover:text-dark hover:bg-gray-100',
|
||||
'active:bg-white/80 disabled:bg-gray-200',
|
||||
],
|
||||
variant === 'dark' && [
|
||||
'bg-gray-900 text-white',
|
||||
'border border-gray-600',
|
||||
'hover:bg-gray-800 active:bg-gray-700 disabled:bg-gray-700',
|
||||
],
|
||||
],
|
||||
//#endregion //*======== Variants ===========
|
||||
'disabled:cursor-not-allowed',
|
||||
isLoading &&
|
||||
'relative text-transparent transition-none hover:text-transparent disabled:cursor-wait',
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{isLoading && (
|
||||
<div
|
||||
className={cn(
|
||||
'absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2',
|
||||
{
|
||||
'text-white': ['primary', 'dark'].includes(variant),
|
||||
'text-black': ['light'].includes(variant),
|
||||
'text-primary-500': ['outline', 'ghost'].includes(variant),
|
||||
}
|
||||
)}
|
||||
>
|
||||
<ImSpinner2 className='animate-spin' />
|
||||
</div>
|
||||
)}
|
||||
{Icon && <Icon size='1em' className={cn(classNames?.icon)} />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default IconButton;
|
||||
52
src/components/buttons/TextButton.tsx
Normal file
52
src/components/buttons/TextButton.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const TextButtonVariant = ['primary', 'basic'] as const;
|
||||
|
||||
type TextButtonProps = {
|
||||
variant?: (typeof TextButtonVariant)[number];
|
||||
} & React.ComponentPropsWithRef<'button'>;
|
||||
|
||||
const TextButton = React.forwardRef<HTMLButtonElement, TextButtonProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
className,
|
||||
variant = 'primary',
|
||||
disabled: buttonDisabled,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type='button'
|
||||
disabled={buttonDisabled}
|
||||
className={cn(
|
||||
'button inline-flex items-center justify-center font-semibold',
|
||||
'focus-visible:ring-primary-500 focus:outline-none focus-visible:ring',
|
||||
'transition duration-100',
|
||||
//#region //*=========== Variant ===========
|
||||
variant === 'primary' && [
|
||||
'text-primary-500 hover:text-primary-600 active:text-primary-700',
|
||||
'disabled:text-primary-200',
|
||||
],
|
||||
variant === 'basic' && [
|
||||
'text-black hover:text-gray-600 active:text-gray-800',
|
||||
'disabled:text-gray-300',
|
||||
],
|
||||
//#endregion //*======== Variant ===========
|
||||
'disabled:cursor-not-allowed disabled:brightness-105 disabled:hover:underline',
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default TextButton;
|
||||
64
src/components/links/ArrowLink.tsx
Normal file
64
src/components/links/ArrowLink.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
import UnderlineLink from '@/components/links/UnderlineLink';
|
||||
import { UnstyledLinkProps } from '@/components/links/UnstyledLink';
|
||||
|
||||
type ArrowLinkProps<C extends React.ElementType> = {
|
||||
as?: C;
|
||||
direction?: 'left' | 'right';
|
||||
} & UnstyledLinkProps &
|
||||
React.ComponentProps<C>;
|
||||
|
||||
export default function ArrowLink<C extends React.ElementType>({
|
||||
children,
|
||||
className,
|
||||
direction = 'right',
|
||||
as,
|
||||
...rest
|
||||
}: ArrowLinkProps<C>) {
|
||||
const Component = as || UnderlineLink;
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...rest}
|
||||
className={cn(
|
||||
'group gap-[0.25em]',
|
||||
direction === 'left' && 'flex-row-reverse',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<span>{children}</span>
|
||||
<svg
|
||||
viewBox='0 0 16 16'
|
||||
height='1em'
|
||||
width='1em'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
className={cn(
|
||||
'relative',
|
||||
'transition-transform duration-200',
|
||||
direction === 'right' ? 'motion-safe:-translate-x-1' : 'rotate-180',
|
||||
'group-hover:translate-x-0'
|
||||
)}
|
||||
>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z'
|
||||
/>
|
||||
<path
|
||||
stroke='currentColor'
|
||||
d='M1.75 8H11'
|
||||
strokeWidth='1.5'
|
||||
strokeLinecap='round'
|
||||
className={cn(
|
||||
'origin-left transition-all duration-200',
|
||||
'opacity-0 motion-safe:-translate-x-1',
|
||||
'group-hover:translate-x-0 group-hover:opacity-100'
|
||||
)}
|
||||
/>
|
||||
</svg>
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
146
src/components/links/ButtonLink.tsx
Normal file
146
src/components/links/ButtonLink.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import * as React from 'react';
|
||||
import { IconType } from 'react-icons';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
import UnstyledLink, {
|
||||
UnstyledLinkProps,
|
||||
} from '@/components/links/UnstyledLink';
|
||||
|
||||
const ButtonLinkVariant = [
|
||||
'primary',
|
||||
'outline',
|
||||
'ghost',
|
||||
'light',
|
||||
'dark',
|
||||
] as const;
|
||||
const ButtonLinkSize = ['sm', 'base'] as const;
|
||||
|
||||
type ButtonLinkProps = {
|
||||
isDarkBg?: boolean;
|
||||
variant?: (typeof ButtonLinkVariant)[number];
|
||||
size?: (typeof ButtonLinkSize)[number];
|
||||
leftIcon?: IconType | LucideIcon;
|
||||
rightIcon?: IconType | LucideIcon;
|
||||
classNames?: {
|
||||
leftIcon?: string;
|
||||
rightIcon?: string;
|
||||
};
|
||||
} & UnstyledLinkProps;
|
||||
|
||||
const ButtonLink = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
className,
|
||||
variant = 'primary',
|
||||
size = 'base',
|
||||
isDarkBg = false,
|
||||
leftIcon: LeftIcon,
|
||||
rightIcon: RightIcon,
|
||||
classNames,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<UnstyledLink
|
||||
ref={ref}
|
||||
{...rest}
|
||||
className={cn(
|
||||
'inline-flex items-center rounded font-medium',
|
||||
'focus-visible:ring-primary-500 focus:outline-none focus-visible:ring',
|
||||
'shadow-sm',
|
||||
'transition-colors duration-75',
|
||||
//#region //*=========== Size ===========
|
||||
[
|
||||
size === 'base' && ['px-3 py-1.5', 'text-sm md:text-base'],
|
||||
size === 'sm' && ['px-2 py-1', 'text-xs md:text-sm'],
|
||||
],
|
||||
//#endregion //*======== Size ===========
|
||||
//#region //*=========== Variants ===========
|
||||
[
|
||||
variant === 'primary' && [
|
||||
'bg-primary-500 text-white',
|
||||
'border-primary-600 border',
|
||||
'hover:bg-primary-600 hover:text-white',
|
||||
'active:bg-primary-700',
|
||||
'disabled:bg-primary-700',
|
||||
],
|
||||
variant === 'outline' && [
|
||||
'text-primary-500',
|
||||
'border-primary-500 border',
|
||||
'hover:bg-primary-50 active:bg-primary-100 disabled:bg-primary-100',
|
||||
isDarkBg &&
|
||||
'hover:bg-gray-900 active:bg-gray-800 disabled:bg-gray-800',
|
||||
],
|
||||
variant === 'ghost' && [
|
||||
'text-primary-500',
|
||||
'shadow-none',
|
||||
'hover:bg-primary-50 active:bg-primary-100 disabled:bg-primary-100',
|
||||
isDarkBg &&
|
||||
'hover:bg-gray-900 active:bg-gray-800 disabled:bg-gray-800',
|
||||
],
|
||||
variant === 'light' && [
|
||||
'bg-white text-gray-700',
|
||||
'border border-gray-300',
|
||||
'hover:text-dark hover:bg-gray-100',
|
||||
'active:bg-white/80 disabled:bg-gray-200',
|
||||
],
|
||||
variant === 'dark' && [
|
||||
'bg-gray-900 text-white',
|
||||
'border border-gray-600',
|
||||
'hover:bg-gray-800 active:bg-gray-700 disabled:bg-gray-700',
|
||||
],
|
||||
],
|
||||
//#endregion //*======== Variants ===========
|
||||
'disabled:cursor-not-allowed',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{LeftIcon && (
|
||||
<div
|
||||
className={cn([
|
||||
size === 'base' && 'mr-1',
|
||||
size === 'sm' && 'mr-1.5',
|
||||
])}
|
||||
>
|
||||
<LeftIcon
|
||||
size='1em'
|
||||
className={cn(
|
||||
[
|
||||
size === 'base' && 'md:text-md text-md',
|
||||
size === 'sm' && 'md:text-md text-sm',
|
||||
],
|
||||
classNames?.leftIcon
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
{RightIcon && (
|
||||
<div
|
||||
className={cn([
|
||||
size === 'base' && 'ml-1',
|
||||
size === 'sm' && 'ml-1.5',
|
||||
])}
|
||||
>
|
||||
<RightIcon
|
||||
size='1em'
|
||||
className={cn(
|
||||
[
|
||||
size === 'base' && 'text-md md:text-md',
|
||||
size === 'sm' && 'md:text-md text-sm',
|
||||
],
|
||||
classNames?.rightIcon
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</UnstyledLink>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default ButtonLink;
|
||||
97
src/components/links/IconLink.tsx
Normal file
97
src/components/links/IconLink.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import * as React from 'react';
|
||||
import { IconType } from 'react-icons';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
import UnstyledLink, {
|
||||
UnstyledLinkProps,
|
||||
} from '@/components/links/UnstyledLink';
|
||||
|
||||
const IconLinkVariant = [
|
||||
'primary',
|
||||
'outline',
|
||||
'ghost',
|
||||
'light',
|
||||
'dark',
|
||||
] as const;
|
||||
|
||||
type IconLinkProps = {
|
||||
isDarkBg?: boolean;
|
||||
variant?: (typeof IconLinkVariant)[number];
|
||||
icon?: IconType | LucideIcon;
|
||||
classNames?: {
|
||||
icon?: string;
|
||||
};
|
||||
} & Omit<UnstyledLinkProps, 'children'>;
|
||||
|
||||
const IconLink = React.forwardRef<HTMLAnchorElement, IconLinkProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
icon: Icon,
|
||||
variant = 'outline',
|
||||
isDarkBg = false,
|
||||
classNames,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<UnstyledLink
|
||||
ref={ref}
|
||||
type='button'
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center rounded font-medium',
|
||||
'focus-visible:ring-primary-500 focus:outline-none focus-visible:ring',
|
||||
'shadow-sm',
|
||||
'transition-colors duration-75',
|
||||
'min-h-[28px] min-w-[28px] p-1 md:min-h-[34px] md:min-w-[34px] md:p-2',
|
||||
//#region //*=========== Variants ===========
|
||||
[
|
||||
variant === 'primary' && [
|
||||
'bg-primary-500 text-white',
|
||||
'border-primary-600 border',
|
||||
'hover:bg-primary-600 hover:text-white',
|
||||
'active:bg-primary-700',
|
||||
'disabled:bg-primary-700',
|
||||
],
|
||||
variant === 'outline' && [
|
||||
'text-primary-500',
|
||||
'border-primary-500 border',
|
||||
'hover:bg-primary-50 active:bg-primary-100 disabled:bg-primary-100',
|
||||
isDarkBg &&
|
||||
'hover:bg-gray-900 active:bg-gray-800 disabled:bg-gray-800',
|
||||
],
|
||||
variant === 'ghost' && [
|
||||
'text-primary-500',
|
||||
'shadow-none',
|
||||
'hover:bg-primary-50 active:bg-primary-100 disabled:bg-primary-100',
|
||||
isDarkBg &&
|
||||
'hover:bg-gray-900 active:bg-gray-800 disabled:bg-gray-800',
|
||||
],
|
||||
variant === 'light' && [
|
||||
'bg-white text-gray-700',
|
||||
'border border-gray-300',
|
||||
'hover:text-dark hover:bg-gray-100',
|
||||
'active:bg-white/80 disabled:bg-gray-200',
|
||||
],
|
||||
variant === 'dark' && [
|
||||
'bg-gray-900 text-white',
|
||||
'border border-gray-600',
|
||||
'hover:bg-gray-800 active:bg-gray-700 disabled:bg-gray-700',
|
||||
],
|
||||
],
|
||||
//#endregion //*======== Variants ===========
|
||||
'disabled:cursor-not-allowed',
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{Icon && <Icon size='1em' className={cn(classNames?.icon)} />}
|
||||
</UnstyledLink>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default IconLink;
|
||||
43
src/components/links/PrimaryLink.tsx
Normal file
43
src/components/links/PrimaryLink.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
import UnstyledLink, {
|
||||
UnstyledLinkProps,
|
||||
} from '@/components/links/UnstyledLink';
|
||||
|
||||
const PrimaryLinkVariant = ['primary', 'basic'] as const;
|
||||
type PrimaryLinkProps = {
|
||||
variant?: (typeof PrimaryLinkVariant)[number];
|
||||
} & UnstyledLinkProps;
|
||||
|
||||
const PrimaryLink = React.forwardRef<HTMLAnchorElement, PrimaryLinkProps>(
|
||||
({ className, children, variant = 'primary', ...rest }, ref) => {
|
||||
return (
|
||||
<UnstyledLink
|
||||
ref={ref}
|
||||
{...rest}
|
||||
className={cn(
|
||||
'inline-flex items-center',
|
||||
'focus-visible:ring-primary-500 focus:outline-none focus-visible:rounded focus-visible:ring focus-visible:ring-offset-2',
|
||||
'font-medium',
|
||||
//#region //*=========== Variant ===========
|
||||
variant === 'primary' && [
|
||||
'text-primary-500 hover:text-primary-600 active:text-primary-700',
|
||||
'disabled:text-primary-200',
|
||||
],
|
||||
variant === 'basic' && [
|
||||
'text-black hover:text-gray-600 active:text-gray-800',
|
||||
'disabled:text-gray-300',
|
||||
],
|
||||
//#endregion //*======== Variant ===========
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</UnstyledLink>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default PrimaryLink;
|
||||
28
src/components/links/UnderlineLink.tsx
Normal file
28
src/components/links/UnderlineLink.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
import UnstyledLink, {
|
||||
UnstyledLinkProps,
|
||||
} from '@/components/links/UnstyledLink';
|
||||
|
||||
const UnderlineLink = React.forwardRef<HTMLAnchorElement, UnstyledLinkProps>(
|
||||
({ children, className, ...rest }, ref) => {
|
||||
return (
|
||||
<UnstyledLink
|
||||
ref={ref}
|
||||
{...rest}
|
||||
className={cn(
|
||||
'animated-underline custom-link inline-flex items-center font-medium',
|
||||
'focus-visible:ring-primary-500 focus:outline-none focus-visible:rounded focus-visible:ring focus-visible:ring-offset-2',
|
||||
'border-dark border-b border-dotted hover:border-black/0',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</UnstyledLink>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default UnderlineLink;
|
||||
50
src/components/links/UnstyledLink.tsx
Normal file
50
src/components/links/UnstyledLink.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import Link, { LinkProps } from 'next/link';
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export type UnstyledLinkProps = {
|
||||
href: string;
|
||||
children: React.ReactNode;
|
||||
openNewTab?: boolean;
|
||||
className?: string;
|
||||
nextLinkProps?: Omit<LinkProps, 'href'>;
|
||||
} & React.ComponentPropsWithRef<'a'>;
|
||||
|
||||
const UnstyledLink = React.forwardRef<HTMLAnchorElement, UnstyledLinkProps>(
|
||||
({ children, href, openNewTab, className, nextLinkProps, ...rest }, ref) => {
|
||||
const isNewTab =
|
||||
openNewTab !== undefined
|
||||
? openNewTab
|
||||
: href && !href.startsWith('/') && !href.startsWith('#');
|
||||
|
||||
if (!isNewTab) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
ref={ref}
|
||||
className={className}
|
||||
{...rest}
|
||||
{...nextLinkProps}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
ref={ref}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
href={href}
|
||||
{...rest}
|
||||
className={cn('cursor-newtab', className)}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default UnstyledLink;
|
||||
7
src/constant/config.ts
Normal file
7
src/constant/config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const siteConfig = {
|
||||
title: 'Next.js + Tailwind CSS + TypeScript Starter',
|
||||
description:
|
||||
'A starter for Next.js, Tailwind CSS, and TypeScript with Absolute Import, Seo, Link component, pre-configured with Husky',
|
||||
/** Without additional '/' on the end, e.g. https://theodorusclarence.com */
|
||||
url: 'https://tsnext-tw.thcl.dev',
|
||||
};
|
||||
6
src/constant/env.ts
Normal file
6
src/constant/env.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const isProd = process.env.NODE_ENV === 'production';
|
||||
export const isLocal = process.env.NODE_ENV === 'development';
|
||||
|
||||
export const showLogger = isLocal
|
||||
? true
|
||||
: process.env.NEXT_PUBLIC_SHOW_LOGGER === 'true' ?? false;
|
||||
20
src/lib/__tests__/og.test.ts
Normal file
20
src/lib/__tests__/og.test.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { openGraph } from '@/lib/og';
|
||||
|
||||
describe('Open Graph function should work correctly', () => {
|
||||
it('should not return templateTitle when not specified', () => {
|
||||
const result = openGraph({
|
||||
description: 'Test description',
|
||||
siteName: 'Test site name',
|
||||
});
|
||||
expect(result).not.toContain('&templateTitle=');
|
||||
});
|
||||
|
||||
it('should return templateTitle when specified', () => {
|
||||
const result = openGraph({
|
||||
templateTitle: 'Test Template Title',
|
||||
description: 'Test description',
|
||||
siteName: 'Test site name',
|
||||
});
|
||||
expect(result).toContain('&templateTitle=Test%20Template%20Title');
|
||||
});
|
||||
});
|
||||
20
src/lib/env.ts
Normal file
20
src/lib/env.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
/**
|
||||
* Configuration for type-safe environment variables.
|
||||
* Imported through src/app/page.tsx
|
||||
* @see https://x.com/mattpocockuk/status/1760991147793449396
|
||||
*/
|
||||
import { z } from 'zod';
|
||||
|
||||
const envVariables = z.object({
|
||||
NEXT_PUBLIC_SHOW_LOGGER: z.enum(['true', 'false']).optional(),
|
||||
});
|
||||
|
||||
envVariables.parse(process.env);
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface ProcessEnv extends z.infer<typeof envVariables> {}
|
||||
}
|
||||
}
|
||||
13
src/lib/helper.ts
Normal file
13
src/lib/helper.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export function getFromLocalStorage(key: string): string | null {
|
||||
if (typeof window !== 'undefined') {
|
||||
return window.localStorage.getItem(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getFromSessionStorage(key: string): string | null {
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
return sessionStorage.getItem(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
19
src/lib/logger.ts
Normal file
19
src/lib/logger.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/* eslint-disable no-console */
|
||||
import { showLogger } from '@/constant/env';
|
||||
|
||||
/**
|
||||
* A logger function that will only logs on development
|
||||
* @param object - The object to log
|
||||
* @param comment - Autogenerated with `lg` snippet
|
||||
*/
|
||||
export default function logger(object: unknown, comment?: string): void {
|
||||
if (!showLogger) return;
|
||||
|
||||
console.log(
|
||||
'%c ============== INFO LOG \n',
|
||||
'color: #22D3EE',
|
||||
`${typeof window !== 'undefined' && window?.location.pathname}\n`,
|
||||
`=== ${comment ?? ''}\n`,
|
||||
object
|
||||
);
|
||||
}
|
||||
27
src/lib/og.ts
Normal file
27
src/lib/og.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
type OpenGraphType = {
|
||||
siteName: string;
|
||||
description: string;
|
||||
templateTitle?: string;
|
||||
logo?: string;
|
||||
};
|
||||
// !STARTERCONF This OG is generated from https://github.com/theodorusclarence/og
|
||||
// Please clone them and self-host if your site is going to be visited by many people.
|
||||
// Then change the url and the default logo.
|
||||
export function openGraph({
|
||||
siteName,
|
||||
templateTitle,
|
||||
description,
|
||||
// !STARTERCONF Or, you can use my server with your own logo.
|
||||
logo = 'https://og.<your-domain>/images/logo.jpg',
|
||||
}: OpenGraphType): string {
|
||||
const ogLogo = encodeURIComponent(logo);
|
||||
const ogSiteName = encodeURIComponent(siteName.trim());
|
||||
const ogTemplateTitle = templateTitle
|
||||
? encodeURIComponent(templateTitle.trim())
|
||||
: undefined;
|
||||
const ogDesc = encodeURIComponent(description.trim());
|
||||
|
||||
return `https://og.<your-domain>/api/general?siteName=${ogSiteName}&description=${ogDesc}&logo=${ogLogo}${
|
||||
ogTemplateTitle ? `&templateTitle=${ogTemplateTitle}` : ''
|
||||
}`;
|
||||
}
|
||||
7
src/lib/utils.ts
Normal file
7
src/lib/utils.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import clsx, { ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
/** Merge classes with tailwind-merge with clsx full feature */
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
550
src/styles/colors.css
Normal file
550
src/styles/colors.css
Normal file
@@ -0,0 +1,550 @@
|
||||
/* //!STARTERCONF Remove this file after copying your desired color, this is a large file you should remove it. */
|
||||
|
||||
.slate {
|
||||
--tw-color-primary-50: 248 250 252;
|
||||
--tw-color-primary-100: 241 245 249;
|
||||
--tw-color-primary-200: 226 232 240;
|
||||
--tw-color-primary-300: 203 213 225;
|
||||
--tw-color-primary-400: 148 163 184;
|
||||
--tw-color-primary-500: 100 116 139;
|
||||
--tw-color-primary-600: 71 85 105;
|
||||
--tw-color-primary-700: 51 65 85;
|
||||
--tw-color-primary-800: 30 41 59;
|
||||
--tw-color-primary-900: 15 23 42;
|
||||
--tw-color-primary-950: 2 6 23;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #f8fafc */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #f1f5f9 */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #e2e8f0 */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #cbd5e1 */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #94a3b8 */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #64748b */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #475569 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #334155 */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #1e293b */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #0f172a */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #020617 */
|
||||
}
|
||||
|
||||
.gray {
|
||||
--tw-color-primary-50: 249 250 251;
|
||||
--tw-color-primary-100: 243 244 246;
|
||||
--tw-color-primary-200: 229 231 235;
|
||||
--tw-color-primary-300: 209 213 219;
|
||||
--tw-color-primary-400: 156 163 175;
|
||||
--tw-color-primary-500: 107 114 128;
|
||||
--tw-color-primary-600: 75 85 99;
|
||||
--tw-color-primary-700: 55 65 81;
|
||||
--tw-color-primary-800: 31 41 55;
|
||||
--tw-color-primary-900: 17 24 39;
|
||||
--tw-color-primary-950: 3 7 18;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #f9fafb */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #f3f4f6 */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #e5e7eb */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #d1d5db */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #9ca3af */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #6b7280 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #4b5563 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #374151 */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #1f2937 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #111827 */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #030712 */
|
||||
}
|
||||
|
||||
.zinc {
|
||||
--tw-color-primary-50: 250 250 250;
|
||||
--tw-color-primary-100: 244 244 245;
|
||||
--tw-color-primary-200: 228 228 231;
|
||||
--tw-color-primary-300: 212 212 216;
|
||||
--tw-color-primary-400: 161 161 170;
|
||||
--tw-color-primary-500: 113 113 122;
|
||||
--tw-color-primary-600: 82 82 91;
|
||||
--tw-color-primary-700: 63 63 70;
|
||||
--tw-color-primary-800: 39 39 42;
|
||||
--tw-color-primary-900: 24 24 27;
|
||||
--tw-color-primary-950: 9 9 11;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #fafafa */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #f4f4f5 */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #e4e4e7 */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #d4d4d8 */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #a1a1aa */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #71717a */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #52525b */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #3f3f46 */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #27272a */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #18181b */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #09090b */
|
||||
}
|
||||
|
||||
.neutral {
|
||||
--tw-color-primary-50: 250 250 250;
|
||||
--tw-color-primary-100: 245 245 245;
|
||||
--tw-color-primary-200: 229 229 229;
|
||||
--tw-color-primary-300: 212 212 212;
|
||||
--tw-color-primary-400: 163 163 163;
|
||||
--tw-color-primary-500: 115 115 115;
|
||||
--tw-color-primary-600: 82 82 82;
|
||||
--tw-color-primary-700: 64 64 64;
|
||||
--tw-color-primary-800: 38 38 38;
|
||||
--tw-color-primary-900: 23 23 23;
|
||||
--tw-color-primary-950: 10 10 10;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #fafafa */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #f5f5f5 */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #e5e5e5 */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #d4d4d4 */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #a3a3a3 */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #737373 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #525252 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #404040 */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #262626 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #171717 */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #0a0a0a */
|
||||
}
|
||||
|
||||
.stone {
|
||||
--tw-color-primary-50: 250 250 249;
|
||||
--tw-color-primary-100: 245 245 244;
|
||||
--tw-color-primary-200: 231 229 228;
|
||||
--tw-color-primary-300: 214 211 209;
|
||||
--tw-color-primary-400: 168 162 158;
|
||||
--tw-color-primary-500: 120 113 108;
|
||||
--tw-color-primary-600: 87 83 78;
|
||||
--tw-color-primary-700: 68 64 60;
|
||||
--tw-color-primary-800: 41 37 36;
|
||||
--tw-color-primary-900: 28 25 23;
|
||||
--tw-color-primary-950: 12 10 9;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #fafaf9 */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #f5f5f4 */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #e7e5e4 */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #d6d3d1 */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #a8a29e */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #78716c */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #57534e */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #44403c */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #292524 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #1c1917 */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #0c0a09 */
|
||||
}
|
||||
|
||||
.red {
|
||||
--tw-color-primary-50: 254 242 242;
|
||||
--tw-color-primary-100: 254 226 226;
|
||||
--tw-color-primary-200: 254 202 202;
|
||||
--tw-color-primary-300: 252 165 165;
|
||||
--tw-color-primary-400: 248 113 113;
|
||||
--tw-color-primary-500: 239 68 68;
|
||||
--tw-color-primary-600: 220 38 38;
|
||||
--tw-color-primary-700: 185 28 28;
|
||||
--tw-color-primary-800: 153 27 27;
|
||||
--tw-color-primary-900: 127 29 29;
|
||||
--tw-color-primary-950: 69 10 10;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #fef2f2 */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #fee2e2 */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #fecaca */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #fca5a5 */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #f87171 */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #ef4444 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #dc2626 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #b91c1c */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #991b1b */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #7f1d1d */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #450a0a */
|
||||
}
|
||||
|
||||
.orange {
|
||||
--tw-color-primary-50: 255 247 237;
|
||||
--tw-color-primary-100: 255 237 213;
|
||||
--tw-color-primary-200: 254 215 170;
|
||||
--tw-color-primary-300: 253 186 116;
|
||||
--tw-color-primary-400: 251 146 60;
|
||||
--tw-color-primary-500: 249 115 22;
|
||||
--tw-color-primary-600: 234 88 12;
|
||||
--tw-color-primary-700: 194 65 12;
|
||||
--tw-color-primary-800: 154 52 18;
|
||||
--tw-color-primary-900: 124 45 18;
|
||||
--tw-color-primary-950: 67 20 7;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #fff7ed */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #ffedd5 */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #fed7aa */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #fdba74 */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #fb923c */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #f97316 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #ea580c */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #c2410c */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #9a3412 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #7c2d12 */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #431407 */
|
||||
}
|
||||
|
||||
.amber {
|
||||
--tw-color-primary-50: 255 251 235;
|
||||
--tw-color-primary-100: 254 243 199;
|
||||
--tw-color-primary-200: 253 230 138;
|
||||
--tw-color-primary-300: 252 211 77;
|
||||
--tw-color-primary-400: 251 191 36;
|
||||
--tw-color-primary-500: 245 158 11;
|
||||
--tw-color-primary-600: 217 119 6;
|
||||
--tw-color-primary-700: 180 83 9;
|
||||
--tw-color-primary-800: 146 64 14;
|
||||
--tw-color-primary-900: 120 53 15;
|
||||
--tw-color-primary-950: 69 26 3;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #fffbeb */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #fef3c7 */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #fde68a */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #fcd34d */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #fbbf24 */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #f59e0b */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #d97706 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #b45309 */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #92400e */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #78350f */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #451a03 */
|
||||
}
|
||||
|
||||
.yellow {
|
||||
--tw-color-primary-50: 254 252 232;
|
||||
--tw-color-primary-100: 254 249 195;
|
||||
--tw-color-primary-200: 254 240 138;
|
||||
--tw-color-primary-300: 253 224 71;
|
||||
--tw-color-primary-400: 250 204 21;
|
||||
--tw-color-primary-500: 234 179 8;
|
||||
--tw-color-primary-600: 202 138 4;
|
||||
--tw-color-primary-700: 161 98 7;
|
||||
--tw-color-primary-800: 133 77 14;
|
||||
--tw-color-primary-900: 113 63 18;
|
||||
--tw-color-primary-950: 66 32 6;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #fefce8 */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #fef9c3 */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #fef08a */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #fde047 */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #facc15 */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #eab308 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #ca8a04 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #a16207 */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #854d0e */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #713f12 */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #422006 */
|
||||
}
|
||||
.lime {
|
||||
--tw-color-primary-50: 247 254 231;
|
||||
--tw-color-primary-100: 236 252 203;
|
||||
--tw-color-primary-200: 217 249 157;
|
||||
--tw-color-primary-300: 190 242 100;
|
||||
--tw-color-primary-400: 163 230 53;
|
||||
--tw-color-primary-500: 132 204 22;
|
||||
--tw-color-primary-600: 101 163 13;
|
||||
--tw-color-primary-700: 77 124 15;
|
||||
--tw-color-primary-800: 63 98 18;
|
||||
--tw-color-primary-900: 54 83 20;
|
||||
--tw-color-primary-950: 26 46 5;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #f7fee7 */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #ecfccb */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #d9f99d */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #bef264 */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #a3e635 */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #84cc16 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #65a30d */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #4d7c0f */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #3f6212 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #365314 */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #1a2e05 */
|
||||
}
|
||||
|
||||
.green {
|
||||
--tw-color-primary-50: 240 253 244;
|
||||
--tw-color-primary-100: 220 252 231;
|
||||
--tw-color-primary-200: 187 247 208;
|
||||
--tw-color-primary-300: 134 239 172;
|
||||
--tw-color-primary-400: 74 222 128;
|
||||
--tw-color-primary-500: 34 197 94;
|
||||
--tw-color-primary-600: 22 163 74;
|
||||
--tw-color-primary-700: 21 128 61;
|
||||
--tw-color-primary-800: 22 101 52;
|
||||
--tw-color-primary-900: 20 83 45;
|
||||
--tw-color-primary-950: 5 46 22;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #f0fdf4 */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #dcfce7 */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #bbf7d0 */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #86efac */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #4ade80 */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #22c55e */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #16a34a */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #15803d */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #166534 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #14532d */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #052e16 */
|
||||
}
|
||||
|
||||
.emerald {
|
||||
--tw-color-primary-50: 236 253 245;
|
||||
--tw-color-primary-100: 209 250 229;
|
||||
--tw-color-primary-200: 167 243 208;
|
||||
--tw-color-primary-300: 110 231 183;
|
||||
--tw-color-primary-400: 52 211 153;
|
||||
--tw-color-primary-500: 16 185 129;
|
||||
--tw-color-primary-600: 5 150 105;
|
||||
--tw-color-primary-700: 4 120 87;
|
||||
--tw-color-primary-800: 6 95 70;
|
||||
--tw-color-primary-900: 6 78 59;
|
||||
--tw-color-primary-950: 2 44 34;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #ecfdf5 */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #d1fae5 */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #a7f3d0 */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #6ee7b7 */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #34d399 */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #10b981 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #059669 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #047857 */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #065f46 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #064e3b */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #022c22 */
|
||||
}
|
||||
|
||||
.teal {
|
||||
--tw-color-primary-50: 240 253 250;
|
||||
--tw-color-primary-100: 204 251 241;
|
||||
--tw-color-primary-200: 153 246 228;
|
||||
--tw-color-primary-300: 94 234 212;
|
||||
--tw-color-primary-400: 45 212 191;
|
||||
--tw-color-primary-500: 20 184 166;
|
||||
--tw-color-primary-600: 13 148 136;
|
||||
--tw-color-primary-700: 15 118 110;
|
||||
--tw-color-primary-800: 17 94 89;
|
||||
--tw-color-primary-900: 19 78 74;
|
||||
--tw-color-primary-950: 4 47 46;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #f0fdfa */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #ccfbf1 */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #99f6e4 */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #5eead4 */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #2dd4bf */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #14b8a6 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #0d9488 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #0f766e */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #115e59 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #134e4a */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #042f2e */
|
||||
}
|
||||
|
||||
.cyan {
|
||||
--tw-color-primary-50: 236 254 255;
|
||||
--tw-color-primary-100: 207 250 254;
|
||||
--tw-color-primary-200: 165 243 252;
|
||||
--tw-color-primary-300: 103 232 249;
|
||||
--tw-color-primary-400: 34 211 238;
|
||||
--tw-color-primary-500: 6 182 212;
|
||||
--tw-color-primary-600: 8 145 178;
|
||||
--tw-color-primary-700: 14 116 144;
|
||||
--tw-color-primary-800: 21 94 117;
|
||||
--tw-color-primary-900: 22 78 99;
|
||||
--tw-color-primary-950: 8 51 68;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #ecfeff */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #cffafe */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #a5f3fc */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #67e8f9 */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #22d3ee */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #06b6d4 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #0891b2 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #0e7490 */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #155e75 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #164e63 */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #083344 */
|
||||
}
|
||||
|
||||
.sky {
|
||||
--tw-color-primary-50: 240 249 255;
|
||||
--tw-color-primary-100: 224 242 254;
|
||||
--tw-color-primary-200: 186 230 253;
|
||||
--tw-color-primary-300: 125 211 252;
|
||||
--tw-color-primary-400: 56 189 248;
|
||||
--tw-color-primary-500: 14 165 233;
|
||||
--tw-color-primary-600: 2 132 199;
|
||||
--tw-color-primary-700: 3 105 161;
|
||||
--tw-color-primary-800: 7 89 133;
|
||||
--tw-color-primary-900: 12 74 110;
|
||||
--tw-color-primary-950: 8 47 73;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #f0f9ff */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #e0f2fe */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #bae6fd */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #7dd3fc */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #38bdf8 */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #0ea5e9 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #0284c7 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #0369a1 */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #075985 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #0c4a6e */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #082f49 */
|
||||
}
|
||||
|
||||
.blue {
|
||||
--tw-color-primary-50: 239 246 255;
|
||||
--tw-color-primary-100: 219 234 254;
|
||||
--tw-color-primary-200: 191 219 254;
|
||||
--tw-color-primary-300: 147 197 253;
|
||||
--tw-color-primary-400: 96 165 250;
|
||||
--tw-color-primary-500: 59 130 246;
|
||||
--tw-color-primary-600: 37 99 235;
|
||||
--tw-color-primary-700: 29 78 216;
|
||||
--tw-color-primary-800: 30 64 175;
|
||||
--tw-color-primary-900: 30 58 138;
|
||||
--tw-color-primary-950: 23 37 84;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #eff6ff */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #dbeafe */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #bfdbfe */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #93c5fd */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #60a5fa */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #3b82f6 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #2563eb */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #1d4ed8 */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #1e40af */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #1e3a8a */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #172554 */
|
||||
}
|
||||
|
||||
.indigo {
|
||||
--tw-color-primary-50: 238 242 255;
|
||||
--tw-color-primary-100: 224 231 255;
|
||||
--tw-color-primary-200: 199 210 254;
|
||||
--tw-color-primary-300: 165 180 252;
|
||||
--tw-color-primary-400: 129 140 248;
|
||||
--tw-color-primary-500: 99 102 241;
|
||||
--tw-color-primary-600: 79 70 229;
|
||||
--tw-color-primary-700: 67 56 202;
|
||||
--tw-color-primary-800: 55 48 163;
|
||||
--tw-color-primary-900: 49 46 129;
|
||||
--tw-color-primary-950: 30 27 75;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #eef2ff */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #e0e7ff */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #c7d2fe */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #a5b4fc */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #818cf8 */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #6366f1 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #4f46e5 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #4338ca */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #3730a3 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #312e81 */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #1e1b4b */
|
||||
}
|
||||
|
||||
.violet {
|
||||
--tw-color-primary-50: 245 243 255;
|
||||
--tw-color-primary-100: 237 233 254;
|
||||
--tw-color-primary-200: 221 214 254;
|
||||
--tw-color-primary-300: 196 181 253;
|
||||
--tw-color-primary-400: 167 139 250;
|
||||
--tw-color-primary-500: 139 92 246;
|
||||
--tw-color-primary-600: 124 58 237;
|
||||
--tw-color-primary-700: 109 40 217;
|
||||
--tw-color-primary-800: 91 33 182;
|
||||
--tw-color-primary-900: 76 29 149;
|
||||
--tw-color-primary-950: 46 16 101;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #f5f3ff */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #ede9fe */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #ddd6fe */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #c4b5fd */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #a78bfa */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #8b5cf6 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #7c3aed */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #6d28d9 */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #5b21b6 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #4c1d95 */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #2e1065 */
|
||||
}
|
||||
|
||||
.purple {
|
||||
--tw-color-primary-50: 250 245 255;
|
||||
--tw-color-primary-100: 243 232 255;
|
||||
--tw-color-primary-200: 233 213 255;
|
||||
--tw-color-primary-300: 216 180 254;
|
||||
--tw-color-primary-400: 192 132 252;
|
||||
--tw-color-primary-500: 168 85 247;
|
||||
--tw-color-primary-600: 147 51 234;
|
||||
--tw-color-primary-700: 126 34 206;
|
||||
--tw-color-primary-800: 107 33 168;
|
||||
--tw-color-primary-900: 88 28 135;
|
||||
--tw-color-primary-950: 59 7 100;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #faf5ff */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #f3e8ff */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #e9d5ff */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #d8b4fe */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #c084fc */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #a855f7 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #9333ea */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #7e22ce */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #6b21a8 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #581c87 */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #3b0764 */
|
||||
}
|
||||
|
||||
.fuchsia {
|
||||
--tw-color-primary-50: 253 244 255;
|
||||
--tw-color-primary-100: 250 232 255;
|
||||
--tw-color-primary-200: 245 208 254;
|
||||
--tw-color-primary-300: 240 171 252;
|
||||
--tw-color-primary-400: 232 121 249;
|
||||
--tw-color-primary-500: 217 70 239;
|
||||
--tw-color-primary-600: 192 38 211;
|
||||
--tw-color-primary-700: 162 28 175;
|
||||
--tw-color-primary-800: 134 25 143;
|
||||
--tw-color-primary-900: 112 26 117;
|
||||
--tw-color-primary-950: 74 4 78;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #fdf4ff */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #fae8ff */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #f5d0fe */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #f0abfc */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #e879f9 */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #d946ef */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #c026d3 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #a21caf */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #86198f */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #701a75 */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #4a044e */
|
||||
}
|
||||
|
||||
.pink {
|
||||
--tw-color-primary-50: 253 242 248;
|
||||
--tw-color-primary-100: 252 231 243;
|
||||
--tw-color-primary-200: 251 207 232;
|
||||
--tw-color-primary-300: 249 168 212;
|
||||
--tw-color-primary-400: 244 114 182;
|
||||
--tw-color-primary-500: 236 72 153;
|
||||
--tw-color-primary-600: 219 39 119;
|
||||
--tw-color-primary-700: 190 24 93;
|
||||
--tw-color-primary-800: 157 23 77;
|
||||
--tw-color-primary-900: 131 24 67;
|
||||
--tw-color-primary-950: 80 4 36;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #fdf2f8 */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #fce7f3 */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #fbcfe8 */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #f9a8d4 */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #f472b6 */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #ec4899 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #db2777 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #be185d */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #9d174d */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #831843 */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #500724 */
|
||||
}
|
||||
|
||||
.rose {
|
||||
--tw-color-primary-50: 255 241 242;
|
||||
--tw-color-primary-100: 255 228 230;
|
||||
--tw-color-primary-200: 254 205 211;
|
||||
--tw-color-primary-300: 253 164 175;
|
||||
--tw-color-primary-400: 251 113 133;
|
||||
--tw-color-primary-500: 244 63 94;
|
||||
--tw-color-primary-600: 225 29 72;
|
||||
--tw-color-primary-700: 190 18 60;
|
||||
--tw-color-primary-800: 159 18 57;
|
||||
--tw-color-primary-900: 136 19 55;
|
||||
--tw-color-primary-950: 76 5 25;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #fff1f2 */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #ffe4e6 */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #fecdd3 */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #fda4af */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #fb7185 */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #f43f5e */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #e11d48 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #be123c */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #9f1239 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #881337 */
|
||||
--color-primary-950: rgb(var(--tw-color-primary-950)); /* #4c0519 */
|
||||
}
|
||||
118
src/styles/globals.css
Normal file
118
src/styles/globals.css
Normal file
@@ -0,0 +1,118 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
/* #region /**=========== Primary Color =========== */
|
||||
/* !STARTERCONF Customize these variable, copy and paste from /styles/colors.css for list of colors */
|
||||
--tw-color-primary-50: 240 249 255;
|
||||
--tw-color-primary-100: 224 242 254;
|
||||
--tw-color-primary-200: 186 230 253;
|
||||
--tw-color-primary-300: 125 211 252;
|
||||
--tw-color-primary-400: 56 189 248;
|
||||
--tw-color-primary-500: 14 165 233;
|
||||
--tw-color-primary-600: 2 132 199;
|
||||
--tw-color-primary-700: 3 105 161;
|
||||
--tw-color-primary-800: 7 89 133;
|
||||
--tw-color-primary-900: 12 74 110;
|
||||
--color-primary-50: rgb(var(--tw-color-primary-50)); /* #f0f9ff */
|
||||
--color-primary-100: rgb(var(--tw-color-primary-100)); /* #e0f2fe */
|
||||
--color-primary-200: rgb(var(--tw-color-primary-200)); /* #bae6fd */
|
||||
--color-primary-300: rgb(var(--tw-color-primary-300)); /* #7dd3fc */
|
||||
--color-primary-400: rgb(var(--tw-color-primary-400)); /* #38bdf8 */
|
||||
--color-primary-500: rgb(var(--tw-color-primary-500)); /* #0ea5e9 */
|
||||
--color-primary-600: rgb(var(--tw-color-primary-600)); /* #0284c7 */
|
||||
--color-primary-700: rgb(var(--tw-color-primary-700)); /* #0369a1 */
|
||||
--color-primary-800: rgb(var(--tw-color-primary-800)); /* #075985 */
|
||||
--color-primary-900: rgb(var(--tw-color-primary-900)); /* #0c4a6e */
|
||||
/* #endregion /**======== Primary Color =========== */
|
||||
}
|
||||
|
||||
@layer base {
|
||||
/* inter var - latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: block;
|
||||
src: url('/fonts/inter-var-latin.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
||||
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
|
||||
U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
.cursor-newtab {
|
||||
cursor: url('/images/new-tab.png') 10 10, pointer;
|
||||
}
|
||||
|
||||
/* #region /**=========== Typography =========== */
|
||||
.h0 {
|
||||
@apply font-primary text-3xl font-bold md:text-5xl;
|
||||
}
|
||||
|
||||
h1,
|
||||
.h1 {
|
||||
@apply font-primary text-2xl font-bold md:text-4xl;
|
||||
}
|
||||
|
||||
h2,
|
||||
.h2 {
|
||||
@apply font-primary text-xl font-bold md:text-3xl;
|
||||
}
|
||||
|
||||
h3,
|
||||
.h3 {
|
||||
@apply font-primary text-lg font-bold md:text-2xl;
|
||||
}
|
||||
|
||||
h4,
|
||||
.h4 {
|
||||
@apply font-primary text-base font-bold md:text-lg;
|
||||
}
|
||||
|
||||
body,
|
||||
.p {
|
||||
@apply font-primary text-sm md:text-base;
|
||||
}
|
||||
/* #endregion /**======== Typography =========== */
|
||||
|
||||
.layout {
|
||||
/* 1100px */
|
||||
max-width: 68.75rem;
|
||||
@apply mx-auto w-11/12;
|
||||
}
|
||||
|
||||
.bg-dark a.custom-link {
|
||||
@apply border-gray-200 hover:border-gray-200/0;
|
||||
}
|
||||
|
||||
/* Class to adjust with sticky footer */
|
||||
.min-h-main {
|
||||
@apply min-h-[calc(100vh-56px)];
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.animated-underline {
|
||||
background-image: linear-gradient(#33333300, #33333300),
|
||||
linear-gradient(
|
||||
to right,
|
||||
var(--color-primary-400),
|
||||
var(--color-primary-500)
|
||||
);
|
||||
background-size: 100% 2px, 0 2px;
|
||||
background-position: 100% 100%, 0 100%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.animated-underline {
|
||||
transition: 0.3s ease;
|
||||
transition-property: background-size, color, background-color,
|
||||
border-color;
|
||||
}
|
||||
}
|
||||
.animated-underline:hover,
|
||||
.animated-underline:focus-visible {
|
||||
background-size: 0 2px, 100% 2px;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user