feat: initial release!
This commit is contained in:
parent
c369cf9d76
commit
fbf07f5dff
@ -52,7 +52,13 @@
|
|||||||
"eslint-comments/no-unused-disable": "off",
|
"eslint-comments/no-unused-disable": "off",
|
||||||
"no-useless-concat": "off",
|
"no-useless-concat": "off",
|
||||||
"func-style": "off",
|
"func-style": "off",
|
||||||
"eslint-comments/no-unlimited-disable": "off"
|
"eslint-comments/no-unlimited-disable": "off",
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"endOfLine": "auto"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
|
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
@ -0,0 +1 @@
|
|||||||
|
pnpm lint
|
36
README.md
36
README.md
@ -1,36 +1,16 @@
|
|||||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
# Site - [refansa.my.id](https://refansa.my.id)
|
||||||
|
|
||||||
|
The source code of my frontend website, [refansa.my.id](https://refansa.my.id)
|
||||||
|
|
||||||
|
Built with [Next.JS](https://nextjs.org), [shadcn/ui](https://ui.shadcn.com), and [tailwindcss](https://tailwindcss.com)
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
First, run the development server:
|
After you cloned this repo you could easily run by;
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
npm install
|
||||||
npm run dev
|
npm run dev
|
||||||
# or
|
|
||||||
yarn dev
|
|
||||||
# or
|
|
||||||
pnpm dev
|
|
||||||
# or
|
|
||||||
bun dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
Open http://localhost:3000 with your browser to see the result.
|
||||||
|
|
||||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
|
||||||
|
|
||||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
To learn more about Next.js, take a look at the following resources:
|
|
||||||
|
|
||||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
||||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
||||||
|
|
||||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
|
||||||
|
|
||||||
## Deploy on Vercel
|
|
||||||
|
|
||||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
||||||
|
|
||||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
|
||||||
|
28
package.json
28
package.json
@ -1,20 +1,40 @@
|
|||||||
{
|
{
|
||||||
"name": "refansa.my.id",
|
"name": "refansa.my.id",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1a",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"homepage": "https://refansa.my.id",
|
||||||
|
"author": {
|
||||||
|
"name": "Muhammad Refansa Ali Muzky",
|
||||||
|
"nickname": "Refansa",
|
||||||
|
"email": "m.refansa.am@gmail.com",
|
||||||
|
"url": "https://github.com/refansa"
|
||||||
|
},
|
||||||
|
"description": "A humble internet abode.",
|
||||||
|
"repository": {
|
||||||
|
"url": "https://github.com/refansa/refansa.my.id"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint",
|
||||||
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@icons-pack/react-simple-icons": "^9.6.0",
|
||||||
|
"@radix-ui/react-dialog": "^1.1.1",
|
||||||
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
|
"@react-spring/web": "^9.7.4",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.408.0",
|
"lucide-react": "^0.408.0",
|
||||||
"next": "14.2.5",
|
"next": "14.2.5",
|
||||||
|
"next-themes": "^0.3.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-toggle-dark-mode": "^1.1.1",
|
||||||
|
"slug": "^9.1.0",
|
||||||
"tailwind-merge": "^2.4.0",
|
"tailwind-merge": "^2.4.0",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
@ -22,6 +42,7 @@
|
|||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"@types/slug": "^5.0.8",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "14.2.5",
|
"eslint-config-next": "14.2.5",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
@ -30,9 +51,10 @@
|
|||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-jsx-a11y": "^6.9.0",
|
"eslint-plugin-jsx-a11y": "^6.9.0",
|
||||||
"eslint-plugin-primer-react": "^5.3.0",
|
"eslint-plugin-primer-react": "^5.3.0",
|
||||||
|
"husky": "^9.1.3",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5679
pnpm-lock.yaml
5679
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,6 @@ const config = {
|
|||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
export default config;
|
export default config
|
||||||
|
21
src/app/blog/page.tsx
Normal file
21
src/app/blog/page.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Metadata } from 'next'
|
||||||
|
|
||||||
|
import Header from '@/components/blocks/header/header'
|
||||||
|
import Footer from '@/components/blocks/footer/footer'
|
||||||
|
import UnderConstruction from '@/components/blocks/error/under-construction'
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Blog',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Blog() {
|
||||||
|
return (
|
||||||
|
<div className="max-w-screen-lg w-full mx-auto px-6">
|
||||||
|
<Header />
|
||||||
|
<main className="flex justify-center items-center h-[85vh]">
|
||||||
|
<UnderConstruction />
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,69 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
:root {
|
|
||||||
--background: 0 0% 100%;
|
|
||||||
--foreground: 222.2 84% 4.9%;
|
|
||||||
--card: 0 0% 100%;
|
|
||||||
--card-foreground: 222.2 84% 4.9%;
|
|
||||||
--popover: 0 0% 100%;
|
|
||||||
--popover-foreground: 222.2 84% 4.9%;
|
|
||||||
--primary: 222.2 47.4% 11.2%;
|
|
||||||
--primary-foreground: 210 40% 98%;
|
|
||||||
--secondary: 210 40% 96.1%;
|
|
||||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
||||||
--muted: 210 40% 96.1%;
|
|
||||||
--muted-foreground: 215.4 16.3% 46.9%;
|
|
||||||
--accent: 210 40% 96.1%;
|
|
||||||
--accent-foreground: 222.2 47.4% 11.2%;
|
|
||||||
--destructive: 0 84.2% 60.2%;
|
|
||||||
--destructive-foreground: 210 40% 98%;
|
|
||||||
--border: 214.3 31.8% 91.4%;
|
|
||||||
--input: 214.3 31.8% 91.4%;
|
|
||||||
--ring: 222.2 84% 4.9%;
|
|
||||||
--radius: 0.5rem;
|
|
||||||
--chart-1: 12 76% 61%;
|
|
||||||
--chart-2: 173 58% 39%;
|
|
||||||
--chart-3: 197 37% 24%;
|
|
||||||
--chart-4: 43 74% 66%;
|
|
||||||
--chart-5: 27 87% 67%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--background: 222.2 84% 4.9%;
|
|
||||||
--foreground: 210 40% 98%;
|
|
||||||
--card: 222.2 84% 4.9%;
|
|
||||||
--card-foreground: 210 40% 98%;
|
|
||||||
--popover: 222.2 84% 4.9%;
|
|
||||||
--popover-foreground: 210 40% 98%;
|
|
||||||
--primary: 210 40% 98%;
|
|
||||||
--primary-foreground: 222.2 47.4% 11.2%;
|
|
||||||
--secondary: 217.2 32.6% 17.5%;
|
|
||||||
--secondary-foreground: 210 40% 98%;
|
|
||||||
--muted: 217.2 32.6% 17.5%;
|
|
||||||
--muted-foreground: 215 20.2% 65.1%;
|
|
||||||
--accent: 217.2 32.6% 17.5%;
|
|
||||||
--accent-foreground: 210 40% 98%;
|
|
||||||
--destructive: 0 62.8% 30.6%;
|
|
||||||
--destructive-foreground: 210 40% 98%;
|
|
||||||
--border: 217.2 32.6% 17.5%;
|
|
||||||
--input: 217.2 32.6% 17.5%;
|
|
||||||
--ring: 212.7 26.8% 83.9%;
|
|
||||||
--chart-1: 220 70% 50%;
|
|
||||||
--chart-2: 160 60% 45%;
|
|
||||||
--chart-3: 30 80% 55%;
|
|
||||||
--chart-4: 280 65% 60%;
|
|
||||||
--chart-5: 340 75% 55%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
* {
|
|
||||||
@apply border-border;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
@apply bg-background text-foreground;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +1,56 @@
|
|||||||
import type { Metadata } from 'next'
|
import '@/styles/globals.css'
|
||||||
import { Inter } from 'next/font/google'
|
|
||||||
import './globals.css'
|
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] })
|
import type { Metadata, Viewport } from 'next'
|
||||||
|
|
||||||
|
import { siteConfig } from '@/config/site'
|
||||||
|
|
||||||
|
import { ThemeProvider } from '@/components/providers/theme-provider'
|
||||||
|
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Create Next App',
|
title: {
|
||||||
description: 'Generated by create next app',
|
default: siteConfig.name,
|
||||||
|
template: `${siteConfig.name} | %s`,
|
||||||
|
},
|
||||||
|
metadataBase: new URL(siteConfig.url),
|
||||||
|
description: siteConfig.description,
|
||||||
|
keywords: ['Muhammad Refansa Ali Muzky', 'Refansa'],
|
||||||
|
authors: [
|
||||||
|
{
|
||||||
|
name: 'Muhammad Refansa Ali Muzky',
|
||||||
|
url: 'https://refansa.my.id',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
creator: 'Muhammad Refansa Ali Muzky',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout({
|
export const viewport: Viewport = {
|
||||||
children,
|
themeColor: [
|
||||||
}: Readonly<{
|
{ media: '(prefers-color-scheme: light)', color: 'white' },
|
||||||
|
{ media: '(prefers-color-scheme: dark)', color: 'black' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RootLayoutProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}>) {
|
}
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: RootLayoutProps) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body className={inter.className}>{children}</body>
|
<head />
|
||||||
|
<body>
|
||||||
|
<ThemeProvider
|
||||||
|
attribute="class"
|
||||||
|
defaultTheme="system"
|
||||||
|
enableSystem
|
||||||
|
disableTransitionOnChange
|
||||||
|
>
|
||||||
|
<TooltipProvider>
|
||||||
|
<div className="relative flex min-h-screen flex-col bg-background">{children}</div>
|
||||||
|
</TooltipProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
19
src/app/not-found.tsx
Normal file
19
src/app/not-found.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Metadata } from 'next'
|
||||||
|
|
||||||
|
import Header from '@/components/blocks/header/header'
|
||||||
|
import PageNotFound from '@/components/blocks/error/page-not-found'
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'You seem to be lost...',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<div className="max-w-screen-lg w-full mx-auto px-6">
|
||||||
|
<Header />
|
||||||
|
<main className="flex justify-center items-center h-[85vh]">
|
||||||
|
<PageNotFound />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
122
src/app/page.tsx
122
src/app/page.tsx
@ -1,113 +1,19 @@
|
|||||||
import Image from 'next/image'
|
import Header from '@/components/blocks/header/header'
|
||||||
|
import Footer from '@/components/blocks/footer/footer'
|
||||||
|
import AboutSection from '@/components/blocks/home/about-section'
|
||||||
|
import ContactSection from '@/components/blocks/home/contact-section'
|
||||||
|
import IntroductionSection from '@/components/blocks/home/introduction-section'
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<main className="flex min-h-screen flex-col items-center justify-between p-24">
|
<div className="max-w-screen-lg w-full mx-auto px-6">
|
||||||
<div className="z-10 w-full max-w-5xl items-center justify-between font-mono text-sm lg:flex">
|
<Header />
|
||||||
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
|
<main className="flex flex-col gap-24 mb-24">
|
||||||
Get started by editing
|
<IntroductionSection />
|
||||||
<code className="font-mono font-bold">src/app/page.tsx</code>
|
<AboutSection />
|
||||||
</p>
|
<ContactSection />
|
||||||
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:size-auto lg:bg-none">
|
</main>
|
||||||
<a
|
<Footer />
|
||||||
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
|
</div>
|
||||||
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
By{' '}
|
|
||||||
<Image
|
|
||||||
src="/vercel.svg"
|
|
||||||
alt="Vercel Logo"
|
|
||||||
className="dark:invert"
|
|
||||||
width={100}
|
|
||||||
height={24}
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative z-[-1] flex place-items-center before:absolute before:h-[300px] before:w-full before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 sm:before:w-[480px] sm:after:w-[240px] before:lg:h-[360px]">
|
|
||||||
<Image
|
|
||||||
className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
|
|
||||||
src="/next.svg"
|
|
||||||
alt="Next.js Logo"
|
|
||||||
width={180}
|
|
||||||
height={37}
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-32 grid text-center lg:mb-0 lg:w-full lg:max-w-5xl lg:grid-cols-4 lg:text-left">
|
|
||||||
<a
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
|
||||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<h2 className="mb-3 text-2xl font-semibold">
|
|
||||||
Docs{' '}
|
|
||||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
|
||||||
->
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<p className="m-0 max-w-[30ch] text-sm opacity-50">
|
|
||||||
Find in-depth information about Next.js features and API.
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<h2 className="mb-3 text-2xl font-semibold">
|
|
||||||
Learn{' '}
|
|
||||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
|
||||||
->
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<p className="m-0 max-w-[30ch] text-sm opacity-50">
|
|
||||||
Learn about Next.js in an interactive course with quizzes!
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
|
||||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<h2 className="mb-3 text-2xl font-semibold">
|
|
||||||
Templates{' '}
|
|
||||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
|
||||||
->
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<p className="m-0 max-w-[30ch] text-sm opacity-50">
|
|
||||||
Explore starter templates for Next.js.
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
|
||||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<h2 className="mb-3 text-2xl font-semibold">
|
|
||||||
Deploy{' '}
|
|
||||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
|
||||||
->
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<p className="m-0 max-w-[30ch] text-balance text-sm opacity-50">
|
|
||||||
Instantly deploy your Next.js site to a shareable URL with Vercel.
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
21
src/app/projects/page.tsx
Normal file
21
src/app/projects/page.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Metadata } from 'next'
|
||||||
|
|
||||||
|
import Header from '@/components/blocks/header/header'
|
||||||
|
import Footer from '@/components/blocks/footer/footer'
|
||||||
|
import UnderConstruction from '@/components/blocks/error/under-construction'
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Projects',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Projects() {
|
||||||
|
return (
|
||||||
|
<div className="max-w-screen-lg w-full mx-auto px-6">
|
||||||
|
<Header />
|
||||||
|
<main className="flex justify-center items-center h-[85vh]">
|
||||||
|
<UnderConstruction />
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
17
src/components/anchor.tsx
Normal file
17
src/components/anchor.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
import { UrlObject } from 'url'
|
||||||
|
|
||||||
|
import { HTMLAttributes } from 'react'
|
||||||
|
|
||||||
|
export interface Props extends HTMLAttributes<HTMLAnchorElement> {
|
||||||
|
href: string | UrlObject
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Anchor({ href, children, ...rest }: Props) {
|
||||||
|
return (
|
||||||
|
<Link className="underline hover:text-foreground/80" href={href} {...rest}>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
21
src/components/blocks/error/page-not-found.tsx
Normal file
21
src/components/blocks/error/page-not-found.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
import { HomeIcon } from 'lucide-react'
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
|
export default function PageNotFound() {
|
||||||
|
return (
|
||||||
|
<div className="flex font-mono gap-4 flex-col items-center tracking-wider">
|
||||||
|
<span className="text-7xl md:text-9xl">404</span>
|
||||||
|
<i className="text-xl md:text-2xl">Not Found</i>
|
||||||
|
<p className="text-center">You are trying to access a page that doesn't exists.</p>
|
||||||
|
<Button className="font-sans font-bold" variant="secondary">
|
||||||
|
<Link className="flex gap-2 items-center" href="/">
|
||||||
|
<HomeIcon />
|
||||||
|
Go Home
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
21
src/components/blocks/error/under-construction.tsx
Normal file
21
src/components/blocks/error/under-construction.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
import { HomeIcon } from 'lucide-react'
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
|
export default function UnderConstruction() {
|
||||||
|
return (
|
||||||
|
<div className="flex font-mono gap-4 flex-col items-center tracking-wider">
|
||||||
|
<span className="text-7xl md:text-9xl">501</span>
|
||||||
|
<i className="text-xl md:text-2xl">Not Implemented</i>
|
||||||
|
<p className="text-center">Sorry! The page is currently under construction.</p>
|
||||||
|
<Button className="font-sans font-bold" variant="secondary">
|
||||||
|
<Link className="flex gap-2 items-center" href="/">
|
||||||
|
<HomeIcon />
|
||||||
|
Go Home
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
13
src/components/blocks/footer/footer.tsx
Normal file
13
src/components/blocks/footer/footer.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import Anchor from '@/components/anchor'
|
||||||
|
import Package from '../../../../package.json'
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<footer className="flex flex-col gap-1 items-center mb-8">
|
||||||
|
<p className="font-semibold text-center">Site Version: {Package.version}</p>
|
||||||
|
<p className="font-semibold text-center">
|
||||||
|
Created with ❤️ by <Anchor href={Package.author.url}>{Package.author.nickname}</Anchor>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
75
src/components/blocks/header/header-navigation.tsx
Normal file
75
src/components/blocks/header/header-navigation.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import Link from 'next/link'
|
||||||
|
import dynamic from 'next/dynamic'
|
||||||
|
|
||||||
|
import { MenuIcon, XIcon } from 'lucide-react'
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton'
|
||||||
|
import {
|
||||||
|
Sheet,
|
||||||
|
SheetClose,
|
||||||
|
SheetContent,
|
||||||
|
SheetDescription,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
SheetTrigger,
|
||||||
|
} from '@/components/ui/sheet'
|
||||||
|
import NavigationItem from '@/components/blocks/header/navigation-item'
|
||||||
|
|
||||||
|
const Clock = dynamic(() => import('@/components/clock').then((mod) => mod.Clock), {
|
||||||
|
loading: () => <Skeleton className="md:w-52 w-[100px] h-7" />,
|
||||||
|
ssr: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const ThemeSwitch = dynamic(
|
||||||
|
() => import('@/components/theme-switch').then((mod) => mod.ThemeSwitch),
|
||||||
|
{
|
||||||
|
loading: () => <Skeleton className="w-10 h-10" />,
|
||||||
|
ssr: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export default function HeaderNavigation() {
|
||||||
|
return (
|
||||||
|
<nav className="flex h-16 p-2 items-center">
|
||||||
|
<div className="flex-1">
|
||||||
|
<Link href={'/'} className="font-bold text-xl">
|
||||||
|
Refansa
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 flex justify-center">
|
||||||
|
<Clock />
|
||||||
|
</div>
|
||||||
|
<div id="Desktop" className="flex-1 hidden md:flex gap-4 items-center justify-end">
|
||||||
|
<NavigationItem href="blog">Blog</NavigationItem>
|
||||||
|
<NavigationItem href="projects">Projects</NavigationItem>
|
||||||
|
<ThemeSwitch />
|
||||||
|
</div>
|
||||||
|
<div id="Mobile" className="flex-1 flex md:hidden justify-end">
|
||||||
|
<Sheet>
|
||||||
|
<SheetTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon">
|
||||||
|
<MenuIcon />
|
||||||
|
</Button>
|
||||||
|
</SheetTrigger>
|
||||||
|
<SheetContent side="right">
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle className="flex gap-2 justify-between px-2">
|
||||||
|
<ThemeSwitch starterId={10} />
|
||||||
|
<SheetClose asChild>
|
||||||
|
<Button variant="ghost" size="icon">
|
||||||
|
<XIcon />
|
||||||
|
</Button>
|
||||||
|
</SheetClose>
|
||||||
|
</SheetTitle>
|
||||||
|
<SheetDescription className="flex flex-col gap-2">
|
||||||
|
<NavigationItem href="blog">Blog</NavigationItem>
|
||||||
|
<NavigationItem href="projects">Projects</NavigationItem>
|
||||||
|
</SheetDescription>
|
||||||
|
</SheetHeader>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
9
src/components/blocks/header/header.tsx
Normal file
9
src/components/blocks/header/header.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import HeaderNavigation from './header-navigation'
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
return (
|
||||||
|
<header className="sticky top-0 backdrop-blur-xl bg-background/80">
|
||||||
|
<HeaderNavigation />
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
20
src/components/blocks/header/navigation-item.tsx
Normal file
20
src/components/blocks/header/navigation-item.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
children: ReactNode
|
||||||
|
href: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NavigationItem({ children, href }: Props) {
|
||||||
|
return (
|
||||||
|
<Button asChild variant="ghost">
|
||||||
|
<Link href={href}>
|
||||||
|
<span className="font-bold">{children}</span>
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
40
src/components/blocks/home/about-section.tsx
Normal file
40
src/components/blocks/home/about-section.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import Anchor from '@/components/anchor'
|
||||||
|
import TermWord from '@/components/term-word'
|
||||||
|
import { Heading } from '@/components/ui/heading'
|
||||||
|
|
||||||
|
export default function AboutSection() {
|
||||||
|
return (
|
||||||
|
<section className="flex flex-col gap-4 tracking-wider leading-relaxed text-xs md:text-base">
|
||||||
|
<Heading level={3}>A bit about me</Heading>
|
||||||
|
<p>
|
||||||
|
I'm a Software Developer from Jakarta, Indonesia 🇮🇩,{' '}
|
||||||
|
<TermWord description="Nice to meet you!">Senang berkenalan denganmu!</TermWord>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This is my humble internet abode, where I sometimes <Anchor href="/blog">blog</Anchor> about
|
||||||
|
programming, software development, game development, and some 3D modeling in my daily work.
|
||||||
|
But I mainly do web development, so that's probably what you will commonly see.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
I love nothing more than diving into complex projects, but that doesn't mean I admire
|
||||||
|
complexity over simplicity, quite the contrary in fact. It always amaze me how people turn a
|
||||||
|
complex problems into a simple, digestable format for a simpleton like me to understand.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
As a supporter of open source, I believe that sharing knowledge and collaborating on
|
||||||
|
projects is essential for the advancement of technologies.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Oh! And before I forget, I always have this urge to say that{' '}
|
||||||
|
<em className="text-foreground/50">
|
||||||
|
I use{' '}
|
||||||
|
<TermWord description="Arch Linux, a lightweight and flexible Linux® distribution that tries to Keep It Simple.">
|
||||||
|
<em>arch</em>
|
||||||
|
</TermWord>{' '}
|
||||||
|
btw
|
||||||
|
</em>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
20
src/components/blocks/home/contact-section.tsx
Normal file
20
src/components/blocks/home/contact-section.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { siteConfig } from '@/config/site'
|
||||||
|
|
||||||
|
import { Heading } from '@/components/ui/heading'
|
||||||
|
import Anchor from '@/components/anchor'
|
||||||
|
|
||||||
|
export default function ContactSection() {
|
||||||
|
return (
|
||||||
|
<section className="flex flex-col gap-4 text-xs md:text-base tracking-wider leading-relaxed">
|
||||||
|
<Heading level={3}>Contact</Heading>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<span>
|
||||||
|
Email: <Anchor href={siteConfig.links.email}>{siteConfig.email}</Anchor>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Tel: <Anchor href={siteConfig.links.tel}>{siteConfig.tel}</Anchor>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
46
src/components/blocks/home/introduction-section.tsx
Normal file
46
src/components/blocks/home/introduction-section.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { SiGithub } from '@icons-pack/react-simple-icons'
|
||||||
|
|
||||||
|
import { Mail } from 'lucide-react'
|
||||||
|
|
||||||
|
import { siteConfig } from '@/config/site'
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
|
export default function IntroductionSection() {
|
||||||
|
return (
|
||||||
|
<section className="flex flex-col py-24 gap-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-16 h-[2px] bg-primary" />
|
||||||
|
<span className="md:text-xl font-bold text-primary">Welcome, New & Old Friends!</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
className="text-5xl md:text-7xl font-bold"
|
||||||
|
style={{ textShadow: '3px 3px hsla(var(--primary) / 0.4)' }}
|
||||||
|
>
|
||||||
|
I'm Refansa
|
||||||
|
</span>
|
||||||
|
<div className="mt-4 flex flex-col">
|
||||||
|
<span className="text-lg md:text-2xl font-bold">
|
||||||
|
A Passionate, <i>self-taught</i> Software Developer
|
||||||
|
</span>
|
||||||
|
<span className="text-lg md:text-2xl font-bold text-foreground/50">
|
||||||
|
And a Patron of Open Source Software.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 flex gap-4">
|
||||||
|
<Button className="text-lg font-bold flex gap-2" size="lg" asChild>
|
||||||
|
<a href={siteConfig.links.github}>
|
||||||
|
<SiGithub />
|
||||||
|
Github
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
<Button className="text-lg font-bold flex gap-2" variant="secondary" size="lg" asChild>
|
||||||
|
<a href={siteConfig.links.email}>
|
||||||
|
<Mail />
|
||||||
|
Email
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
37
src/components/clock.tsx
Normal file
37
src/components/clock.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export function Clock() {
|
||||||
|
const [time, setTime] = useState(
|
||||||
|
new Intl.DateTimeFormat('en-US', {
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
|
timeZone: 'Asia/Jakarta',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timerInterval = setInterval(() => {
|
||||||
|
setTime(
|
||||||
|
new Intl.DateTimeFormat('en-US', {
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
|
timeZone: 'Asia/Jakarta',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
return () => clearInterval(timerInterval)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="font-bold text-lg md:text-xl">
|
||||||
|
<span>{time.format()}</span>
|
||||||
|
<span className="md:inline hidden"> - </span>
|
||||||
|
<span className="md:inline hidden">Jakarta</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
9
src/components/providers/theme-provider.tsx
Normal file
9
src/components/providers/theme-provider.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
||||||
|
import { type ThemeProviderProps } from 'next-themes/dist/types'
|
||||||
|
|
||||||
|
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||||
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||||
|
}
|
24
src/components/term-word.tsx
Normal file
24
src/components/term-word.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
children: ReactNode
|
||||||
|
/**
|
||||||
|
* The description of the term word.
|
||||||
|
*/
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TermWord({ children, description }: Props) {
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<span className="underline decoration-dashed underline-offset-2">{children}</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<span className="not-italic">{description}</span>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
130
src/components/theme-switch.tsx
Normal file
130
src/components/theme-switch.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useTheme } from 'next-themes'
|
||||||
|
|
||||||
|
import { animated, useSpring } from '@react-spring/web'
|
||||||
|
import { useEffect, useState, HTMLAttributes } from 'react'
|
||||||
|
import { Button } from './ui/button'
|
||||||
|
|
||||||
|
type SVGProps = Omit<HTMLAttributes<HTMLOrSVGElement>, 'onChange'>
|
||||||
|
|
||||||
|
export interface Props extends SVGProps {
|
||||||
|
onChange?: (checked: boolean) => void
|
||||||
|
size?: number | string
|
||||||
|
moonColor?: string
|
||||||
|
sunColor?: string
|
||||||
|
starterId?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ThemeSwitch({
|
||||||
|
onChange,
|
||||||
|
size = 24,
|
||||||
|
moonColor = 'white',
|
||||||
|
sunColor = 'dark',
|
||||||
|
starterId = 0,
|
||||||
|
}: Props) {
|
||||||
|
let REACT_TOGGLE_DARK_MODE_GLOBAL_ID = starterId
|
||||||
|
|
||||||
|
const { theme, setTheme } = useTheme()
|
||||||
|
|
||||||
|
const [id, setId] = useState(REACT_TOGGLE_DARK_MODE_GLOBAL_ID)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
REACT_TOGGLE_DARK_MODE_GLOBAL_ID += 1
|
||||||
|
setId(REACT_TOGGLE_DARK_MODE_GLOBAL_ID)
|
||||||
|
}, [setId])
|
||||||
|
|
||||||
|
const properties = {
|
||||||
|
circle: {
|
||||||
|
r: theme === 'dark' ? 9 : 5,
|
||||||
|
},
|
||||||
|
mask: {
|
||||||
|
cx: theme === 'dark' ? '50%' : '100',
|
||||||
|
cy: theme === 'dark' ? '23%' : '0%',
|
||||||
|
},
|
||||||
|
svg: {
|
||||||
|
transform: theme === 'dark' ? 'rotate(40deg)' : 'rotate(90deg)',
|
||||||
|
},
|
||||||
|
lines: {
|
||||||
|
opacity: theme === 'dark' ? 0 : 1,
|
||||||
|
},
|
||||||
|
config: { mass: 4, tension: 250, friction: 35 },
|
||||||
|
}
|
||||||
|
|
||||||
|
const svgContainerProps = useSpring({
|
||||||
|
...properties.svg,
|
||||||
|
config: properties.config,
|
||||||
|
})
|
||||||
|
|
||||||
|
const centerCircleProps = useSpring({
|
||||||
|
...properties.circle,
|
||||||
|
config: properties.config,
|
||||||
|
})
|
||||||
|
const maskedCircleProps = useSpring({
|
||||||
|
...properties.mask,
|
||||||
|
config: properties.config,
|
||||||
|
})
|
||||||
|
const linesProps = useSpring({
|
||||||
|
...properties.lines,
|
||||||
|
config: properties.config,
|
||||||
|
})
|
||||||
|
|
||||||
|
const uniqueMaskId = `circle-mask-${id}`
|
||||||
|
|
||||||
|
const toggle = () => {
|
||||||
|
setTheme(theme === 'dark' ? 'light' : 'dark')
|
||||||
|
onChange && onChange(theme === 'dark')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button onClick={toggle} variant="ghost" size="icon">
|
||||||
|
<div className="flex items-center w-5 h-5 bg-transparent">
|
||||||
|
<animated.svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
color={theme === 'dark' ? moonColor : sunColor}
|
||||||
|
fill="none"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
stroke="currentColor"
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
...svgContainerProps,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<mask id={uniqueMaskId}>
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="white" />
|
||||||
|
<animated.circle
|
||||||
|
// @ts-ignore
|
||||||
|
style={maskedCircleProps}
|
||||||
|
r="9"
|
||||||
|
fill="black"
|
||||||
|
/>
|
||||||
|
</mask>
|
||||||
|
|
||||||
|
<animated.circle
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
fill={theme === 'dark' ? moonColor : sunColor}
|
||||||
|
// @ts-ignore
|
||||||
|
style={centerCircleProps}
|
||||||
|
mask={`url(#${uniqueMaskId})`}
|
||||||
|
/>
|
||||||
|
<animated.g stroke="currentColor" style={linesProps}>
|
||||||
|
<line x1="12" y1="1" x2="12" y2="3" />
|
||||||
|
<line x1="12" y1="21" x2="12" y2="23" />
|
||||||
|
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
||||||
|
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
||||||
|
<line x1="1" y1="12" x2="3" y2="12" />
|
||||||
|
<line x1="21" y1="12" x2="23" y2="12" />
|
||||||
|
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
||||||
|
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
||||||
|
</animated.g>
|
||||||
|
</animated.svg>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
49
src/components/ui/button.tsx
Normal file
49
src/components/ui/button.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import { Slot } from '@radix-ui/react-slot'
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||||
|
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||||
|
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||||
|
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||||
|
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||||
|
link: 'text-primary underline-offset-4 hover:underline',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: 'h-10 px-4 py-2',
|
||||||
|
sm: 'h-9 rounded-md px-3',
|
||||||
|
lg: 'h-11 rounded-md px-8',
|
||||||
|
icon: 'h-10 w-10',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
size: 'default',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : 'button'
|
||||||
|
return (
|
||||||
|
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Button.displayName = 'Button'
|
||||||
|
|
||||||
|
export { Button, buttonVariants }
|
86
src/components/ui/heading.tsx
Normal file
86
src/components/ui/heading.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
import slug from 'slug'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { HTMLAttributes } from 'react'
|
||||||
|
|
||||||
|
type HeadingProps = HTMLAttributes<HTMLHeadingElement>
|
||||||
|
|
||||||
|
export interface Props extends HeadingProps {
|
||||||
|
children: string
|
||||||
|
/**
|
||||||
|
* Heading level, each level represent the HTML heading level.
|
||||||
|
* @min 1
|
||||||
|
* @max 6
|
||||||
|
*/
|
||||||
|
level: number
|
||||||
|
/**
|
||||||
|
* If `true`, the heading will be associated with a hash link.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
withLink?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const HashLink = ({ text }: { text: string }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Link
|
||||||
|
href={`#${slug(text)}`}
|
||||||
|
className="group-hover/heading:opacity-100 opacity-0 transition-opacity ease-in-out duration-500 text-foreground/40"
|
||||||
|
>
|
||||||
|
#
|
||||||
|
</Link>
|
||||||
|
<div id={slug(text)} className="relative invisible -top-24" />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Heading({ children, level, withLink = true, ...rest }: Props) {
|
||||||
|
const defaultClasses = ['group/heading', 'font-bold', 'flex', 'items-center', 'gap-4', 'mb-2']
|
||||||
|
|
||||||
|
switch (level) {
|
||||||
|
case 1:
|
||||||
|
return (
|
||||||
|
<h1 {...rest} className={cn(defaultClasses, 'text-4xl md:text-6xl', rest.className)}>
|
||||||
|
{children}
|
||||||
|
{withLink ? <HashLink text={children} /> : null}
|
||||||
|
</h1>
|
||||||
|
)
|
||||||
|
case 2:
|
||||||
|
return (
|
||||||
|
<h2 {...rest} className={cn(defaultClasses, 'text-3xl md:text-5xl', rest.className)}>
|
||||||
|
{children}
|
||||||
|
{withLink ? <HashLink text={children} /> : null}
|
||||||
|
</h2>
|
||||||
|
)
|
||||||
|
case 3:
|
||||||
|
return (
|
||||||
|
<h3 {...rest} className={cn(defaultClasses, 'text-2xl md:text-4xl', rest.className)}>
|
||||||
|
{children}
|
||||||
|
{withLink ? <HashLink text={children} /> : null}
|
||||||
|
</h3>
|
||||||
|
)
|
||||||
|
case 4:
|
||||||
|
return (
|
||||||
|
<h4 {...rest} className={cn(defaultClasses, 'text-xl md:text-3xl', rest.className)}>
|
||||||
|
{children}
|
||||||
|
{withLink ? <HashLink text={children} /> : null}
|
||||||
|
</h4>
|
||||||
|
)
|
||||||
|
case 5:
|
||||||
|
return (
|
||||||
|
<h5 {...rest} className={cn(defaultClasses, 'text-lg md:text-2xl', rest.className)}>
|
||||||
|
{children}
|
||||||
|
{withLink ? <HashLink text={children} /> : null}
|
||||||
|
</h5>
|
||||||
|
)
|
||||||
|
case 6:
|
||||||
|
return (
|
||||||
|
<h6 {...rest} className={cn(defaultClasses, 'text-base md:text-xl', rest.className)}>
|
||||||
|
{children}
|
||||||
|
{withLink ? <HashLink text={children} /> : null}
|
||||||
|
</h6>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
116
src/components/ui/sheet.tsx
Normal file
116
src/components/ui/sheet.tsx
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import * as SheetPrimitive from '@radix-ui/react-dialog'
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const Sheet = SheetPrimitive.Root
|
||||||
|
|
||||||
|
const SheetTrigger = SheetPrimitive.Trigger
|
||||||
|
|
||||||
|
const SheetClose = SheetPrimitive.Close
|
||||||
|
|
||||||
|
const SheetPortal = SheetPrimitive.Portal
|
||||||
|
|
||||||
|
const SheetOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Overlay
|
||||||
|
className={cn(
|
||||||
|
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const sheetVariants = cva(
|
||||||
|
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
side: {
|
||||||
|
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
||||||
|
bottom:
|
||||||
|
'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
||||||
|
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
|
||||||
|
right:
|
||||||
|
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
side: 'right',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
interface SheetContentProps
|
||||||
|
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||||
|
VariantProps<typeof sheetVariants> {}
|
||||||
|
|
||||||
|
const SheetContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||||
|
SheetContentProps
|
||||||
|
>(({ side = 'right', className, children, ...props }, ref) => (
|
||||||
|
<SheetPortal>
|
||||||
|
<SheetOverlay />
|
||||||
|
<SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
|
||||||
|
{children}
|
||||||
|
</SheetPrimitive.Content>
|
||||||
|
</SheetPortal>
|
||||||
|
))
|
||||||
|
SheetContent.displayName = SheetPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
|
||||||
|
)
|
||||||
|
SheetHeader.displayName = 'SheetHeader'
|
||||||
|
|
||||||
|
const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
SheetFooter.displayName = 'SheetFooter'
|
||||||
|
|
||||||
|
const SheetTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-lg font-semibold text-foreground', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const SheetDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-sm text-muted-foreground', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Sheet,
|
||||||
|
SheetPortal,
|
||||||
|
SheetOverlay,
|
||||||
|
SheetTrigger,
|
||||||
|
SheetClose,
|
||||||
|
SheetContent,
|
||||||
|
SheetHeader,
|
||||||
|
SheetFooter,
|
||||||
|
SheetTitle,
|
||||||
|
SheetDescription,
|
||||||
|
}
|
7
src/components/ui/skeleton.tsx
Normal file
7
src/components/ui/skeleton.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return <div className={cn('animate-pulse rounded-md bg-muted', className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Skeleton }
|
30
src/components/ui/tooltip.tsx
Normal file
30
src/components/ui/tooltip.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const TooltipProvider = TooltipPrimitive.Provider
|
||||||
|
|
||||||
|
const Tooltip = TooltipPrimitive.Root
|
||||||
|
|
||||||
|
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||||
|
|
||||||
|
const TooltipContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||||
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
|
<TooltipPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
19
src/config/site.ts
Normal file
19
src/config/site.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export const siteConfig = {
|
||||||
|
name: 'Refansa',
|
||||||
|
email: 'm.refansa.am@gmail.com',
|
||||||
|
tel: '(+62) 812-8543-3284',
|
||||||
|
url: 'https://refansa.my.id',
|
||||||
|
description: 'A humble internet abode.',
|
||||||
|
get links() {
|
||||||
|
return siteLinks
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const siteLinks = {
|
||||||
|
github: 'https://github.com/refansa',
|
||||||
|
email: `mailto:${siteConfig.email}`,
|
||||||
|
tel: `tel:${siteConfig.tel}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SiteConfig = typeof siteConfig
|
||||||
|
export type SiteLinks = typeof siteLinks
|
72
src/styles/globals.css
Normal file
72
src/styles/globals.css
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 240 10% 3.9%;
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 240 10% 3.9%;
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 240 10% 3.9%;
|
||||||
|
--primary: 33 44% 22%;
|
||||||
|
--primary-foreground: 355.7 100% 97.3%;
|
||||||
|
--secondary: 240 4.8% 95.9%;
|
||||||
|
--secondary-foreground: 240 5.9% 10%;
|
||||||
|
--muted: 240 4.8% 95.9%;
|
||||||
|
--muted-foreground: 240 3.8% 46.1%;
|
||||||
|
--accent: 240 4.8% 95.9%;
|
||||||
|
--accent-foreground: 240 5.9% 10%;
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 240 5.9% 90%;
|
||||||
|
--input: 240 5.9% 90%;
|
||||||
|
--ring: 33 44% 42%;
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--chart-1: 12 76% 61%;
|
||||||
|
--chart-2: 173 58% 39%;
|
||||||
|
--chart-3: 197 37% 24%;
|
||||||
|
--chart-4: 43 74% 66%;
|
||||||
|
--chart-5: 27 87% 67%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 20 14.3% 4.1%;
|
||||||
|
--foreground: 0 0% 95%;
|
||||||
|
--card: 24 9.8% 10%;
|
||||||
|
--card-foreground: 0 0% 95%;
|
||||||
|
--popover: 0 0% 9%;
|
||||||
|
--popover-foreground: 0 0% 95%;
|
||||||
|
--primary: 33 44% 52%;
|
||||||
|
--primary-foreground: 144.9 80.4% 10%;
|
||||||
|
--secondary: 240 3.7% 15.9%;
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
--muted: 0 0% 15%;
|
||||||
|
--muted-foreground: 240 5% 64.9%;
|
||||||
|
--accent: 12 6.5% 15.1%;
|
||||||
|
--accent-foreground: 0 0% 98%;
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 0 85.7% 97.3%;
|
||||||
|
--border: 240 3.7% 15.9%;
|
||||||
|
--input: 240 3.7% 15.9%;
|
||||||
|
--ring: 33 44% 52%;
|
||||||
|
--chart-1: 220 70% 50%;
|
||||||
|
--chart-2: 160 60% 45%;
|
||||||
|
--chart-3: 30 80% 55%;
|
||||||
|
--chart-4: 280 65% 60%;
|
||||||
|
--chart-5: 340 75% 55%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
*::selection {
|
||||||
|
@apply bg-primary/20;
|
||||||
|
}
|
||||||
|
}
|
@ -1,80 +1,81 @@
|
|||||||
import type { Config } from "tailwindcss"
|
import type { Config } from 'tailwindcss'
|
||||||
|
import TailwindCSSAnimate from 'tailwindcss-animate'
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
darkMode: ["class"],
|
darkMode: ['class'],
|
||||||
content: [
|
content: [
|
||||||
'./pages/**/*.{ts,tsx}',
|
'./pages/**/*.{ts,tsx}',
|
||||||
'./components/**/*.{ts,tsx}',
|
'./components/**/*.{ts,tsx}',
|
||||||
'./app/**/*.{ts,tsx}',
|
'./app/**/*.{ts,tsx}',
|
||||||
'./src/**/*.{ts,tsx}',
|
'./src/**/*.{ts,tsx}',
|
||||||
],
|
],
|
||||||
prefix: "",
|
prefix: '',
|
||||||
theme: {
|
theme: {
|
||||||
container: {
|
container: {
|
||||||
center: true,
|
center: true,
|
||||||
padding: "2rem",
|
padding: '2rem',
|
||||||
screens: {
|
screens: {
|
||||||
"2xl": "1400px",
|
'2xl': '1400px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
border: "hsl(var(--border))",
|
border: 'hsl(var(--border))',
|
||||||
input: "hsl(var(--input))",
|
input: 'hsl(var(--input))',
|
||||||
ring: "hsl(var(--ring))",
|
ring: 'hsl(var(--ring))',
|
||||||
background: "hsl(var(--background))",
|
background: 'hsl(var(--background))',
|
||||||
foreground: "hsl(var(--foreground))",
|
foreground: 'hsl(var(--foreground))',
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: "hsl(var(--primary))",
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
foreground: 'hsl(var(--primary-foreground))',
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
DEFAULT: "hsl(var(--secondary))",
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
foreground: "hsl(var(--secondary-foreground))",
|
foreground: 'hsl(var(--secondary-foreground))',
|
||||||
},
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
DEFAULT: "hsl(var(--destructive))",
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
foreground: "hsl(var(--destructive-foreground))",
|
foreground: 'hsl(var(--destructive-foreground))',
|
||||||
},
|
},
|
||||||
muted: {
|
muted: {
|
||||||
DEFAULT: "hsl(var(--muted))",
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
foreground: "hsl(var(--muted-foreground))",
|
foreground: 'hsl(var(--muted-foreground))',
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: "hsl(var(--accent))",
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
foreground: "hsl(var(--accent-foreground))",
|
foreground: 'hsl(var(--accent-foreground))',
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
DEFAULT: "hsl(var(--popover))",
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
foreground: "hsl(var(--popover-foreground))",
|
foreground: 'hsl(var(--popover-foreground))',
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
DEFAULT: "hsl(var(--card))",
|
DEFAULT: 'hsl(var(--card))',
|
||||||
foreground: "hsl(var(--card-foreground))",
|
foreground: 'hsl(var(--card-foreground))',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: "var(--radius)",
|
lg: 'var(--radius)',
|
||||||
md: "calc(var(--radius) - 2px)",
|
md: 'calc(var(--radius) - 2px)',
|
||||||
sm: "calc(var(--radius) - 4px)",
|
sm: 'calc(var(--radius) - 4px)',
|
||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
"accordion-down": {
|
'accordion-down': {
|
||||||
from: { height: "0" },
|
from: { height: '0' },
|
||||||
to: { height: "var(--radix-accordion-content-height)" },
|
to: { height: 'var(--radix-accordion-content-height)' },
|
||||||
},
|
},
|
||||||
"accordion-up": {
|
'accordion-up': {
|
||||||
from: { height: "var(--radix-accordion-content-height)" },
|
from: { height: 'var(--radix-accordion-content-height)' },
|
||||||
to: { height: "0" },
|
to: { height: '0' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||||
"accordion-up": "accordion-up 0.2s ease-out",
|
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate")],
|
plugins: [TailwindCSSAnimate],
|
||||||
} satisfies Config
|
} satisfies Config
|
||||||
|
|
||||||
export default config
|
export default config
|
||||||
|
Loading…
Reference in New Issue
Block a user