├── .eslintrc.json
├── .gitignore
├── README.md
├── app
├── favicon.ico
├── globals.css
├── layout.tsx
└── page.tsx
├── components.json
├── components
├── craft.tsx
├── nav
│ ├── mobile-nav.tsx
│ └── nav-menu.tsx
├── theme
│ ├── theme-provider.tsx
│ └── theme-toggle.tsx
└── ui
│ ├── button.tsx
│ ├── dropdown-menu.tsx
│ ├── form.tsx
│ ├── label.tsx
│ ├── navigation-menu.tsx
│ ├── scroll-area.tsx
│ ├── separator.tsx
│ └── sheet.tsx
├── lib
├── types.d.ts
└── utils.ts
├── next.config.mjs
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── public
└── logo.svg
├── tailwind.config.ts
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Craft UI](https://github.com/brijr/craft) Starter Template
2 |
3 | [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fbrijr%2Fcraft-starter)
4 |
5 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
6 |
7 | ## Getting Started
8 |
9 | First, run the development server:
10 |
11 | ```bash
12 | npm run dev
13 | # or
14 | yarn dev
15 | # or
16 | pnpm dev
17 | # or
18 | bun dev
19 | ```
20 |
21 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
22 |
23 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
24 |
25 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
26 |
27 | ## Learn More
28 |
29 | To learn more about Next.js, take a look at the following resources:
30 |
31 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
32 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
33 |
34 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
35 |
36 | ## Deploy on Vercel
37 |
38 | 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.
39 |
40 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
41 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brijr/starter/47ab5ab62c2699017880f29a5ac6c45016db2cce/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 0 0% 3.9%;
9 |
10 | --card: 0 0% 100%;
11 | --card-foreground: 0 0% 3.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 0 0% 3.9%;
15 |
16 | --primary: 0 0% 9%;
17 | --primary-foreground: 0 0% 98%;
18 |
19 | --secondary: 0 0% 96.1%;
20 | --secondary-foreground: 0 0% 9%;
21 |
22 | --muted: 0 0% 96.1%;
23 | --muted-foreground: 0 0% 45.1%;
24 |
25 | --accent: 0 0% 96.1%;
26 | --accent-foreground: 0 0% 9%;
27 |
28 | --destructive: 0 84.2% 60.2%;
29 | --destructive-foreground: 0 0% 98%;
30 |
31 | --border: 0 0% 89.8%;
32 | --input: 0 0% 89.8%;
33 | --ring: 0 0% 3.9%;
34 |
35 | --radius: 0.5rem;
36 | }
37 |
38 | .dark {
39 | --background: 0 0% 3.9%;
40 | --foreground: 0 0% 98%;
41 |
42 | --card: 0 0% 3.9%;
43 | --card-foreground: 0 0% 98%;
44 |
45 | --popover: 0 0% 3.9%;
46 | --popover-foreground: 0 0% 98%;
47 |
48 | --primary: 0 0% 98%;
49 | --primary-foreground: 0 0% 9%;
50 |
51 | --secondary: 0 0% 14.9%;
52 | --secondary-foreground: 0 0% 98%;
53 |
54 | --muted: 0 0% 14.9%;
55 | --muted-foreground: 0 0% 63.9%;
56 |
57 | --accent: 0 0% 14.9%;
58 | --accent-foreground: 0 0% 98%;
59 |
60 | --destructive: 0 62.8% 30.6%;
61 | --destructive-foreground: 0 0% 98%;
62 |
63 | --border: 0 0% 14.9%;
64 | --input: 0 0% 14.9%;
65 | --ring: 0 0% 83.1%;
66 | }
67 | }
68 |
69 | @layer base {
70 | * {
71 | @apply border-border;
72 | }
73 |
74 | body {
75 | @apply bg-background text-foreground;
76 | }
77 | }
78 |
79 | @layer prose-m-none {
80 | * {
81 | @apply prose-headings:m-0;
82 | }
83 | }
84 |
85 | @layer utilities {
86 |
87 | /* Hide scrollbar for Chrome, Safari and Opera */
88 | .no-scrollbar::-webkit-scrollbar {
89 | display: none;
90 | }
91 |
92 | /* Hide scrollbar for IE, Edge and Firefox */
93 | .no-scrollbar {
94 | -ms-overflow-style: none;
95 | /* IE and Edge */
96 | scrollbar-width: none;
97 | /* Firefox */
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Inter as FontSans } from "next/font/google";
3 | import { ThemeProvider } from "@/components/theme/theme-provider";
4 |
5 | import "./globals.css";
6 |
7 | import { Button } from "@/components/ui/button";
8 | import { NavMenu } from "@/components/nav/nav-menu";
9 | import { MobileNav } from "@/components/nav/mobile-nav";
10 | import { ThemeToggle } from "@/components/theme/theme-toggle";
11 |
12 | import Logo from "@/public/logo.svg";
13 |
14 | import Image from "next/image";
15 | import Link from "next/link";
16 |
17 | import { cn } from "@/lib/utils";
18 |
19 | const fontSans = FontSans({
20 | subsets: ["latin"],
21 | variable: "--font-sans",
22 | });
23 |
24 | export const metadata: Metadata = {
25 | title: "Craft Next.js Starter",
26 | description: "Generated by create next app",
27 | };
28 |
29 | export default function RootLayout({
30 | children,
31 | }: {
32 | children: React.ReactNode;
33 | }) {
34 | return (
35 |
36 |
40 |
46 |
47 | {children}
48 |
49 |
50 |
51 | );
52 | }
53 |
54 | const Nav = ({ className, children, id }: NavProps) => {
55 | return (
56 |
60 |
64 |
68 |
Craft UI
69 |
76 |
77 | {children}
78 |
79 |
80 |
81 |
82 | Get Started
83 |
84 |
85 |
86 |
87 |
88 | );
89 | };
90 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { Main, Section, Container } from "@/components/craft";
2 | import Balancer from "react-wrap-balancer";
3 |
4 | export default function Home() {
5 | return (
6 |
7 |
12 |
13 | );
14 | }
15 |
16 | const ExampleJsx = () => {
17 | return (
18 |
19 |
20 |
21 | Hello World, welcome to the Next.js and{" "}
22 | brijr/craft Starter!
23 |
24 |
25 |
26 | {/* eslint-disable-next-line */}
27 |
32 |
33 |
34 | Welcome to the{" "}
35 | Craft Starter by{" "}
36 | Bridger Tower . This Next JS template
37 | has been set up based on the recommended{" "}
38 |
42 | Shadcn/ui Next.js setup
43 |
44 | . Use this template paired with{" "}
45 | brijr/components for rapid
46 | building.
47 |
48 |
49 | Example Heading
50 |
51 | This is an example paragraph to illustrate what an article section might
52 | look like in this context. You can add more content here to expand on
53 | the topic and provide more detailed information to your readers.
54 |
55 |
56 | List Item 1
57 | List Item 2
58 | List Item 3
59 |
60 | This is an example H3
61 |
62 | Further explore the topic by discussing relevant points, providing
63 | insights, or sharing experiences that can engage the audience. An
64 | article is not just about listing information but also about
65 | storytelling and conveying a message that resonates with the readers.
66 |
67 |
68 |
69 | {`// This is an example code block
70 | function exampleFunction() {
71 | console.log("This is a code snippet.");
72 | }`}
73 |
74 |
75 |
76 | Lastly, you may want to conclude with a summary or a call-to-action that
77 | encourages readers to take the next steps, such as learning more about a
78 | subject or getting involved in a community discussion.
79 |
80 |
81 | This is an example blockquote. It can be used to highlight important
82 | information or quotes from other sources.
83 |
84 |
85 |
86 |
87 | Header 1
88 | Header 2
89 | Header 3
90 |
91 |
92 |
93 |
94 | Data 1
95 | Data 2
96 | Data 3
97 |
98 |
99 | Data 4
100 | Data 5
101 | Data 6
102 |
103 |
104 |
105 |
106 | {/* eslint-disable-next-line */}
107 |
111 | This is an example figure caption.
112 |
113 |
114 | );
115 | };
116 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "app/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/components/craft.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | // cn util
4 | import { type ClassValue, clsx } from "clsx";
5 | import { twMerge } from "tailwind-merge";
6 |
7 | export function cn(...inputs: ClassValue[]) {
8 | return twMerge(clsx(inputs));
9 | }
10 |
11 | // Layout Component
12 | type LayoutProps = {
13 | children: React.ReactNode;
14 | className?: string;
15 | };
16 |
17 | const Layout = ({ children, className }: LayoutProps) => {
18 | return (
19 |
24 | {children}
25 |
26 | );
27 | };
28 |
29 | // Main Component
30 | type MainProps = {
31 | children: React.ReactNode;
32 | className?: string;
33 | id?: string;
34 | };
35 |
36 | const Main = ({ children, className, id }: MainProps) => {
37 | return (
38 |
60 | {children}
61 |
62 | );
63 | };
64 |
65 | // Section Component
66 | type SectionProps = {
67 | children: React.ReactNode;
68 | className?: string;
69 | id?: string;
70 | };
71 |
72 | const Section = ({ children, className, id }: SectionProps) => {
73 | return (
74 |
77 | );
78 | };
79 |
80 | // Container Component
81 | type ContainerProps = {
82 | children: React.ReactNode;
83 | className?: string;
84 | id?: string;
85 | };
86 |
87 | const Container = ({ children, className, id }: ContainerProps) => {
88 | return (
89 |
90 | {children}
91 |
92 | );
93 | };
94 |
95 | // Article Component
96 | type ArticleProps = {
97 | children: React.ReactNode;
98 | className?: string;
99 | id?: string;
100 | };
101 |
102 | const Article = ({ children, className, id }: ArticleProps) => {
103 | return (
104 |
126 | {children}
127 |
128 | );
129 | };
130 |
131 | export { Layout, Main, Section, Container, Article };
132 |
--------------------------------------------------------------------------------
/components/nav/mobile-nav.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | // React and Next Imports
4 | import * as React from "react";
5 | import Link, { LinkProps } from "next/link";
6 | import { useRouter } from "next/navigation";
7 |
8 | // Utility Imports
9 | import { Menu, ArrowRightSquare } from "lucide-react";
10 | import { cn } from "@/lib/utils";
11 |
12 | // Component Imports
13 | import { Button } from "@/components/ui/button";
14 | import { ScrollArea } from "@/components/ui/scroll-area";
15 | import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
16 | import { Separator } from "@/components/ui/separator";
17 |
18 | // Define the menu items
19 | const mainMenu = {
20 | home: "/",
21 | about: "/about",
22 | contact: "/contact",
23 | };
24 |
25 | const contentMenu = {
26 | blog: "/blog",
27 | articles: "/articles",
28 | tutorials: "/tutorials",
29 | };
30 |
31 | export function MobileNav() {
32 | const [open, setOpen] = React.useState(false);
33 |
34 | return (
35 |
36 |
37 |
41 |
42 | Toggle Menu
43 |
44 |
45 |
46 |
51 |
52 | My Site
53 |
54 |
55 |
56 |
Menu
57 |
58 | {Object.entries(mainMenu).map(([key, href]) => (
59 |
60 | {key.charAt(0).toUpperCase() + key.slice(1)}
61 |
62 | ))}
63 | Blog Menu
64 |
65 | {Object.entries(contentMenu).map(([key, href]) => (
66 |
67 | {key.charAt(0).toUpperCase() + key.slice(1)}
68 |
69 | ))}
70 |
71 |
72 |
73 |
74 | );
75 | }
76 |
77 | interface MobileLinkProps extends LinkProps {
78 | onOpenChange?: (open: boolean) => void;
79 | children: React.ReactNode;
80 | className?: string;
81 | }
82 |
83 | function MobileLink({
84 | href,
85 | onOpenChange,
86 | className,
87 | children,
88 | ...props
89 | }: MobileLinkProps) {
90 | const router = useRouter();
91 | return (
92 | {
95 | router.push(href.toString());
96 | onOpenChange?.(false);
97 | }}
98 | className={cn("text-lg", className)}
99 | {...props}
100 | >
101 | {children}
102 |
103 | );
104 | }
105 |
--------------------------------------------------------------------------------
/components/nav/nav-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import Link from "next/link";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | import {
9 | NavigationMenu,
10 | NavigationMenuContent,
11 | NavigationMenuItem,
12 | NavigationMenuLink,
13 | NavigationMenuList,
14 | NavigationMenuTrigger,
15 | navigationMenuTriggerStyle,
16 | } from "@/components/ui/navigation-menu";
17 |
18 | const components: { title: string; href: string; description: string }[] = [
19 | {
20 | title: "Alert Dialog",
21 | href: "/docs/primitives/alert-dialog",
22 | description:
23 | "A modal dialog that interrupts the user with important content and expects a response.",
24 | },
25 | {
26 | title: "Hover Card",
27 | href: "/docs/primitives/hover-card",
28 | description:
29 | "For sighted users to preview content available behind a link.",
30 | },
31 | {
32 | title: "Progress",
33 | href: "/docs/primitives/progress",
34 | description:
35 | "Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.",
36 | },
37 | {
38 | title: "Scroll-area",
39 | href: "/docs/primitives/scroll-area",
40 | description: "Visually or semantically separates content.",
41 | },
42 | {
43 | title: "Tabs",
44 | href: "/docs/primitives/tabs",
45 | description:
46 | "A set of layered sections of content—known as tab panels—that are displayed one at a time.",
47 | },
48 | {
49 | title: "Tooltip",
50 | href: "/docs/primitives/tooltip",
51 | description:
52 | "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.",
53 | },
54 | ];
55 |
56 | export function NavMenu() {
57 | return (
58 |
59 |
60 |
61 | Getting started
62 |
63 |
91 |
92 |
93 |
94 | Components
95 |
96 |
97 | {components.map((component) => (
98 |
103 | {component.description}
104 |
105 | ))}
106 |
107 |
108 |
109 |
110 |
111 |
112 | Documentation
113 |
114 |
115 |
116 |
117 |
118 | );
119 | }
120 |
121 | const ListItem = React.forwardRef<
122 | React.ElementRef<"a">,
123 | React.ComponentPropsWithoutRef<"a">
124 | >(({ className, title, children, ...props }, ref) => {
125 | return (
126 |
127 |
128 |
136 | {title}
137 |
138 | {children}
139 |
140 |
141 |
142 |
143 | );
144 | });
145 | ListItem.displayName = "ListItem";
146 |
--------------------------------------------------------------------------------
/components/theme/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { ThemeProvider as NextThemesProvider } from "next-themes";
5 | import { type ThemeProviderProps } from "next-themes/dist/types";
6 |
7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
8 | return {children} ;
9 | }
10 |
--------------------------------------------------------------------------------
/components/theme/theme-toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { Moon, Sun } from "lucide-react";
5 | import { useTheme } from "next-themes";
6 |
7 | import { Button } from "@/components/ui/button";
8 | import {
9 | DropdownMenu,
10 | DropdownMenuContent,
11 | DropdownMenuItem,
12 | DropdownMenuTrigger,
13 | } from "@/components/ui/dropdown-menu";
14 |
15 | export function ThemeToggle() {
16 | const { setTheme } = useTheme();
17 |
18 | return (
19 |
20 |
21 |
22 |
23 |
24 | Toggle theme
25 |
26 |
27 |
28 | setTheme("light")}>
29 | Light
30 |
31 | setTheme("dark")}>
32 | Dark
33 |
34 | setTheme("system")}>
35 | System
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "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",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const DropdownMenu = DropdownMenuPrimitive.Root
10 |
11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12 |
13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
14 |
15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16 |
17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
18 |
19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20 |
21 | const DropdownMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | DropdownMenuSubTrigger.displayName =
41 | DropdownMenuPrimitive.SubTrigger.displayName
42 |
43 | const DropdownMenuSubContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, ...props }, ref) => (
47 |
55 | ))
56 | DropdownMenuSubContent.displayName =
57 | DropdownMenuPrimitive.SubContent.displayName
58 |
59 | const DropdownMenuContent = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, sideOffset = 4, ...props }, ref) => (
63 |
64 |
73 |
74 | ))
75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
76 |
77 | const DropdownMenuItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef & {
80 | inset?: boolean
81 | }
82 | >(({ className, inset, ...props }, ref) => (
83 |
92 | ))
93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
94 |
95 | const DropdownMenuCheckboxItem = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, children, checked, ...props }, ref) => (
99 |
108 |
109 |
110 |
111 |
112 |
113 | {children}
114 |
115 | ))
116 | DropdownMenuCheckboxItem.displayName =
117 | DropdownMenuPrimitive.CheckboxItem.displayName
118 |
119 | const DropdownMenuRadioItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
140 |
141 | const DropdownMenuLabel = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef & {
144 | inset?: boolean
145 | }
146 | >(({ className, inset, ...props }, ref) => (
147 |
156 | ))
157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
158 |
159 | const DropdownMenuSeparator = React.forwardRef<
160 | React.ElementRef,
161 | React.ComponentPropsWithoutRef
162 | >(({ className, ...props }, ref) => (
163 |
168 | ))
169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
170 |
171 | const DropdownMenuShortcut = ({
172 | className,
173 | ...props
174 | }: React.HTMLAttributes) => {
175 | return (
176 |
180 | )
181 | }
182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
183 |
184 | export {
185 | DropdownMenu,
186 | DropdownMenuTrigger,
187 | DropdownMenuContent,
188 | DropdownMenuItem,
189 | DropdownMenuCheckboxItem,
190 | DropdownMenuRadioItem,
191 | DropdownMenuLabel,
192 | DropdownMenuSeparator,
193 | DropdownMenuShortcut,
194 | DropdownMenuGroup,
195 | DropdownMenuPortal,
196 | DropdownMenuSub,
197 | DropdownMenuSubContent,
198 | DropdownMenuSubTrigger,
199 | DropdownMenuRadioGroup,
200 | }
201 |
--------------------------------------------------------------------------------
/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { Slot } from "@radix-ui/react-slot"
4 | import {
5 | Controller,
6 | ControllerProps,
7 | FieldPath,
8 | FieldValues,
9 | FormProvider,
10 | useFormContext,
11 | } from "react-hook-form"
12 |
13 | import { cn } from "@/lib/utils"
14 | import { Label } from "@/components/ui/label"
15 |
16 | const Form = FormProvider
17 |
18 | type FormFieldContextValue<
19 | TFieldValues extends FieldValues = FieldValues,
20 | TName extends FieldPath = FieldPath
21 | > = {
22 | name: TName
23 | }
24 |
25 | const FormFieldContext = React.createContext(
26 | {} as FormFieldContextValue
27 | )
28 |
29 | const FormField = <
30 | TFieldValues extends FieldValues = FieldValues,
31 | TName extends FieldPath = FieldPath
32 | >({
33 | ...props
34 | }: ControllerProps) => {
35 | return (
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | const useFormField = () => {
43 | const fieldContext = React.useContext(FormFieldContext)
44 | const itemContext = React.useContext(FormItemContext)
45 | const { getFieldState, formState } = useFormContext()
46 |
47 | const fieldState = getFieldState(fieldContext.name, formState)
48 |
49 | if (!fieldContext) {
50 | throw new Error("useFormField should be used within ")
51 | }
52 |
53 | const { id } = itemContext
54 |
55 | return {
56 | id,
57 | name: fieldContext.name,
58 | formItemId: `${id}-form-item`,
59 | formDescriptionId: `${id}-form-item-description`,
60 | formMessageId: `${id}-form-item-message`,
61 | ...fieldState,
62 | }
63 | }
64 |
65 | type FormItemContextValue = {
66 | id: string
67 | }
68 |
69 | const FormItemContext = React.createContext(
70 | {} as FormItemContextValue
71 | )
72 |
73 | const FormItem = React.forwardRef<
74 | HTMLDivElement,
75 | React.HTMLAttributes
76 | >(({ className, ...props }, ref) => {
77 | const id = React.useId()
78 |
79 | return (
80 |
81 |
82 |
83 | )
84 | })
85 | FormItem.displayName = "FormItem"
86 |
87 | const FormLabel = React.forwardRef<
88 | React.ElementRef,
89 | React.ComponentPropsWithoutRef
90 | >(({ className, ...props }, ref) => {
91 | const { error, formItemId } = useFormField()
92 |
93 | return (
94 |
100 | )
101 | })
102 | FormLabel.displayName = "FormLabel"
103 |
104 | const FormControl = React.forwardRef<
105 | React.ElementRef,
106 | React.ComponentPropsWithoutRef
107 | >(({ ...props }, ref) => {
108 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109 |
110 | return (
111 |
122 | )
123 | })
124 | FormControl.displayName = "FormControl"
125 |
126 | const FormDescription = React.forwardRef<
127 | HTMLParagraphElement,
128 | React.HTMLAttributes
129 | >(({ className, ...props }, ref) => {
130 | const { formDescriptionId } = useFormField()
131 |
132 | return (
133 |
139 | )
140 | })
141 | FormDescription.displayName = "FormDescription"
142 |
143 | const FormMessage = React.forwardRef<
144 | HTMLParagraphElement,
145 | React.HTMLAttributes
146 | >(({ className, children, ...props }, ref) => {
147 | const { error, formMessageId } = useFormField()
148 | const body = error ? String(error?.message) : children
149 |
150 | if (!body) {
151 | return null
152 | }
153 |
154 | return (
155 |
161 | {body}
162 |
163 | )
164 | })
165 | FormMessage.displayName = "FormMessage"
166 |
167 | export {
168 | useFormField,
169 | Form,
170 | FormItem,
171 | FormLabel,
172 | FormControl,
173 | FormDescription,
174 | FormMessage,
175 | FormField,
176 | }
177 |
--------------------------------------------------------------------------------
/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/components/ui/navigation-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
3 | import { cva } from "class-variance-authority"
4 | import { ChevronDown } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const NavigationMenu = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
20 | {children}
21 |
22 |
23 | ))
24 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
25 |
26 | const NavigationMenuList = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, ...props }, ref) => (
30 |
38 | ))
39 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
40 |
41 | const NavigationMenuItem = NavigationMenuPrimitive.Item
42 |
43 | const navigationMenuTriggerStyle = cva(
44 | "group inline-flex h-10 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"
45 | )
46 |
47 | const NavigationMenuTrigger = React.forwardRef<
48 | React.ElementRef,
49 | React.ComponentPropsWithoutRef
50 | >(({ className, children, ...props }, ref) => (
51 |
56 | {children}{" "}
57 |
61 |
62 | ))
63 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
64 |
65 | const NavigationMenuContent = React.forwardRef<
66 | React.ElementRef,
67 | React.ComponentPropsWithoutRef
68 | >(({ className, ...props }, ref) => (
69 |
77 | ))
78 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
79 |
80 | const NavigationMenuLink = NavigationMenuPrimitive.Link
81 |
82 | const NavigationMenuViewport = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef
85 | >(({ className, ...props }, ref) => (
86 |
87 |
95 |
96 | ))
97 | NavigationMenuViewport.displayName =
98 | NavigationMenuPrimitive.Viewport.displayName
99 |
100 | const NavigationMenuIndicator = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, ...props }, ref) => (
104 |
112 |
113 |
114 | ))
115 | NavigationMenuIndicator.displayName =
116 | NavigationMenuPrimitive.Indicator.displayName
117 |
118 | export {
119 | navigationMenuTriggerStyle,
120 | NavigationMenu,
121 | NavigationMenuList,
122 | NavigationMenuItem,
123 | NavigationMenuContent,
124 | NavigationMenuTrigger,
125 | NavigationMenuLink,
126 | NavigationMenuIndicator,
127 | NavigationMenuViewport,
128 | }
129 |
--------------------------------------------------------------------------------
/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ))
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = "vertical", ...props }, ref) => (
30 |
43 |
44 |
45 | ))
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47 |
48 | export { ScrollArea, ScrollBar }
49 |
--------------------------------------------------------------------------------
/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SheetPrimitive from "@radix-ui/react-dialog"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 | import { X } from "lucide-react"
7 |
8 | import { cn } from "@/lib/utils"
9 |
10 | const Sheet = SheetPrimitive.Root
11 |
12 | const SheetTrigger = SheetPrimitive.Trigger
13 |
14 | const SheetClose = SheetPrimitive.Close
15 |
16 | const SheetPortal = SheetPrimitive.Portal
17 |
18 | const SheetOverlay = React.forwardRef<
19 | React.ElementRef,
20 | React.ComponentPropsWithoutRef
21 | >(({ className, ...props }, ref) => (
22 |
30 | ))
31 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
32 |
33 | const sheetVariants = cva(
34 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
35 | {
36 | variants: {
37 | side: {
38 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
39 | bottom:
40 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
41 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
42 | right:
43 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
44 | },
45 | },
46 | defaultVariants: {
47 | side: "right",
48 | },
49 | }
50 | )
51 |
52 | interface SheetContentProps
53 | extends React.ComponentPropsWithoutRef,
54 | VariantProps {}
55 |
56 | const SheetContent = React.forwardRef<
57 | React.ElementRef,
58 | SheetContentProps
59 | >(({ side = "right", className, children, ...props }, ref) => (
60 |
61 |
62 |
67 | {children}
68 |
69 |
70 | Close
71 |
72 |
73 |
74 | ))
75 | SheetContent.displayName = SheetPrimitive.Content.displayName
76 |
77 | const SheetHeader = ({
78 | className,
79 | ...props
80 | }: React.HTMLAttributes) => (
81 |
88 | )
89 | SheetHeader.displayName = "SheetHeader"
90 |
91 | const SheetFooter = ({
92 | className,
93 | ...props
94 | }: React.HTMLAttributes) => (
95 |
102 | )
103 | SheetFooter.displayName = "SheetFooter"
104 |
105 | const SheetTitle = React.forwardRef<
106 | React.ElementRef,
107 | React.ComponentPropsWithoutRef
108 | >(({ className, ...props }, ref) => (
109 |
114 | ))
115 | SheetTitle.displayName = SheetPrimitive.Title.displayName
116 |
117 | const SheetDescription = React.forwardRef<
118 | React.ElementRef,
119 | React.ComponentPropsWithoutRef
120 | >(({ className, ...props }, ref) => (
121 |
126 | ))
127 | SheetDescription.displayName = SheetPrimitive.Description.displayName
128 |
129 | export {
130 | Sheet,
131 | SheetPortal,
132 | SheetOverlay,
133 | SheetTrigger,
134 | SheetClose,
135 | SheetContent,
136 | SheetHeader,
137 | SheetFooter,
138 | SheetTitle,
139 | SheetDescription,
140 | }
141 |
--------------------------------------------------------------------------------
/lib/types.d.ts:
--------------------------------------------------------------------------------
1 | type NavProps = {
2 | className?: string;
3 | children?: React.ReactNode;
4 | id?: string;
5 | };
6 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {};
3 |
4 | export default nextConfig;
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "craft-starter",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbo",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@hookform/resolvers": "^3.3.4",
13 | "@radix-ui/react-dialog": "^1.0.5",
14 | "@radix-ui/react-dropdown-menu": "^2.0.6",
15 | "@radix-ui/react-label": "^2.0.2",
16 | "@radix-ui/react-navigation-menu": "^1.1.4",
17 | "@radix-ui/react-scroll-area": "^1.0.5",
18 | "@radix-ui/react-separator": "^1.0.3",
19 | "@radix-ui/react-slot": "^1.0.2",
20 | "class-variance-authority": "^0.7.0",
21 | "clsx": "^2.1.0",
22 | "lucide-react": "^0.344.0",
23 | "next": "^14.2.5",
24 | "next-themes": "^0.3.0",
25 | "react": "^18.2.0",
26 | "react-dom": "^18.2.0",
27 | "react-hook-form": "^7.51.0",
28 | "react-wrap-balancer": "^1.1.0",
29 | "tailwind-merge": "^2.2.1",
30 | "tailwindcss-animate": "^1.0.7",
31 | "zod": "^3.22.4"
32 | },
33 | "devDependencies": {
34 | "@tailwindcss/typography": "^0.5.11",
35 | "@types/node": "^20",
36 | "@types/react": "^18",
37 | "@types/react-dom": "^18",
38 | "autoprefixer": "^10.0.1",
39 | "eslint": "^8",
40 | "eslint-config-next": "^14.2.1",
41 | "postcss": "^8",
42 | "tailwindcss": "^3.3.0",
43 | "typescript": "^5"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 | const { fontFamily } = require("tailwindcss/defaultTheme");
3 |
4 | const config = {
5 | darkMode: ["class"],
6 | content: [
7 | "./pages/**/*.{ts,tsx}",
8 | "./components/**/*.{ts,tsx}",
9 | "./app/**/*.{ts,tsx}",
10 | "./src/**/*.{ts,tsx}",
11 | ],
12 | prefix: "",
13 | theme: {
14 | container: {
15 | center: true,
16 | padding: "2rem",
17 | screens: {
18 | "2xl": "1400px",
19 | },
20 | },
21 | extend: {
22 | fontFamily: {
23 | sans: ["var(--font-sans)", ...fontFamily.sans],
24 | },
25 | colors: {
26 | border: "hsl(var(--border))",
27 | input: "hsl(var(--input))",
28 | ring: "hsl(var(--ring))",
29 | background: "hsl(var(--background))",
30 | foreground: "hsl(var(--foreground))",
31 | primary: {
32 | DEFAULT: "hsl(var(--primary))",
33 | foreground: "hsl(var(--primary-foreground))",
34 | },
35 | secondary: {
36 | DEFAULT: "hsl(var(--secondary))",
37 | foreground: "hsl(var(--secondary-foreground))",
38 | },
39 | destructive: {
40 | DEFAULT: "hsl(var(--destructive))",
41 | foreground: "hsl(var(--destructive-foreground))",
42 | },
43 | muted: {
44 | DEFAULT: "hsl(var(--muted))",
45 | foreground: "hsl(var(--muted-foreground))",
46 | },
47 | accent: {
48 | DEFAULT: "hsl(var(--accent))",
49 | foreground: "hsl(var(--accent-foreground))",
50 | },
51 | popover: {
52 | DEFAULT: "hsl(var(--popover))",
53 | foreground: "hsl(var(--popover-foreground))",
54 | },
55 | card: {
56 | DEFAULT: "hsl(var(--card))",
57 | foreground: "hsl(var(--card-foreground))",
58 | },
59 | },
60 | borderRadius: {
61 | lg: "var(--radius)",
62 | md: "calc(var(--radius) - 2px)",
63 | sm: "calc(var(--radius) - 4px)",
64 | },
65 | keyframes: {
66 | "accordion-down": {
67 | from: { height: "0" },
68 | to: { height: "var(--radix-accordion-content-height)" },
69 | },
70 | "accordion-up": {
71 | from: { height: "var(--radix-accordion-content-height)" },
72 | to: { height: "0" },
73 | },
74 | },
75 | animation: {
76 | "accordion-down": "accordion-down 0.2s ease-out",
77 | "accordion-up": "accordion-up 0.2s ease-out",
78 | },
79 | },
80 | },
81 | plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
82 | } satisfies Config;
83 |
84 | export default config;
85 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------