바로 포폴 정상화화

This commit is contained in:
암냥 2025-02-10 06:13:31 +09:00
commit de09401b68
45 changed files with 4401 additions and 1800 deletions

View file

@ -0,0 +1,97 @@
import { useState, useEffect } from "react";
import { Send, AlignJustify, BadgeCheck, House, CircleHelp, ChartGantt, PhoneCall } from "lucide-react";
import { Link, useLocation } from "react-router";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export default function BottomBar() {
const location = useLocation();
const [email, setEmail] = useState<string>('me@imnya.ng');
useEffect(() => {
const emaillist = ['me', 'mail', 'not', 'cat', 'neko', 'meow', 'heart']
const domainlist = ['imnya.ng', 'al-1s.kr']
// furry is 0.001%
const randomEmail = () => {
const random = Math.floor(Math.random() * 1000);
if (random === 0) {
setEmail(`furry@${domainlist[Math.floor(Math.random() * domainlist.length)]}`);
}
else {
setEmail(`${emaillist[Math.floor(Math.random() * emaillist.length)]}@${domainlist[Math.floor(Math.random() * domainlist.length)]}`);
}
}
randomEmail();
}, []);
return (
<div className="w-full flex justify-center fixed bottom-0 z-50">
<header className="bg-background/75 text-foreground w-full md:w-[50%] h-12 border rounded-full select-none m-4 mt-2">
<div className="flex items-center justify-between w-full h-full py-4 px-8">
<Link to={`mailto:${email}`} className="flex flex-row gap-4"><Send width={16} /> {email}</Link>
<div className="flex flex-row items-center justify-center">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button><AlignJustify /></button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem asChild>
<div className="flex flex-row gap-2 items-center">
{location.hash === "#top" ? (
<BadgeCheck />
) : (
<House />
)}
<Link to="/#top">Home</Link>
</div>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<div className="flex flex-row gap-2 items-center">
{location.hash === "#top" ? (
<BadgeCheck />
) : (
<CircleHelp />
)}
<Link to="/#about">About</Link>
</div>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<div className="flex flex-row gap-2 items-center">
{location.hash === "#top" ? (
<BadgeCheck />
) : (
<ChartGantt />
)}
<Link to="/#timeline">Timeline</Link>
</div>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<div className="flex flex-row gap-2 items-center">
{location.hash === "#top" ? (
<BadgeCheck />
) : (
<PhoneCall />
)}
<Link to="/#contact">Contact</Link>
</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuLabel>© 2021-2025 imnyang</DropdownMenuLabel>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</header>
</div>
)
}

View file

@ -0,0 +1,80 @@
import { useEffect, useState, useRef } from "react";
import { Link } from "react-router";
export default function About() {
const [count, setCount] = useState<number>(0);
const [isVisible, setIsVisible] = useState<boolean>(false);
const aboutRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
}
},
{ threshold: 0.1 }
);
if (aboutRef.current) {
observer.observe(aboutRef.current);
}
return () => {
if (aboutRef.current) {
observer.unobserve(aboutRef.current);
}
};
}, []);
useEffect(() => {
if (isVisible && count < 15) {
const timer = setTimeout(() => setCount(count + 1), count === 0 ? 600 : 25);
return () => clearTimeout(timer);
}
}, [isVisible, count]);
const [posts, setPosts] = useState<any[]>([]);
useEffect(() => {
fetch("https://api.imnya.ng/rss", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
})
.then(response => response.json())
.then(data => {
if (data) {
setPosts(data.slice(0, 3));
} else {
console.error("Error: data is undefined");
}
})
.catch(error => console.error("Error fetching posts:", error));
}, []);
return (
<div ref={aboutRef} className="w-full h-screen flex flex-col items-center justify-center">
<div className="w-full md:w-[50%] p-4">
<h1 className="text-2xl font-bold">About</h1>
<p className="mt-2">! .</p>
<p className="mt-2"> <a href="https://sqlare.com">Sqlare</a>, <a href="https://orygonix.com">TEAM ORYGON:IX</a> .</p>
</div>
<div className="w-full md:w-[50%] p-4">
<strong> </strong>
<p>{count}</p>
</div>
<div className="w-full md:w-[50%] p-4">
<strong> </strong>
<ul>
{posts.map((post, index) => (
<li key={index}>
<Link to={post.link}>{post.title}</Link>
</li>
))}
</ul>
</div>
</div>
);
}

View file

@ -0,0 +1,42 @@
import { Github, Instagram, Rss } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
export default function Contact() {
return (
<div className="w-full h-screen flex items-center justify-center">
<div className="w-full md:w-[50%] p-4 flex items-center justify-center flex-row gap-4">
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<a href="https://github.com/imnyang" target="_blank" rel="noreferrer" className="flex flex-row gap-4"><Github /></a>
</TooltipTrigger>
<TooltipContent className="px-2 py-1 text-xs">Github</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<a href="https://x.com/fur_local" target="_blank" rel="noreferrer" className="flex flex-row gap-4 text-3xl">𝕏</a>
</TooltipTrigger>
<TooltipContent className="px-2 py-1 text-xs">𝕏</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<a href="https://instagram.com/loopback.ip" target="_blank" rel="noreferrer" className="flex flex-row gap-4"><Instagram /></a>
</TooltipTrigger>
<TooltipContent className="px-2 py-1 text-xs">Instagram</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<a href="https://blog.imnya.ng" target="_blank" rel="noreferrer" className="flex flex-row gap-4"><Rss /></a>
</TooltipTrigger>
<TooltipContent className="px-2 py-1 text-xs">Blog</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
);
}

View file

@ -0,0 +1,170 @@
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
import { Link } from "react-router";
const events = [
{
date: "2025-01-19",
description:
"2024 Sunrin LOGCON(TeamLog 주최) 중등부 3위",
category: "Award",
link: "https://teamlog.kr"
},
{
date: "2025-01-12",
description:
"2024 Sunrin Layer7 CTF 중등부 2위",
category: "Award",
link: "https://layer7.kr"
},
{
date: "2024-12-14",
description:
"2024 글로벌스타트업학교 K-청소년스타트업 경진대회 우수상 수상",
category: "Award",
link: "https://www.ncf.or.kr/projects/'2024-%EA%B8%80%EB%A1%9C%EB%B2%8C%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85%ED%95%99%EA%B5%90-k-%EC%B2%AD%EC%86%8C%EB%85%84%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85-%EA%B2%BD%EC%A7%84%EB%8C%80%ED%9A%8C'-%EC%B0%B8%EA%B0%80%EC%9E%90-%EB%AA%A8%EC%A7%91",
},
{
date: "2024-12-07",
description: "글로벌 스타트업 학교 팀 1위",
category: "Award",
link: "https://ncf.or.kr",
},
{
date: "2024-12-07",
description: "글로벌 스타트업 학교 개인 최우수상",
category: "Award",
link: "https://ncf.or.kr",
},
{
date: "2024-08-18",
description: "29회 해킹캠프 CTF 1위 (고민중독)",
category: "Award & Conference",
link: "https://ctf.hackingcamp.org/",
},
{
date: "2024-08-01",
description:
"글로벌 스타트업 학교 2기 베트남 해외 연수 데모데이 대상 (1위)",
category: "Award",
link: "http://ncf.or.kr",
},
{
date: "2024-05-16",
description: "글로벌 스타트업 학교 2기 합격",
category: "Education",
link: "http://ncf.or.kr",
},
{
date: "2024-05-11",
description: "LG AI 청소년 캠프 1기 LG 탐색상 수상",
category: "Award",
link: "https://lgaiyouthcamp.or.kr/",
},
{
date: "2024-05-11",
description: "LG AI 청소년 캠프 1기 수료",
category: "Award",
link: "https://lgaiyouthcamp.or.kr/",
},
{
date: "2024-03-24",
description: "Dreamhack #133",
link: "https://dreamhack.io/users/40116/wargame",
},
{
date: "2023-12-20",
description: "LG AI 청소년 캠프 1기 합격",
category: "Education",
},
{
date: "2023-11-14",
description: "인천상정중학교 2023학년도 SW 문제 해결 활동 우수상(2위) 수여",
category: "Award",
},
{
date: "2023-09-02",
description:
"선린인터넷고등학교 제6회 소프트웨어나눔축제 Layer7 부서 과정 이수",
category: "Education",
},
{
date: "2023-07-24",
description: "한국정보기술연구원이 주도하는 사이버 가디언즈 보안캠프 수료",
category: "Education",
},
{
date: "2023-05-15",
description: "한국 코드페어 예선 진출",
category: "Award",
},
{
date: "2022-12-20",
description: "2022 SW영재 창작대회 은상 수상",
category: "Award",
},
{
date: "2022-09-27",
description: "2022 삼성 주니어 SW 창작대회 본선 진출",
category: "Award",
},
{
date: "2022-05-23",
description: "2022학년도 석정초SW영재학급 첫 수업",
category: "Education",
},
{
date: "2022-07-26",
description: "제 14회 맑은하늘 맑은웃음 공모전에서 맑은웃음상 수여",
category: "Award",
},
{
date: "2021-11-14",
description: "Become a ZEPETO Creator 이수",
category: "Education",
},
{
date: "2021-05-19",
description:
"소프트웨어와 전자신문이 주관한 소프트웨어재단 꿈찾기 캠프 이수",
category: "Education",
},
{
date: "2018-01-27",
description:
"제4회 맑은하늘 맑은웃음 어린이 문예공모전에서 위닉스상(2위) 수여",
category: "Award",
},
];
export default function Timeline() {
return (
<div className="w-full flex flex-col items-center justify-center p-4">
<div className="w-full h-screen md:p-16 md:pb-16 flex flex-col items-center justify-center">
<h1 className="text-2xl font-bold mb-4 w-full">Timeline (Beta)</h1>
<ScrollArea className="w-full h-[85%] whitespace-nowrap rounded-md border">
<div className="flex w-max space-x-4 p-4">
{Array.from(new Set(events.map(event => new Date(event.date).getFullYear()))).sort((a, b) => b - a).map(year => (
<div key={year} className="mb-6">
<h2 className="text-3xl font-bold text-transparent opacity-60 select-none" style={{ WebkitTextStrokeWidth: '1px', WebkitTextStrokeColor: 'var(--foreground)' }}>{year}</h2>
{events.filter(event => new Date(event.date).getFullYear() === year).map((event, index) => (
<div key={index} className="my-2">
<p className="text-lg font-semibold">{new Date(event.date).toLocaleDateString('ko-KR', { month: '2-digit', day: '2-digit' })}</p>
{event.link ? (
<Link to={event.link} className="text-md">{event.description}</Link>
) : (
<span className="text-md">{event.description}</span>
)}
</div>
))}
</div>
))}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
</div>
</div>
);
}

View file

@ -0,0 +1,37 @@
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
export default function Top() {
return (
<div className="bg-background text-foreground w-full h-screen flex items-center justify-center">
<div className="flex flex-col md:flex-row w-full md:w-[50%] h-full py-16 md:py-32">
<div className="w-full md:w-[40%] h-full flex flex-col justify-center items-center md:items-end md:pr-16">
<div className="text-center md:text-right">
<h1 className="text-4xl font-bold">Nam<br/>HyunSuk</h1>
<p className="mt-4 text-xl">
<br/> .
</p>
</div>
</div>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<div className="w-full md:w-[60%] h-full flex justify-center md:justify-end items-end bg-transparent md:bg-[#654e5c] rounded-3xl mt-8 md:mt-0 select-none">
<img
src="https://f.imnya.ng/profile/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.avif"
className="w-[60%] max-w-[360px] select-none pointer-events-none"
title="Special Thanks to @dob2_"
/>
</div>
</TooltipTrigger>
<TooltipContent align="end" side="bottom" className="px-2 py-1 text-xs">Special Thanks to @dob2_</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
);
}

View file

@ -0,0 +1,73 @@
import { createContext, useContext, useEffect, useState } from "react"
type Theme = "dark" | "light" | "system"
type ThemeProviderProps = {
children: React.ReactNode
defaultTheme?: Theme
storageKey?: string
}
type ThemeProviderState = {
theme: Theme
setTheme: (theme: Theme) => void
}
const initialState: ThemeProviderState = {
theme: "system",
setTheme: () => null,
}
const ThemeProviderContext = createContext<ThemeProviderState>(initialState)
export function ThemeProvider({
children,
defaultTheme = "system",
storageKey = "vite-ui-theme",
...props
}: ThemeProviderProps) {
const [theme, setTheme] = useState<Theme>(
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
)
useEffect(() => {
const root = window.document.documentElement
root.classList.remove("light", "dark")
if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light"
root.classList.add(systemTheme)
return
}
root.classList.add(theme)
}, [theme])
const value = {
theme,
setTheme: (theme: Theme) => {
localStorage.setItem(storageKey, theme)
setTheme(theme)
},
}
return (
<ThemeProviderContext.Provider {...props} value={value}>
{children}
</ThemeProviderContext.Provider>
)
}
export const useTheme = () => {
const context = useContext(ThemeProviderContext)
if (context === undefined)
throw new Error("useTheme must be used within a ThemeProvider")
return context
}

View file

@ -0,0 +1,199 @@
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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 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}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"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 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}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}

View file

@ -0,0 +1,46 @@
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }

View file

@ -0,0 +1,59 @@
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { cn } from "@/lib/utils"
function TooltipProvider({
delayDuration = 0,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
return (
<TooltipPrimitive.Provider
data-slot="tooltip-provider"
delayDuration={delayDuration}
{...props}
/>
)
}
function Tooltip({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return (
<TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
</TooltipProvider>
)
}
function TooltipTrigger({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
}
function TooltipContent({
className,
sideOffset = 4,
children,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
return (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
data-slot="tooltip-content"
sideOffset={sideOffset}
className={cn(
"bg-primary text-primary-foreground 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 z-50 max-w-sm rounded-md px-3 py-1.5 text-xs",
className
)}
{...props}
>
{children}
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
)
}
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }