feat: pricing page, navigation menu, footer

This commit is contained in:
mehedi-hasan 2024-04-09 02:01:54 +06:00
parent 8a6d8dbfff
commit 985da98aee
13 changed files with 809 additions and 0 deletions

37
package-lock.json generated
View File

@ -21,6 +21,7 @@
"@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-scroll-area": "^1.0.5",
@ -2423,6 +2424,42 @@
} }
} }
}, },
"node_modules/@radix-ui/react-navigation-menu": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.1.4.tgz",
"integrity": "sha512-Cc+seCS3PmWmjI51ufGG7zp1cAAIRqHVw7C9LOA2TZ+R4hG6rDvHcTqIsEEFLmZO3zNVH72jOOE7kKNy8W+RtA==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.1",
"@radix-ui/react-collection": "1.0.3",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-context": "1.0.1",
"@radix-ui/react-direction": "1.0.1",
"@radix-ui/react-dismissable-layer": "1.0.5",
"@radix-ui/react-id": "1.0.1",
"@radix-ui/react-presence": "1.0.1",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-use-callback-ref": "1.0.1",
"@radix-ui/react-use-controllable-state": "1.0.1",
"@radix-ui/react-use-layout-effect": "1.0.1",
"@radix-ui/react-use-previous": "1.0.1",
"@radix-ui/react-visually-hidden": "1.0.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover": { "node_modules/@radix-ui/react-popover": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz",

View File

@ -23,6 +23,7 @@
"@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-scroll-area": "^1.0.5",

22
src/app/(app)/layout.tsx Normal file
View File

@ -0,0 +1,22 @@
import Footer from "@/components/footer";
import Navbar from "@/components/navbar";
import { Metadata } from "next";
import React from "react";
export const metadata: Metadata = {
title: "Skilled Ai",
description: "Skilled Ai",
};
const Layout = ({ children }: { children: React.ReactNode }) => {
return (
<div className="flex flex-col min-h-screen">
<Navbar />
<main className="grow max-w-7xl mx-auto px-4">{children}</main>
<Footer />
</div>
);
};
export default Layout;

View File

@ -0,0 +1,72 @@
"use client";
import React, { useState } from "react";
import PricingHeader from "@/components/pricing/pricing-header";
import PricingSwitch from "@/components/pricing/pricing-switch";
import PricingCard from "@/components/pricing/pricing-card";
export default function Page() {
const [isYearly, setIsYearly] = useState(false);
const togglePricingPeriod = (value: string) =>
setIsYearly(parseInt(value) === 1);
const plans = [
{
title: "Basic",
monthlyPrice: 10,
yearlyPrice: 100,
description: "Essential features you need to get started",
features: [
"Full access to our extensive library of courses and tutorials",
"Regularly updated content to keep you ahead of the curve",
"Skill assessments and progress tracking",
"Community forums for networking and support",
"Certificate of completion for each course",
],
actionLabel: "Get Started",
},
{
title: "Pro",
monthlyPrice: 25,
yearlyPrice: 250,
description: "Perfect for owners of small & medium businessess",
features: [
"All features included in the Basic tier",
"Exclusive access to advanced courses and workshops",
"Personalized career coaching sessions",
"Priority customer support",
"Premium resources such as e-books and industry reports",
],
actionLabel: "Get Started",
popular: true,
},
{
title: "Enterprise",
price: "Custom",
description: "Dedicated support and infrastructure to fit your needs",
features: [
"Tailored solutions for businesses and teams",
"Dedicated account management",
"Customizable learning paths and content curation",
"Team analytics and progress tracking",
"On-site workshops and training sessions (optional)",
],
actionLabel: "Contact Sales",
exclusive: true,
},
];
return (
<div className="py-8 md:py-24">
<PricingHeader
title="Welcome to Skilled AI Pricing"
subtitle="At Skilled AI, we empower software engineers with top-notch upskilling resources tailored to their career goals. Explore our pricing plans below and take the next step towards mastering your craft."
/>
<PricingSwitch onSwitch={togglePricingPeriod} />
<section className="flex flex-col sm:flex-row sm:flex-wrap justify-center gap-8 mt-8">
{plans.map((plan) => {
return <PricingCard key={plan.title} {...plan} isYearly={isYearly} />;
})}
</section>
</div>
);
}

223
src/components/footer.tsx Normal file
View File

@ -0,0 +1,223 @@
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import Link from "next/link";
export default function Footer() {
return (
<div>
<section className="w-full py-12 md:20 border-t">
<div className="container px-4 md:px-6 flex flex-col items-center text-center">
<h2 className="text-2xl font-bold tracking-tighter sm:text-3xl md:text-4xl lg:text-5xl/none ">
Stay Connected
</h2>
<p className="mx-auto max-w-[700px] md:text-lg text-muted-foreground mt-2">
Subscribe to our newsletter and follow us on our social media.
</p>
<div className="w-full max-w-md space-y-2 my-4">
<form className="flex space-x-2">
<Input
className="max-w-lg flex-1 "
placeholder="Enter your email"
type="email"
/>
<Button className="" type="submit" variant="outline">
Subscribe
</Button>
</form>
</div>
{/* <div className="flex justify-center space-x-4">
<Link aria-label="Facebook page" className="" href="#">
<FacebookIcon className="h-6 w-6" />
</Link>
<Link aria-label="Twitter profile" className="" href="#">
<TwitterIcon className="h-6 w-6" />
</Link>
<Link aria-label="Instagram profile" className="" href="#">
<InstagramIcon className="h-6 w-6" />
</Link>
<Link aria-label="LinkedIn profile" className="" href="#">
<LinkedinIcon className="h-6 w-6" />
</Link>
</div> */}
</div>
</section>
<footer className="border-t py-12 md:20">
<div className="max-w-7xl mx-auto px-4 grid grid-cols-4 gap-4">
<ul className="space-y-1">
<li>
<Link className="text-muted-foreground hover:underline" href="#">
About Us
</Link>
</li>
<li>
<Link className="text-muted-foreground hover:underline" href="#">
Contact Us
</Link>
</li>
<li>
<Link className="text-muted-foreground hover:underline" href="#">
FAQs
</Link>
</li>
<li>
<Link className="text-muted-foreground hover:underline" href="#">
Terms of Service
</Link>
</li>
</ul>
<ul className="space-y-1">
<li>
<Link className="text-muted-foreground hover:underline" href="#">
Privacy Policy
</Link>
</li>
<li>
<Link className="text-muted-foreground hover:underline" href="#">
Blog
</Link>
</li>
<li>
<Link className="text-muted-foreground hover:underline" href="#">
Careers
</Link>
</li>
<li>
<Link className="text-muted-foreground hover:underline" href="#">
Resources
</Link>
</li>
</ul>
<ul className="space-y-1">
<li>
<Link className="text-muted-foreground hover:underline" href="#">
Pricing
</Link>
</li>
<li>
<Link className="text-muted-foreground hover:underline" href="#">
Feedback
</Link>
</li>
<li>
<Link className="text-muted-foreground hover:underline" href="#">
Community Guidelines
</Link>
</li>
<li>
<Link className="text-muted-foreground hover:underline" href="#">
Cookie Policy
</Link>
</li>
</ul>
<div className="space-y-2">
<h4 className="font-semibold">Follow Us</h4>
<div className="flex space-x-4 text-muted-foreground">
<Link aria-label="Facebook page" className="" href="#">
<FacebookIcon className="h-6 w-6" />
</Link>
<Link aria-label="Twitter profile" className="" href="#">
<TwitterIcon className="h-6 w-6" />
</Link>
<Link aria-label="Instagram profile" className="" href="#">
<InstagramIcon className="h-6 w-6" />
</Link>
<Link aria-label="LinkedIn profile" className="" href="#">
<LinkedinIcon className="h-6 w-6" />
</Link>
</div>
</div>
</div>
<div className="max-w-7xl mx-auto px-4 flex justify-between mt-8 md:mt-16">
<p className="text-muted-foreground">
Skilled AI - Empowering Software Engineers to Thrive
</p>
<p className="text-muted-foreground">
Copyright © 2024 All Rights Reserved.
</p>
</div>
</footer>
</div>
);
}
function FacebookIcon(props: any) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" />
</svg>
);
}
function InstagramIcon(props: any) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect width="20" height="20" x="2" y="2" rx="5" ry="5" />
<path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" />
<line x1="17.5" x2="17.51" y1="6.5" y2="6.5" />
</svg>
);
}
function LinkedinIcon(props: any) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z" />
<rect width="4" height="12" x="2" y="9" />
<circle cx="4" cy="4" r="2" />
</svg>
);
}
function TwitterIcon(props: any) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z" />
</svg>
);
}

177
src/components/navbar.tsx Normal file
View File

@ -0,0 +1,177 @@
"use client";
import * as React from "react";
import Link from "next/link";
import { cn } from "@/lib/utils";
import { Icons } from "@/components/icons";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu";
import ThemeToggle from "./layout/ThemeToggle/theme-toggle";
import { buttonVariants } from "./ui/button";
const careerPaths: { title: string; href: string; description: string }[] = [
{
title: "Frontend Developer",
href: "#",
description:
"Master frontend technologies like HTML, CSS, and JavaScript to build dynamic and user-friendly web interfaces.",
},
{
title: "Backend Developer",
href: "#",
description:
"Learn server-side programming languages and frameworks to create scalable and efficient web applications.",
},
{
title: "Full Stack Developer",
href: "#",
description:
"Combine frontend and backend development skills to build end-to-end web applications from scratch.",
},
{
title: "Mobile App Developer",
href: "#",
description:
"Specialize in mobile app development for iOS or Android platforms using Swift, Kotlin, or cross-platform frameworks.",
},
{
title: "Game Developer",
href: "#",
description:
"Dive into game development with Unity or Unreal Engine, creating immersive gaming experiences for various platforms.",
},
{
title: "Machine Learning Engineer",
href: "#",
description:
"Develop machine learning models and algorithms to solve complex problems and make predictions based on data.",
},
];
export default function Navbar() {
return (
<header className="border-b py-3">
<div className="max-w-7xl mx-auto flex justify-between items-center px-4">
<Link href="/" className="inline-block w-fit">
<h2 className="text-xl font-bold">SkilledAI</h2>
</Link>
<div className="flex gap-8 items-center">
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Courses</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid gap-3 p-4 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]">
<li className="row-span-3">
<NavigationMenuLink asChild>
<a
className="flex h-full w-full select-none flex-col justify-end rounded-md bg-gradient-to-b from-muted/50 to-muted p-6 no-underline outline-none focus:shadow-md"
href="/"
>
<Icons.logo className="h-6 w-6" />
<div className="mb-2 mt-4 text-lg font-medium">
Skilled AI
</div>
<p className="text-sm leading-tight text-muted-foreground">
Browse our extensive catalog of courses covering a
wide range of topics in software engineering, from
programming languages to advanced technologies.
</p>
</a>
</NavigationMenuLink>
</li>
<ListItem href="#" title="Python Programming">
Learn the fundamentals of Python programming language,
from basic syntax to advanced concepts like
object-oriented programming.
</ListItem>
<ListItem href="#" title="Web Development with React.js:">
Master frontend web development using React.js library,
including state management, component-based architecture,
and modern UI design techniques.
</ListItem>
<ListItem href="#" title="Machine Learning Fundamentals">
Dive into the world of machine learning, covering topics
such as supervised and unsupervised learning, neural
networks, and model evaluation.
</ListItem>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>Career Paths</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
{careerPaths.map((path) => (
<ListItem
key={path.title}
title={path.title}
href={path.href}
>
{path.description}
</ListItem>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href="#" legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
Community
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href="/pricing" legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
Pricing
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
<Link className={buttonVariants({ variant: "secondary" })} href="/">
Sign In
</Link>
<ThemeToggle />
</div>
</div>
</header>
);
}
const ListItem = React.forwardRef<
React.ElementRef<"a">,
React.ComponentPropsWithoutRef<"a">
>(({ className, title, children, ...props }, ref) => {
return (
<li>
<NavigationMenuLink asChild>
<a
ref={ref}
className={cn(
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
className,
)}
{...props}
>
<div className="text-sm font-medium leading-none">{title}</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
{children}
</p>
</a>
</NavigationMenuLink>
</li>
);
});
ListItem.displayName = "ListItem";

View File

@ -0,0 +1,10 @@
import { CheckCircle2 } from "lucide-react"
const CheckItem = ({ text }: { text: string }) => (
<div className="flex gap-2">
<CheckCircle2 size={18} className="my-auto text-green-400" />
<p className="pt-0.5 text-zinc-700 dark:text-zinc-300 text-sm">{text}</p>
</div>
)
export default CheckItem

View File

@ -0,0 +1,100 @@
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import CheckItem from "./check-item";
type PricingCardProps = {
isYearly?: boolean;
title: string;
monthlyPrice?: number;
yearlyPrice?: number;
description: string;
features: string[];
actionLabel: string;
popular?: boolean;
exclusive?: boolean;
};
const PricingCard = ({
isYearly,
title,
monthlyPrice,
yearlyPrice,
description,
features,
actionLabel,
popular,
exclusive,
}: PricingCardProps) => (
<Card
className={cn(
`w-72 flex flex-col justify-between py-1 ${
popular ? "border border-primary" : "border"
} mx-auto sm:mx-0`,
{
"animate-background-shine bg-white dark:bg-[linear-gradient(110deg,#000103,45%,#1e2631,55%,#000103)] bg-[length:200%_100%] transition-colors":
exclusive,
},
)}
>
<div>
<CardHeader className="pb-8 pt-4">
{isYearly && yearlyPrice && monthlyPrice ? (
<div className="flex justify-between">
<CardTitle className="text-zinc-700 dark:text-zinc-300 text-lg">
{title}
</CardTitle>
<div
className={cn(
"px-2.5 rounded-xl h-fit text-sm py-1 bg-zinc-200 text-black dark:bg-zinc-800 dark:text-white",
{
"bg-gradient-to-r from-orange-400 to-rose-400 dark:text-black ":
popular,
},
)}
>
Save ${monthlyPrice * 12 - yearlyPrice}
</div>
</div>
) : (
<CardTitle className="text-zinc-700 dark:text-zinc-300 text-lg">
{title}
</CardTitle>
)}
<div className="flex gap-0.5">
<h3 className="text-3xl font-bold">
{yearlyPrice && isYearly
? "$" + yearlyPrice
: monthlyPrice
? "$" + monthlyPrice
: "Custom"}
</h3>
<span className="flex flex-col justify-end text-sm mb-1">
{yearlyPrice && isYearly ? "/year" : monthlyPrice ? "/month" : null}
</span>
</div>
<CardDescription className="pt-1.5 h-12">{description}</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-2">
{features.map((feature: string) => (
<CheckItem key={feature} text={feature} />
))}
</CardContent>
</div>
<CardFooter className="mt-2">
<Button className="relative inline-flex w-full items-center justify-center rounded-md bg-black text-white dark:bg-white px-6 font-medium dark:text-black transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:ring-offset-slate-50">
<div className="absolute -inset-0.5 -z-10 rounded-lg bg-gradient-to-b from-[#c7d2fe] to-[#8678f9] opacity-75 blur" />
{actionLabel}
</Button>
</CardFooter>
</Card>
);
export default PricingCard;

View File

@ -0,0 +1,9 @@
const PricingHeader = ({ title, subtitle }: { title: string; subtitle: string }) => (
<section className="text-center">
<h2 className="text-3xl font-bold">{title}</h2>
<p className="pt-1 text-muted-foreground max-w-2xl mx-auto text-balance">{subtitle}</p>
<br />
</section>
)
export default PricingHeader

View File

@ -0,0 +1,20 @@
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
type PricingSwitchProps = {
onSwitch: (value: string) => void;
};
const PricingSwitch = ({ onSwitch }: PricingSwitchProps) => (
<Tabs defaultValue="0" className="w-40 mx-auto" onValueChange={onSwitch}>
<TabsList className="py-6 px-2">
<TabsTrigger value="0" className="text-base">
Monthly
</TabsTrigger>
<TabsTrigger value="1" className="text-base">
Yearly
</TabsTrigger>
</TabsList>
</Tabs>
);
export default PricingSwitch;

View File

@ -0,0 +1,128 @@
import * as React from "react"
import { ChevronDownIcon } from "@radix-ui/react-icons"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { cn } from "@/lib/utils"
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn(
"relative z-10 flex max-w-max flex-1 items-center justify-center",
className
)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
"group flex flex-1 list-none items-center justify-center space-x-1",
className
)}
{...props}
/>
))
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item
const navigationMenuTriggerStyle = cva(
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
)
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDownIcon
className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
className
)}
{...props}
/>
))
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
ref={ref}
{...props}
/>
</div>
))
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
))
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
}

View File

@ -9,6 +9,7 @@ export async function markdownToHtml(tutorialCode: string) {
.use(remarkParse) .use(remarkParse)
.use(remarkRehype) .use(remarkRehype)
.use(rehypePrettyCode, {}) .use(rehypePrettyCode, {})
// @ts-ignore
.use(rehypeStringify) .use(rehypeStringify)
.process(tutorialCode); .process(tutorialCode);

View File

@ -69,10 +69,19 @@ module.exports = {
from: { height: "var(--radix-accordion-content-height)" }, from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 }, to: { height: 0 },
}, },
"background-shine": {
from: {
backgroundPosition: "0 0",
},
to: {
backgroundPosition: "-200% 0",
},
},
}, },
animation: { animation: {
"accordion-down": "accordion-down 0.2s ease-out", "accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out",
"background-shine": "background-shine 2s linear infinite",
}, },
}, },
}, },