chore: update dependencies and improve UI components
- Updated dependencies in bun.lock and package.json to specific versions for better stability. - Added a new Contact component to display contact methods with tooltips. - Introduced a Banner component for notifications with customizable actions. - Enhanced the Dialog, Checkbox, Button, Input, and Label components for better usability and styling. - Integrated react-snowfall for a snow effect in the main page. - Added a Discord SVG icon to the project.
This commit is contained in:
parent
65b444cc93
commit
aa721322da
11 changed files with 486 additions and 265 deletions
129
bun.lock
129
bun.lock
|
|
@ -5,69 +5,70 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "imnya",
|
"name": "imnya",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "latest",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@mdx-js/loader": "latest",
|
"@mdx-js/loader": "^3.1.1",
|
||||||
"@mdx-js/react": "latest",
|
"@mdx-js/react": "^3.1.1",
|
||||||
"@next/mdx": "latest",
|
"@next/mdx": "^16.0.10",
|
||||||
"@radix-ui/react-accordion": "latest",
|
"@radix-ui/react-accordion": "^1.2.12",
|
||||||
"@radix-ui/react-alert-dialog": "latest",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-aspect-ratio": "latest",
|
"@radix-ui/react-aspect-ratio": "^1.1.8",
|
||||||
"@radix-ui/react-avatar": "latest",
|
"@radix-ui/react-avatar": "^1.1.11",
|
||||||
"@radix-ui/react-checkbox": "latest",
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
"@radix-ui/react-collapsible": "latest",
|
"@radix-ui/react-collapsible": "^1.1.12",
|
||||||
"@radix-ui/react-context-menu": "latest",
|
"@radix-ui/react-context-menu": "^2.2.16",
|
||||||
"@radix-ui/react-dialog": "latest",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "latest",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-hover-card": "latest",
|
"@radix-ui/react-hover-card": "^1.1.15",
|
||||||
"@radix-ui/react-label": "latest",
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
"@radix-ui/react-menubar": "latest",
|
"@radix-ui/react-menubar": "^1.1.16",
|
||||||
"@radix-ui/react-navigation-menu": "latest",
|
"@radix-ui/react-navigation-menu": "^1.2.14",
|
||||||
"@radix-ui/react-popover": "latest",
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
"@radix-ui/react-progress": "latest",
|
"@radix-ui/react-progress": "^1.1.8",
|
||||||
"@radix-ui/react-radio-group": "latest",
|
"@radix-ui/react-radio-group": "^1.3.8",
|
||||||
"@radix-ui/react-scroll-area": "latest",
|
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||||
"@radix-ui/react-select": "latest",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-separator": "latest",
|
"@radix-ui/react-separator": "^1.1.8",
|
||||||
"@radix-ui/react-slider": "latest",
|
"@radix-ui/react-slider": "^1.3.6",
|
||||||
"@radix-ui/react-slot": "latest",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@radix-ui/react-switch": "latest",
|
"@radix-ui/react-switch": "^1.2.6",
|
||||||
"@radix-ui/react-tabs": "latest",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@radix-ui/react-toggle": "latest",
|
"@radix-ui/react-toggle": "^1.1.10",
|
||||||
"@radix-ui/react-toggle-group": "latest",
|
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||||
"@radix-ui/react-tooltip": "latest",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"class-variance-authority": "latest",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "latest",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "latest",
|
"cmdk": "^1.1.1",
|
||||||
"date-fns": "latest",
|
"date-fns": "^4.1.0",
|
||||||
"embla-carousel-react": "latest",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"framer-motion": "latest",
|
"framer-motion": "^12.23.26",
|
||||||
"gray-matter": "latest",
|
"gray-matter": "^4.0.3",
|
||||||
"highlight.js": "latest",
|
"highlight.js": "^11.11.1",
|
||||||
"input-otp": "latest",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "latest",
|
"lucide-react": "^0.562.0",
|
||||||
"next": "latest",
|
"next": "16.0.10",
|
||||||
"next-themes": "latest",
|
"next-themes": "^0.4.6",
|
||||||
"radix-ui": "latest",
|
"radix-ui": "^1.4.3",
|
||||||
"react": "latest",
|
"react": "19.2.3",
|
||||||
"react-day-picker": "latest",
|
"react-day-picker": "^9.12.0",
|
||||||
"react-dom": "latest",
|
"react-dom": "19.2.3",
|
||||||
"react-hook-form": "latest",
|
"react-hook-form": "^7.68.0",
|
||||||
"react-resizable-panels": "latest",
|
"react-resizable-panels": "^3.0.6",
|
||||||
"recharts": "latest",
|
"react-snowfall": "^2.4.0",
|
||||||
"sonner": "latest",
|
"recharts": "3.5.1",
|
||||||
"tailwind-merge": "latest",
|
"sonner": "^2.0.7",
|
||||||
"vaul": "latest",
|
"tailwind-merge": "^3.4.0",
|
||||||
"zod": "latest",
|
"vaul": "^1.1.2",
|
||||||
|
"zod": "^4.1.13",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "latest",
|
"@biomejs/biome": "2.3.8",
|
||||||
"@tailwindcss/postcss": "latest",
|
"@tailwindcss/postcss": "^4.1.18",
|
||||||
"@types/node": "latest",
|
"@types/node": "^25.0.1",
|
||||||
"@types/react": "latest",
|
"@types/react": "19.2.2",
|
||||||
"@types/react-dom": "latest",
|
"@types/react-dom": "19.2.2",
|
||||||
"tailwindcss": "latest",
|
"tailwindcss": "^4.1.18",
|
||||||
"tw-animate-css": "latest",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "latest",
|
"typescript": "^5.9.3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -580,7 +581,7 @@
|
||||||
|
|
||||||
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
|
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
|
||||||
|
|
||||||
"lucide-react": ["lucide-react@0.560.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-NwKoUA/aBShsdL8WE5lukV2F/tjHzQRlonQs7fkNGI1sCT0Ay4a9Ap3ST2clUUkcY+9eQ0pBe2hybTQd2fmyDA=="],
|
"lucide-react": ["lucide-react@0.562.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw=="],
|
||||||
|
|
||||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
|
|
@ -688,6 +689,8 @@
|
||||||
|
|
||||||
"react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
|
"react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
|
||||||
|
|
||||||
|
"react-fast-compare": ["react-fast-compare@3.2.2", "", {}, "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="],
|
||||||
|
|
||||||
"react-hook-form": ["react-hook-form@7.68.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q=="],
|
"react-hook-form": ["react-hook-form@7.68.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q=="],
|
||||||
|
|
||||||
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
||||||
|
|
@ -700,6 +703,8 @@
|
||||||
|
|
||||||
"react-resizable-panels": ["react-resizable-panels@3.0.6", "", { "peerDependencies": { "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-b3qKHQ3MLqOgSS+FRYKapNkJZf5EQzuf6+RLiq1/IlTHw99YrZ2NJZLk4hQIzTnnIkRg2LUqyVinu6YWWpUYew=="],
|
"react-resizable-panels": ["react-resizable-panels@3.0.6", "", { "peerDependencies": { "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-b3qKHQ3MLqOgSS+FRYKapNkJZf5EQzuf6+RLiq1/IlTHw99YrZ2NJZLk4hQIzTnnIkRg2LUqyVinu6YWWpUYew=="],
|
||||||
|
|
||||||
|
"react-snowfall": ["react-snowfall@2.4.0", "", { "dependencies": { "react-fast-compare": "^3.2.2" }, "peerDependencies": { "react": "^16.8 || 17.x || 18.x || 19.x", "react-dom": "^16.8 || 17.x || 18.x || 19.x" } }, "sha512-KAPMiGnxt11PEgC2pTVrTQsvk5jt1kLUtG+ZamiKLphTZ7GiYT1Aa5kX6jp4jKWq1kqJHchnGT9CDm4g86A5Gg=="],
|
||||||
|
|
||||||
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
|
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
|
||||||
|
|
||||||
"recharts": ["recharts@3.5.1", "", { "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-+v+HJojK7gnEgG6h+b2u7k8HH7FhyFUzAc4+cPrsjL4Otdgqr/ecXzAnHciqlzV1ko064eNcsdzrYOM78kankA=="],
|
"recharts": ["recharts@3.5.1", "", { "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-+v+HJojK7gnEgG6h+b2u7k8HH7FhyFUzAc4+cPrsjL4Otdgqr/ecXzAnHciqlzV1ko064eNcsdzrYOM78kankA=="],
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.560.0",
|
"lucide-react": "^0.562.0",
|
||||||
"next": "16.0.10",
|
"next": "16.0.10",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"radix-ui": "^1.4.3",
|
"radix-ui": "^1.4.3",
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"react-dom": "19.2.3",
|
"react-dom": "19.2.3",
|
||||||
"react-hook-form": "^7.68.0",
|
"react-hook-form": "^7.68.0",
|
||||||
"react-resizable-panels": "^3.0.6",
|
"react-resizable-panels": "^3.0.6",
|
||||||
|
"react-snowfall": "^2.4.0",
|
||||||
"recharts": "3.5.1",
|
"recharts": "3.5.1",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
|
|
|
||||||
22
public/icon/discord.svg
Normal file
22
public/icon/discord.svg
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve" width="512" height="512">
|
||||||
|
<g>
|
||||||
|
<path d="M20.317,4.37c-1.53-0.702-3.17-1.219-4.885-1.515c-0.031-0.006-0.062,0.009-0.079,0.037 c-0.211,0.375-0.445,0.865-0.608,1.249c-1.845-0.276-3.68-0.276-5.487,0C9.095,3.748,8.852,3.267,8.641,2.892 C8.624,2.864,8.593,2.85,8.562,2.855C6.848,3.15,5.208,3.667,3.677,4.37C3.664,4.375,3.652,4.385,3.645,4.397 c-3.111,4.648-3.964,9.182-3.546,13.66c0.002,0.022,0.014,0.043,0.031,0.056c2.053,1.508,4.041,2.423,5.993,3.029 c0.031,0.01,0.064-0.002,0.084-0.028c0.462-0.63,0.873-1.295,1.226-1.994c0.021-0.041,0.001-0.09-0.042-0.106 c-0.653-0.248-1.274-0.55-1.872-0.892c-0.047-0.028-0.051-0.095-0.008-0.128c0.126-0.094,0.252-0.192,0.372-0.291 c0.022-0.018,0.052-0.022,0.078-0.01c3.928,1.793,8.18,1.793,12.061,0c0.026-0.012,0.056-0.009,0.079,0.01 c0.12,0.099,0.246,0.198,0.373,0.292c0.044,0.032,0.041,0.1-0.007,0.128c-0.598,0.349-1.219,0.645-1.873,0.891 c-0.043,0.016-0.061,0.066-0.041,0.107c0.36,0.698,0.772,1.363,1.225,1.993c0.019,0.027,0.053,0.038,0.084,0.029 c1.961-0.607,3.95-1.522,6.002-3.029c0.018-0.013,0.029-0.033,0.031-0.055c0.5-5.177-0.838-9.674-3.548-13.66 C20.342,4.385,20.33,4.375,20.317,4.37z M8.02,15.331c-1.183,0-2.157-1.086-2.157-2.419s0.955-2.419,2.157-2.419 c1.211,0,2.176,1.095,2.157,2.419C10.177,14.246,9.221,15.331,8.02,15.331z M15.995,15.331c-1.182,0-2.157-1.086-2.157-2.419 s0.955-2.419,2.157-2.419c1.211,0,2.176,1.095,2.157,2.419C18.152,14.246,17.206,15.331,15.995,15.331z"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -1,13 +1,25 @@
|
||||||
|
'use client';
|
||||||
import Projects from "@/components/Projects";
|
import Projects from "@/components/Projects";
|
||||||
import TimelineComponent from "@/components/timeline";
|
import TimelineComponent from "@/components/timeline";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import DraggableWindow from "@/components/DraggableWindow";
|
import DraggableWindow from "@/components/DraggableWindow";
|
||||||
import ReadmeWindow from "@/components/ReadmeWindow";
|
import ReadmeWindow from "@/components/ReadmeWindow";
|
||||||
|
import Snowfall from 'react-snowfall';
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { Banner } from "@/components/ui/banner";
|
||||||
|
import { LinkIcon, TreeDeciduous, TreePalmIcon, TreesIcon } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import Contact from "@/components/Contact";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const [show, setShow] = useState(true);
|
||||||
|
|
||||||
export default async function BlogIndex() {
|
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen w-screen overflow-y-scroll snap-y snap-mandatory">
|
<main className="min-h-screen w-screen overflow-y-scroll snap-y snap-mandatory">
|
||||||
<div className="max-w-4xl w-full flex flex-row h-auto mx-auto gap-4 items-center my-8 lg:px-0 px-8">
|
<Snowfall color={theme === 'dark' ? '#ffffff' : '#bcbcd6ff'} />
|
||||||
|
|
||||||
|
<div className="max-w-3xl w-full flex flex-row h-auto mx-auto gap-4 items-center my-8 lg:px-0 px-8">
|
||||||
<Image
|
<Image
|
||||||
src="/Frame.svg"
|
src="/Frame.svg"
|
||||||
alt="logo"
|
alt="logo"
|
||||||
|
|
@ -19,9 +31,31 @@ export default async function BlogIndex() {
|
||||||
<a href="mailto:me@imnya.ng">me@imnya.ng</a>
|
<a href="mailto:me@imnya.ng">me@imnya.ng</a>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="max-w-3xl w-full flex mx-auto mb-8">
|
||||||
|
<Contact />
|
||||||
|
</div>
|
||||||
<section id="about" className="w-full snap-start snap-always flex flex-col items-center justify-center px-8 lg:px-0">
|
<section id="about" className="w-full snap-start snap-always flex flex-col items-center justify-center px-8 lg:px-0">
|
||||||
|
<div className="max-w-3xl w-full mb-8">
|
||||||
|
<Banner
|
||||||
|
show={show}
|
||||||
|
onHide={() => setShow(false)}
|
||||||
|
icon={
|
||||||
|
<TreeDeciduous className="w-4 h-4 text-green-800" />
|
||||||
|
}
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
제 크리스마스 트리를 꾸며주세요!
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
action={{
|
||||||
|
label: "트리 꾸미러 가기",
|
||||||
|
onClick: () => window.open("https://imnya.ng/tree", "_blank"),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<DraggableWindow />
|
<DraggableWindow />
|
||||||
<div className="max-w-4xl w-full">
|
<div className="max-w-3xl w-full">
|
||||||
<p>나의 <strong>어두움</strong>이 다른 사람들에겐 <strong>빛</strong>이 되길 바라는 <strong>개발자, 정보보안전문가</strong> 남현석입니다.</p>
|
<p>나의 <strong>어두움</strong>이 다른 사람들에겐 <strong>빛</strong>이 되길 바라는 <strong>개발자, 정보보안전문가</strong> 남현석입니다.</p>
|
||||||
<p><strong>초등학교 시절 운영체제</strong>에 흥미를 느껴 컴퓨터를 시작했고, 이후 프로그래밍에 관심을 갖게 되었습니다.</p>
|
<p><strong>초등학교 시절 운영체제</strong>에 흥미를 느껴 컴퓨터를 시작했고, 이후 프로그래밍에 관심을 갖게 되었습니다.</p>
|
||||||
<p><strong>초등학교 4학년 때 Python</strong>으로 프로그래밍을 시작했으며, 현재는 <strong>TypeScript</strong>를 주로 사용합니다.</p>
|
<p><strong>초등학교 4학년 때 Python</strong>으로 프로그래밍을 시작했으며, 현재는 <strong>TypeScript</strong>를 주로 사용합니다.</p>
|
||||||
|
|
|
||||||
55
src/components/Contact.tsx
Normal file
55
src/components/Contact.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { Github, Instagram, MailIcon } from "lucide-react";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
const contact = [
|
||||||
|
{
|
||||||
|
"name": "Email",
|
||||||
|
"url": "mailto:me@imnya.ng",
|
||||||
|
"icon": <MailIcon className="w-5 h-5" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GitHub",
|
||||||
|
"url": "https://github.com/imnyang",
|
||||||
|
"icon": <Github className="w-5 h-5" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Instagram",
|
||||||
|
"url": "https://instagram.com/imnya.ng",
|
||||||
|
"icon": <Instagram className="w-5 h-5" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Discord",
|
||||||
|
"url": "https://imnya.ng/discord",
|
||||||
|
"icon": <Image src="/icon/discord.svg" alt="Discord" width={20} height={20} className="w-5 h-5" />
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function Contact() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row space-x-4">
|
||||||
|
{contact.map((method) => (
|
||||||
|
<Tooltip key={method.name} >
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<a
|
||||||
|
href={method.url}
|
||||||
|
className="flex items-center space-x-2 text-foreground/80 hover:text-foreground transition-colors"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{method.icon}
|
||||||
|
</a>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{method.name}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
123
src/components/ui/banner.tsx
Normal file
123
src/components/ui/banner.tsx
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { X } from "lucide-react"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Grid({
|
||||||
|
cellSize = 12,
|
||||||
|
strokeWidth = 1,
|
||||||
|
patternOffset = [0, 0],
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
cellSize?: number
|
||||||
|
strokeWidth?: number
|
||||||
|
patternOffset?: [number, number]
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
const id = React.useId()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={cn(
|
||||||
|
"pointer-events-none absolute inset-0 text-black/10",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<pattern
|
||||||
|
id={`grid-${id}`}
|
||||||
|
x={patternOffset[0] - 1}
|
||||||
|
y={patternOffset[1] - 1}
|
||||||
|
width={cellSize}
|
||||||
|
height={cellSize}
|
||||||
|
patternUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d={`M ${cellSize} 0 L 0 0 0 ${cellSize}`}
|
||||||
|
fill="transparent"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
/>
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
<rect fill={`url(#grid-${id})`} width="100%" height="100%" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BannerProps {
|
||||||
|
show: boolean
|
||||||
|
onHide: () => void
|
||||||
|
icon?: React.ReactNode
|
||||||
|
title: React.ReactNode
|
||||||
|
action: {
|
||||||
|
label: string
|
||||||
|
onClick: () => void
|
||||||
|
}
|
||||||
|
learnMoreUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Banner({
|
||||||
|
show,
|
||||||
|
onHide,
|
||||||
|
icon,
|
||||||
|
title,
|
||||||
|
action,
|
||||||
|
learnMoreUrl,
|
||||||
|
}: BannerProps) {
|
||||||
|
if (!show) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative isolate flex flex-col justify-between gap-3 overflow-hidden rounded-lg border border-green-600/15 bg-gradient-to-r from-lime-100/80 to-emerald-100/80 py-3 pl-4 pr-12 sm:flex-row sm:items-center sm:py-2">
|
||||||
|
<Grid
|
||||||
|
cellSize={13}
|
||||||
|
patternOffset={[0, -1]}
|
||||||
|
className="text-black/30 mix-blend-overlay [mask-image:linear-gradient(to_right,black,transparent)] md:[mask-image:linear-gradient(to_right,black_60%,transparent)]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{icon && (
|
||||||
|
<div className="hidden rounded-full border border-green-600/50 bg-white/50 p-1 shadow-[inset_0_0_1px_1px_#fff] sm:block">
|
||||||
|
{icon}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<p className="text-sm text-gray-900">
|
||||||
|
{title}
|
||||||
|
{learnMoreUrl && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
<a
|
||||||
|
href={learnMoreUrl}
|
||||||
|
target="_blank"
|
||||||
|
className="text-gray-700 underline transition-colors hover:text-black"
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center sm:-my-1">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="whitespace-nowrap rounded-md border border-green-700/50 px-3 py-1 text-sm text-gray-800 transition-colors hover:bg-green-500/10"
|
||||||
|
onClick={action.onClick}
|
||||||
|
>
|
||||||
|
{action.label}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="absolute inset-y-0 right-2.5 p-1 text-sm text-green-700 underline transition-colors hover:text-green-900"
|
||||||
|
onClick={onHide}
|
||||||
|
>
|
||||||
|
<X className="h-[18px] w-[18px]" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -5,54 +5,52 @@ import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"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: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
destructive:
|
destructive:
|
||||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||||
outline:
|
outline:
|
||||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
secondary:
|
secondary:
|
||||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
ghost:
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
default: "h-10 px-4 py-2",
|
||||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
sm: "h-9 rounded-md px-3",
|
||||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
lg: "h-11 rounded-md px-8",
|
||||||
icon: "size-9",
|
icon: "h-10 w-10",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
function Button({
|
export interface ButtonProps
|
||||||
className,
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
variant,
|
VariantProps<typeof buttonVariants> {
|
||||||
size,
|
asChild?: boolean
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"button"> &
|
|
||||||
VariantProps<typeof buttonVariants> & {
|
|
||||||
asChild?: boolean
|
|
||||||
}) {
|
|
||||||
const Comp = asChild ? Slot : "button"
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Comp
|
|
||||||
data-slot="button"
|
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 }
|
export { Button, buttonVariants }
|
||||||
|
|
|
||||||
|
|
@ -2,31 +2,29 @@
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||||
import { CheckIcon } from "lucide-react"
|
import { Check } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
function Checkbox({
|
const Checkbox = React.forwardRef<
|
||||||
className,
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
...props
|
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||||
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
>(({ className, ...props }, ref) => (
|
||||||
return (
|
<CheckboxPrimitive.Root
|
||||||
<CheckboxPrimitive.Root
|
ref={ref}
|
||||||
data-slot="checkbox"
|
className={cn(
|
||||||
className={cn(
|
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||||
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
className,
|
||||||
className
|
)}
|
||||||
)}
|
{...props}
|
||||||
{...props}
|
>
|
||||||
|
<CheckboxPrimitive.Indicator
|
||||||
|
className={cn("flex items-center justify-center text-current")}
|
||||||
>
|
>
|
||||||
<CheckboxPrimitive.Indicator
|
<Check className="h-4 w-4" />
|
||||||
data-slot="checkbox-indicator"
|
</CheckboxPrimitive.Indicator>
|
||||||
className="flex items-center justify-center text-current transition-none"
|
</CheckboxPrimitive.Root>
|
||||||
>
|
))
|
||||||
<CheckIcon className="size-3.5" />
|
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||||
</CheckboxPrimitive.Indicator>
|
|
||||||
</CheckboxPrimitive.Root>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Checkbox }
|
export { Checkbox }
|
||||||
|
|
|
||||||
|
|
@ -1,143 +1,122 @@
|
||||||
"use client"
|
'use client';
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from 'react';
|
||||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||||
import { XIcon } from "lucide-react"
|
import { X } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
function Dialog({
|
const Dialog = DialogPrimitive.Root;
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
|
||||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogTrigger({
|
const DialogTrigger = DialogPrimitive.Trigger;
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
||||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogPortal({
|
const DialogPortal = DialogPrimitive.Portal;
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
||||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogClose({
|
const DialogClose = DialogPrimitive.Close;
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
|
||||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogOverlay({
|
const DialogOverlay = React.forwardRef<
|
||||||
className,
|
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||||
...props
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
>(({ className, ...props }, ref) => (
|
||||||
return (
|
<DialogPrimitive.Overlay
|
||||||
<DialogPrimitive.Overlay
|
ref={ref}
|
||||||
data-slot="dialog-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}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||||
|
|
||||||
|
const DialogContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
)
|
{children}
|
||||||
}
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
));
|
||||||
|
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||||
|
|
||||||
function DialogContent({
|
const DialogHeader = ({
|
||||||
className,
|
|
||||||
children,
|
|
||||||
showCloseButton = true,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
||||||
showCloseButton?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<DialogPortal data-slot="dialog-portal">
|
|
||||||
<DialogOverlay />
|
|
||||||
<DialogPrimitive.Content
|
|
||||||
data-slot="dialog-content"
|
|
||||||
className={cn(
|
|
||||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
{showCloseButton && (
|
|
||||||
<DialogPrimitive.Close
|
|
||||||
data-slot="dialog-close"
|
|
||||||
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
|
||||||
>
|
|
||||||
<XIcon />
|
|
||||||
<span className="sr-only">Close</span>
|
|
||||||
</DialogPrimitive.Close>
|
|
||||||
)}
|
|
||||||
</DialogPrimitive.Content>
|
|
||||||
</DialogPortal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="dialog-header"
|
|
||||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="dialog-footer"
|
|
||||||
className={cn(
|
|
||||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogTitle({
|
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
return (
|
<div
|
||||||
<DialogPrimitive.Title
|
className={cn(
|
||||||
data-slot="dialog-title"
|
'flex flex-col space-y-1.5 text-center sm:text-left',
|
||||||
className={cn("text-lg leading-none font-semibold", className)}
|
className
|
||||||
{...props}
|
)}
|
||||||
/>
|
{...props}
|
||||||
)
|
/>
|
||||||
}
|
);
|
||||||
|
DialogHeader.displayName = 'DialogHeader';
|
||||||
|
|
||||||
function DialogDescription({
|
const DialogFooter = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
return (
|
<div
|
||||||
<DialogPrimitive.Description
|
className={cn(
|
||||||
data-slot="dialog-description"
|
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
||||||
className={cn("text-muted-foreground text-sm", className)}
|
className
|
||||||
{...props}
|
)}
|
||||||
/>
|
{...props}
|
||||||
)
|
/>
|
||||||
}
|
);
|
||||||
|
DialogFooter.displayName = 'DialogFooter';
|
||||||
|
|
||||||
|
const DialogTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'text-lg font-semibold leading-none tracking-tight',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||||
|
|
||||||
|
const DialogDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-sm text-muted-foreground', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogClose,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogOverlay,
|
|
||||||
DialogPortal,
|
DialogPortal,
|
||||||
DialogTitle,
|
DialogOverlay,
|
||||||
|
DialogClose,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
}
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogFooter,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
};
|
||||||
|
|
@ -2,20 +2,24 @@ import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
export interface InputProps
|
||||||
return (
|
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
<input
|
|
||||||
type={type}
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
data-slot="input"
|
({ className, type, ...props }, ref) => {
|
||||||
className={cn(
|
return (
|
||||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
<input
|
||||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
type={type}
|
||||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
className={cn(
|
||||||
className
|
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
)}
|
className
|
||||||
{...props}
|
)}
|
||||||
/>
|
ref={ref}
|
||||||
)
|
{...props}
|
||||||
}
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Input.displayName = "Input"
|
||||||
|
|
||||||
export { Input }
|
export { Input }
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,25 @@
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
function Label({
|
const labelVariants = cva(
|
||||||
className,
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||||
...props
|
)
|
||||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
||||||
return (
|
const Label = React.forwardRef<
|
||||||
<LabelPrimitive.Root
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
data-slot="label"
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
className={cn(
|
VariantProps<typeof labelVariants>
|
||||||
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
>(({ className, ...props }, ref) => (
|
||||||
className
|
<LabelPrimitive.Root
|
||||||
)}
|
ref={ref}
|
||||||
{...props}
|
className={cn(labelVariants(), className)}
|
||||||
/>
|
{...props}
|
||||||
)
|
/>
|
||||||
}
|
))
|
||||||
|
Label.displayName = LabelPrimitive.Root.displayName
|
||||||
|
|
||||||
export { Label }
|
export { Label }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue