diff --git a/README.md b/README.md index e145bd5..e215bc4 100644 --- a/README.md +++ b/README.md @@ -1 +1,36 @@ -# griffith1deady.github.io \ No newline at end of file +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +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. + +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/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## 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/app/building-your-application/deploying) for more details. diff --git a/public/beatifulGirl.jpg b/app/beatifulGirl.jpg similarity index 100% rename from public/beatifulGirl.jpg rename to app/beatifulGirl.jpg diff --git a/public/beatifulGirl1.jpg b/app/beatifulGirl1.jpg similarity index 100% rename from public/beatifulGirl1.jpg rename to app/beatifulGirl1.jpg diff --git a/public/beatifulGirl2.jpg b/app/beatifulGirl2.jpg similarity index 100% rename from public/beatifulGirl2.jpg rename to app/beatifulGirl2.jpg diff --git a/public/beatifulGirl3.jpg b/app/beatifulGirl3.jpg similarity index 100% rename from public/beatifulGirl3.jpg rename to app/beatifulGirl3.jpg diff --git a/public/beatifulGirl4.jpg b/app/beatifulGirl4.jpg similarity index 100% rename from public/beatifulGirl4.jpg rename to app/beatifulGirl4.jpg diff --git a/public/beatifulGirl5.jpg b/app/beatifulGirl5.jpg similarity index 100% rename from public/beatifulGirl5.jpg rename to app/beatifulGirl5.jpg diff --git a/public/beatifulGirl6.jpg b/app/beatifulGirl6.jpg similarity index 100% rename from public/beatifulGirl6.jpg rename to app/beatifulGirl6.jpg diff --git a/app/favicon.ico b/app/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/app/favicon.ico differ diff --git a/app/fonts/GeistMonoVF.woff b/app/fonts/GeistMonoVF.woff new file mode 100644 index 0000000..f2ae185 Binary files /dev/null and b/app/fonts/GeistMonoVF.woff differ diff --git a/app/fonts/GeistVF.woff b/app/fonts/GeistVF.woff new file mode 100644 index 0000000..1b62daa Binary files /dev/null and b/app/fonts/GeistVF.woff differ diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..1849479 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,28 @@ +@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;700&display=swap'); + +@tailwind base; +@tailwind components; +@tailwind utilities; + +* { + font-family: 'Nunito', serif; +} + +body { + font-family: 'Nunito', serif; +} + +html { + scroll-behavior: smooth; +} + +main { + display: block; +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} + diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..f7ace43 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,39 @@ +"use client"; + +import "./globals.css"; +import {ThemeProvider} from "@/components/theme-provider"; +import NavbarComponent from "@/components/layout/NavbarComponent"; +import React from "react"; +import {Box, Container} from "@chakra-ui/react"; +import {Toaster} from "sonner"; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + + + + + + + {children} + + + + + + + ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..f726da5 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,191 @@ +"use client"; + +import {AspectRatio} from "@/components/ui/aspect-ratio"; +import Image from "next/image"; +import { toast } from "sonner" + +import background from "@/app/spare.png"; +import myGirlfriend from "@/app/beatifulGirl.jpg"; +import myGirlfriendOne from "@/app/beatifulGirl1.jpg"; +import myGirlfriendTwo from "@/app/beatifulGirl2.jpg"; +import myGirlfriendThree from "@/app/beatifulGirl3.jpg"; +import myGirlfriendFour from "@/app/beatifulGirl4.jpg"; +import myGirlfriendFive from "@/app/beatifulGirl5.jpg"; +import myGirlfriendSix from "@/app/beatifulGirl6.jpg"; + +import {Box, Center, Container, SimpleGrid, StackSeparator, VStack} from "@chakra-ui/react"; +import {Button} from "@/components/ui/button"; +import Link from "next/link"; + +import { Card, CardContent } from "@/components/ui/card" +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "@/components/ui/carousel" + +export default function Home() { + return ( + + } + > + + + griffith1deady + + + +

+ griffith1deady +

+

+ 18 y/o, Software Developer, Web Developer, Gamer, Linux + Enthusiast, and a little bit of everything. +

+
+
+ +
+
+ "Fork in the eye or in the ass, which one?" +
+
+ + @someone + +
+
+ "I don't see that you have one eye." +
+
+ + @me + +
+
+
+ + + + + + + + + + + +

+ Buy me a beer! +

+
+
+ I don't like beer, but I wouldn't mind if you donate to me for beer. After all, I need something to survive on, as developing cheats for games and my own business cards doesn't give me the money I would like :) +
+
+ + @me, give me a beer! + +
+
+ + + + + +
+ +

+ Beautiful girl! +

+
+ my-girlfriend +
+
+
+ The most perfect girl I've ever met in my entire life. Even though things don't always go as smoothly as we would like, or as many people imagine the ideal relationship to be, but it's a great relationship, and the most beautiful girl, and most importantly, mine. :) I have so many words and ideas for variations on how to write about how beautiful she is - but the only thing that comes to mind is that I constantly want to see her beautiful sweet smile, for which I am willing to do anything. I may be silly, I would even say foolish, but I love her. She is my beautiful ray of sunshine :) She's my beautiful little kitten, very, very fluffy, who I just want to cuddle and cuddle, you have no idea how much :) +
+
+ + @me, sayed that some one year ago :) + +
+
+
+ + + {Array.from([myGirlfriendOne, myGirlfriendTwo, myGirlfriendThree, myGirlfriendFour, myGirlfriendFive, myGirlfriendSix]).map((image, index) => ( + +
+ + + my-girlfriend + + +
+
+ ))} +
+ + +
+
+
+
+ ); +} diff --git a/public/spare.png b/app/spare.png similarity index 100% rename from public/spare.png rename to app/spare.png diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..f863811 Binary files /dev/null and b/bun.lockb differ diff --git a/components.json b/components.json new file mode 100644 index 0000000..6af4ad0 --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": false, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/components/AppBlock.tsx b/components/AppBlock.tsx deleted file mode 100644 index acd3166..0000000 --- a/components/AppBlock.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Box, ChakraProps } from "@chakra-ui/react"; -import { motion } from "framer-motion"; -import { ReactElement } from "react"; - -export type AppBlockProps = { - children: ReactElement | Array | string; - delay: number; -} & ChakraProps; - -const AppBlock = ({ children, delay, ...props }: AppBlockProps) => { - return ( - - - {children} - - - ); -}; - -export default AppBlock; diff --git a/components/AppNavBar.tsx b/components/AppNavBar.tsx deleted file mode 100644 index 6ce7022..0000000 --- a/components/AppNavBar.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import {Box, Container, Flex, Heading,} from "@chakra-ui/react"; -import {useRouter} from "next/router"; -import AppNavBarLink from "./AppNavBarLink"; - -const AppNavBar = () => { - const router = useRouter() - return ( - - - - - router.push('/')}> - griffith1deady - - - - - - ); -}; - -export default AppNavBar; diff --git a/components/AppNavBarLink.tsx b/components/AppNavBarLink.tsx deleted file mode 100644 index a958178..0000000 --- a/components/AppNavBarLink.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { ChakraProps, Link } from "@chakra-ui/react"; -import NextLink from "next/link"; -import React, { ReactElement } from "react"; - -export type AppNavBarLinkProps = { - href: string; - children: ReactElement | string; - target?: string; -} & ChakraProps; - -const AppNavBarLink = ({ - href, - children, - target, - ...props -}: AppNavBarLinkProps) => { - return ( - // @ts-ignore - - - {children} - - - ); -}; - -export default AppNavBarLink; diff --git a/components/Fonts.tsx b/components/Fonts.tsx deleted file mode 100644 index 4b0e45a..0000000 --- a/components/Fonts.tsx +++ /dev/null @@ -1,9 +0,0 @@ -const Fonts = () => { - return ( - - ); -}; - -export default Fonts; diff --git a/components/FooterComponent.tsx b/components/FooterComponent.tsx deleted file mode 100644 index 1e4be0f..0000000 --- a/components/FooterComponent.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import {Box, Container, Text} from "@chakra-ui/react"; - -const FooterComponent = () => { - return - - - 🛠 Builded by griffith1deady with Next / ChakraUI & VKUI - - - -} - -export default FooterComponent \ No newline at end of file diff --git a/components/PageHeader.tsx b/components/PageHeader.tsx deleted file mode 100644 index a5fe880..0000000 --- a/components/PageHeader.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import {Box, ChakraProps} from "@chakra-ui/react"; -import {motion} from "framer-motion"; -import {ReactElement} from "react"; - -export type PageHeaderProps = { - duration: number; - children: ReactElement | ReactElement[] | string; -} & ChakraProps; - -const PageHeader = ({duration, children, ...props}: PageHeaderProps) => { - return ( - - {children} - - ); -}; - -export default PageHeader; diff --git a/components/language/LanguageItem.tsx b/components/language/LanguageItem.tsx deleted file mode 100644 index 26da472..0000000 --- a/components/language/LanguageItem.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import {Language, StackItem} from "../../types"; -import {Box, Center, ChakraProps, Divider, List, ListIcon, ListItem, Progress, SimpleGrid} from "@chakra-ui/react"; -import {FaCodepen, FaJava, FaRust} from "react-icons/fa"; -import {FiHexagon} from "react-icons/fi"; -import {HTMLAttributes} from "react"; -import {DiDart} from "react-icons/di"; -import {TbBrandCpp, TbBrandGolang, TbBrandKotlin, TbBrandTypescript} from "react-icons/tb"; -import AppBlock from "../AppBlock"; - -type LanguageItemProps = { - language: Language - stackItems?: StackItem[] -} & ChakraProps & HTMLAttributes -const LanguageItem = ({language, stackItems, ...props}: LanguageItemProps) => { - const languageIcon = getIconForLanguage(language.name) - return (<> - - - {language.name} -
{language.description}
- - - - {stackItems ? - - {stackItems.map((stack, index) => ( - - - - {stack.name} - - {stack.additional ? : null} - - {stack.additional ? stack.additional.map((stack, index) => ( - - - {stack.name} - - )) : null} - - - ))} - - : null} -
- ) -} - -function getIconForLanguage(language: string) { - if (language == 'Java') { - return FaJava - } else if (language == 'Kotlin') { - return TbBrandKotlin - } else if (language == 'TypeScript') { - return TbBrandTypescript - } else if (language == 'Go') { - return TbBrandGolang - } else if (language == 'C++') { - return TbBrandCpp - } else if (language == 'Rust') { - return FaRust - } else if (language == 'Dart') { - return DiDart - } else { - return FiHexagon - } -} - -export default LanguageItem \ No newline at end of file diff --git a/components/layout/NavbarComponent.tsx b/components/layout/NavbarComponent.tsx new file mode 100644 index 0000000..aab602c --- /dev/null +++ b/components/layout/NavbarComponent.tsx @@ -0,0 +1,19 @@ +import {Box, Container, Flex} from "@chakra-ui/react"; +import {Button} from "@/components/ui/button"; +import Link from "next/link"; +import {ColorModeButton} from "@/components/ui/color-mode"; + +export default function NavbarComponent() { + return ( + + + + + + + + + ) +} \ No newline at end of file diff --git a/components/layouts/AnimationLayout.tsx b/components/layouts/AnimationLayout.tsx deleted file mode 100644 index b4057ad..0000000 --- a/components/layouts/AnimationLayout.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { ChakraProps } from "@chakra-ui/react"; -import { AnimatePresence, motion } from "framer-motion"; -import { ReactElement } from "react"; - -export type AnimationLayoutProps = { - route: string; - children: ReactElement | ReactElement[] | string; -} & ChakraProps; - -const AnimationLayout = ({ route, children }: AnimationLayoutProps) => { - const variants = { - hidden: { opacity: 0, x: 0, y: 0 }, - enter: { opacity: 1, x: 0, y: 0 }, - exit: { opacity: 0, x: -0, y: -0 }, - }; - - return ( - { - window.scrollTo(0, 0); - }} - > - - {children} - - - ); -}; - -export default AnimationLayout; diff --git a/components/layouts/AppLayout.tsx b/components/layouts/AppLayout.tsx deleted file mode 100644 index 64be50c..0000000 --- a/components/layouts/AppLayout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import {Box, Container} from "@chakra-ui/react"; -import {ReactElement} from "react"; -import AppNavBar from "../AppNavBar"; -import FooterComponent from "../FooterComponent"; - -const AppLayout = ({children}: { children: ReactElement }) => { - return ( - - - - {children} - - - - ); -}; - -export default AppLayout; diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx new file mode 100644 index 0000000..723e9f5 --- /dev/null +++ b/components/theme-provider.tsx @@ -0,0 +1,13 @@ +"use client" + +import * as React from "react" +import { ThemeProvider as NextThemesProvider } from "next-themes" +import {Provider} from "@/components/ui/provider"; + +export function ThemeProvider({children, ...props}: React.ComponentProps) { + return ( + + {children} + + ) +} diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx new file mode 100644 index 0000000..24c788c --- /dev/null +++ b/components/ui/accordion.tsx @@ -0,0 +1,58 @@ +"use client" + +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) + +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/components/ui/aspect-ratio.tsx b/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..d6a5226 --- /dev/null +++ b/components/ui/aspect-ratio.tsx @@ -0,0 +1,7 @@ +"use client" + +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" + +const AspectRatio = AspectRatioPrimitive.Root + +export { AspectRatio } diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..cd84664 --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,74 @@ +"use client" + +import type { GroupProps, SlotRecipeProps } from "@chakra-ui/react" +import { Avatar as ChakraAvatar, Group } from "@chakra-ui/react" +import * as React from "react" + +type ImageProps = React.ImgHTMLAttributes + +export interface AvatarProps extends ChakraAvatar.RootProps { + name?: string + src?: string + srcSet?: string + loading?: ImageProps["loading"] + icon?: React.ReactElement + fallback?: React.ReactNode +} + +export const Avatar = React.forwardRef( + function Avatar(props, ref) { + const { name, src, srcSet, loading, icon, fallback, children, ...rest } = + props + return ( + + + {fallback} + + + {children} + + ) + }, +) + +interface AvatarFallbackProps extends ChakraAvatar.FallbackProps { + name?: string + icon?: React.ReactElement +} + +const AvatarFallback = React.forwardRef( + function AvatarFallback(props, ref) { + const { name, icon, children, ...rest } = props + return ( + + {children} + {name != null && children == null && <>{getInitials(name)}} + {name == null && children == null && ( + {icon} + )} + + ) + }, +) + +function getInitials(name: string) { + const names = name.trim().split(" ") + const firstName = names[0] != null ? names[0] : "" + const lastName = names.length > 1 ? names[names.length - 1] : "" + return firstName && lastName + ? `${firstName.charAt(0)}${lastName.charAt(0)}` + : firstName.charAt(0) +} + +interface AvatarGroupProps extends GroupProps, SlotRecipeProps<"avatar"> {} + +export const AvatarGroup = React.forwardRef( + function AvatarGroup(props, ref) { + const { size, variant, borderless, ...rest } = props + return ( + + + + ) + }, +) diff --git a/components/ui/blockquote.tsx b/components/ui/blockquote.tsx new file mode 100644 index 0000000..166446b --- /dev/null +++ b/components/ui/blockquote.tsx @@ -0,0 +1,31 @@ +import { Blockquote as ChakraBlockquote } from "@chakra-ui/react" +import * as React from "react" + +export interface BlockquoteProps extends ChakraBlockquote.RootProps { + cite?: React.ReactNode + citeUrl?: string + icon?: React.ReactNode + showDash?: boolean +} + +export const Blockquote = React.forwardRef( + function Blockquote(props, ref) { + const { children, cite, citeUrl, showDash, icon, ...rest } = props + + return ( + + {icon} + + {children} + + {cite && ( + + {showDash ? <>— : null} {cite} + + )} + + ) + }, +) + +export const BlockquoteIcon = ChakraBlockquote.Icon diff --git a/components/ui/button.tsx b/components/ui/button.tsx new file mode 100644 index 0000000..fe539b0 --- /dev/null +++ b/components/ui/button.tsx @@ -0,0 +1,56 @@ +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 gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300", + { + variants: { + variant: { + default: "bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90", + destructive: + "bg-red-500 text-neutral-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90", + outline: + "border border-neutral-200 bg-white hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", + secondary: + "bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80", + ghost: "hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", + link: "text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50", + }, + 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, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/components/ui/card.tsx b/components/ui/card.tsx new file mode 100644 index 0000000..2945509 --- /dev/null +++ b/components/ui/card.tsx @@ -0,0 +1,79 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/components/ui/carousel.tsx b/components/ui/carousel.tsx new file mode 100644 index 0000000..ec505d0 --- /dev/null +++ b/components/ui/carousel.tsx @@ -0,0 +1,262 @@ +"use client" + +import * as React from "react" +import useEmblaCarousel, { + type UseEmblaCarouselType, +} from "embla-carousel-react" +import { ArrowLeft, ArrowRight } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" + +type CarouselApi = UseEmblaCarouselType[1] +type UseCarouselParameters = Parameters +type CarouselOptions = UseCarouselParameters[0] +type CarouselPlugin = UseCarouselParameters[1] + +type CarouselProps = { + opts?: CarouselOptions + plugins?: CarouselPlugin + orientation?: "horizontal" | "vertical" + setApi?: (api: CarouselApi) => void +} + +type CarouselContextProps = { + carouselRef: ReturnType[0] + api: ReturnType[1] + scrollPrev: () => void + scrollNext: () => void + canScrollPrev: boolean + canScrollNext: boolean +} & CarouselProps + +const CarouselContext = React.createContext(null) + +function useCarousel() { + const context = React.useContext(CarouselContext) + + if (!context) { + throw new Error("useCarousel must be used within a ") + } + + return context +} + +const Carousel = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & CarouselProps +>( + ( + { + orientation = "horizontal", + opts, + setApi, + plugins, + className, + children, + ...props + }, + ref + ) => { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === "horizontal" ? "x" : "y", + }, + plugins + ) + const [canScrollPrev, setCanScrollPrev] = React.useState(false) + const [canScrollNext, setCanScrollNext] = React.useState(false) + + const onSelect = React.useCallback((api: CarouselApi) => { + if (!api) { + return + } + + setCanScrollPrev(api.canScrollPrev()) + setCanScrollNext(api.canScrollNext()) + }, []) + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev() + }, [api]) + + const scrollNext = React.useCallback(() => { + api?.scrollNext() + }, [api]) + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "ArrowLeft") { + event.preventDefault() + scrollPrev() + } else if (event.key === "ArrowRight") { + event.preventDefault() + scrollNext() + } + }, + [scrollPrev, scrollNext] + ) + + React.useEffect(() => { + if (!api || !setApi) { + return + } + + setApi(api) + }, [api, setApi]) + + React.useEffect(() => { + if (!api) { + return + } + + onSelect(api) + api.on("reInit", onSelect) + api.on("select", onSelect) + + return () => { + api?.off("select", onSelect) + } + }, [api, onSelect]) + + return ( + +
+ {children} +
+
+ ) + } +) +Carousel.displayName = "Carousel" + +const CarouselContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { carouselRef, orientation } = useCarousel() + + return ( +
+
+
+ ) +}) +CarouselContent.displayName = "CarouselContent" + +const CarouselItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { orientation } = useCarousel() + + return ( +
+ ) +}) +CarouselItem.displayName = "CarouselItem" + +const CarouselPrevious = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollPrev, canScrollPrev } = useCarousel() + + return ( + + ) +}) +CarouselPrevious.displayName = "CarouselPrevious" + +const CarouselNext = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollNext, canScrollNext } = useCarousel() + + return ( + + ) +}) +CarouselNext.displayName = "CarouselNext" + +export { + type CarouselApi, + Carousel, + CarouselContent, + CarouselItem, + CarouselPrevious, + CarouselNext, +} diff --git a/components/ui/chart.tsx b/components/ui/chart.tsx new file mode 100644 index 0000000..96773e3 --- /dev/null +++ b/components/ui/chart.tsx @@ -0,0 +1,366 @@ +"use client" + +import * as React from "react" +import * as RechartsPrimitive from "recharts" + +import { cn } from "@/lib/utils" + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: "", dark: ".dark" } as const + +export type ChartConfig = { + [k in string]: { + label?: React.ReactNode + icon?: React.ComponentType + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ) +} + +type ChartContextProps = { + config: ChartConfig +} + +const ChartContext = React.createContext(null) + +function useChart() { + const context = React.useContext(ChartContext) + + if (!context) { + throw new Error("useChart must be used within a ") + } + + return context +} + +const ChartContainer = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + config: ChartConfig + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"] + } +>(({ id, className, children, config, ...props }, ref) => { + const uniqueId = React.useId() + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` + + return ( + +
+ + + {children} + +
+
+ ) +}) +ChartContainer.displayName = "Chart" + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ([_, config]) => config.theme || config.color + ) + + if (!colorConfig.length) { + return null + } + + return ( +