@tioelvis/next-template 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # UI Project Generator
2
+
3
+ A **command-line tool** to scaffold a modern **Next.js + TailwindCSS** project with optional prebuilt UI components and hooks — inspired by [shadcn/ui](https://ui.shadcn.com/).
4
+
5
+ > ⚠️ This tool does **not** aim to replace or take credit for [shadcn/ui]. It is simply a utility for convenience and faster setup in personal and internal projects. All UI components are sourced directly from shadcn.
6
+
7
+ ---
8
+
9
+ ## 🔌 React Query Included
10
+
11
+ React Query ([`@tanstack/react-query`](https://tanstack.com/query/latest)) is pre-configured for efficient and scalable data fetching:
12
+
13
+ - ✅ Includes a `QueryClient` setup in `src/providers/query-provider.tsx`
14
+ - ✅ Wraps the application with `QueryClientProvider` (already wired in your layout)
15
+ - ✅ Enables usage of `useQuery`, `useMutation`, and other React Query hooks out of the box
16
+
17
+ ## ✨ Features
18
+
19
+ - 🔧 Choose a **project name** and **package manager** (`npm` or `pnpm`)
20
+ - 🎨 Select UI components to include (e.g., Accordion, Alert, Button)
21
+ - ⚡ Automatically sets up:
22
+ - A **Next.js + TailwindCSS** project
23
+ - Preselected **UI components** and **hooks**
24
+ - Project structure under `src/` (with `components/ui`, `hooks`, `lib`, etc.)
25
+ - **React Query** integration with [`@tanstack/react-query`](https://tanstack.com/query/latest)
26
+ - All required dependencies and devDependencies
27
+
28
+ ---
29
+
30
+ ## 🛠️ Usage
31
+
32
+ Using npm:
33
+
34
+ ```bash
35
+ npx @tioelvis/next-template
36
+ ```
37
+
38
+ Using pnpm:
39
+
40
+ ```bash
41
+ pnpx @tioelvis/next-template
42
+ ```
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@tioelvis/next-template",
3
+ "version": "1.0.3",
4
+ "description": "CLI to scaffold a Next.js + Tailwind project using shadcn/ui components",
5
+ "type": "module",
6
+ "bin": {
7
+ "next-template": "./src/main.js"
8
+ },
9
+ "keywords": [
10
+ "cli",
11
+ "nextjs",
12
+ "shadcn",
13
+ "tailwind",
14
+ "ui",
15
+ "project-generator",
16
+ "scaffolding"
17
+ ],
18
+ "author": {
19
+ "name": "Ti0Elvis",
20
+ "email": "elvis.veramijares@gmail.com"
21
+ },
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/Ti0Elvis/next-template"
26
+ },
27
+ "dependencies": {
28
+ "chalk": "^5.4.1",
29
+ "prompts": "^2.4.2"
30
+ }
31
+ }
@@ -0,0 +1,28 @@
1
+ export const DEPENDENCIES = [
2
+ "@tanstack/react-query",
3
+ "axios",
4
+ "class-variance-authority",
5
+ "clsx",
6
+ "cookies-next",
7
+ "date-fns",
8
+ "lucide-react",
9
+ "next",
10
+ "next-auth",
11
+ "next-themes",
12
+ "react",
13
+ "react-dom",
14
+ "tailwind-merge",
15
+ ];
16
+
17
+ export const DEV_DEPENDENCIES = [
18
+ "@eslint/eslintrc",
19
+ "@tailwindcss/postcss",
20
+ "@types/node",
21
+ "@types/react",
22
+ "@types/react-dom",
23
+ "eslint",
24
+ "eslint-config-next",
25
+ "tailwindcss",
26
+ "tw-animate-css",
27
+ "typescript",
28
+ ];
@@ -0,0 +1,141 @@
1
+ import fs from "node:fs";
2
+ import chalk from "chalk";
3
+ import path from "node:path";
4
+ import prompts from "prompts";
5
+ import { execSync } from "node:child_process";
6
+
7
+ export async function get_prompts() {
8
+ const responses = await prompts([
9
+ {
10
+ type: "text",
11
+ name: "project_name",
12
+ message: "What is your project named?",
13
+ validate: (e) => {
14
+ if (e === undefined || e.trim() === "") {
15
+ return chalk.red("⚠️ The name is required");
16
+ }
17
+
18
+ if (!/^[a-zA-Z0-9-_]+$/.test(e.trim())) {
19
+ return chalk.red(
20
+ "⚠️ Use only letters, numbers, dashes, and underscores."
21
+ );
22
+ }
23
+
24
+ return true;
25
+ },
26
+ },
27
+ {
28
+ type: "select",
29
+ name: "package_manager",
30
+ message: "Which package manager would you want to use?",
31
+ choices: [
32
+ { title: "npm", value: "npm" },
33
+ { title: "pnpm", value: "pnpm" },
34
+ ],
35
+ },
36
+ {
37
+ type: "multiselect",
38
+ name: "components",
39
+ message: "Which components do you want to install?",
40
+ instructions: false,
41
+ choices: [{ title: "Accordion", value: "accordion" }],
42
+ },
43
+ {
44
+ onCancel: () => {
45
+ console.log(chalk.yellow("\n👋 Exiting..."));
46
+ process.exit(0);
47
+ },
48
+ },
49
+ ]);
50
+
51
+ return responses;
52
+ }
53
+
54
+ export function copy_template_files(src, project_path) {
55
+ try {
56
+ const entries = fs.readdirSync(src, { withFileTypes: true });
57
+
58
+ for (const entry of entries) {
59
+ const src_path = path.join(src, entry.name);
60
+ const dest_path = path.join(project_path, entry.name);
61
+
62
+ if (entry.isDirectory()) {
63
+ fs.mkdirSync(dest_path, { recursive: true });
64
+ copy_template_files(src_path, dest_path);
65
+ } else {
66
+ fs.copyFileSync(src_path, dest_path);
67
+ }
68
+ }
69
+ } catch (error) {
70
+ throw error;
71
+ }
72
+ }
73
+
74
+ export function copy_components_files(src, project_path, components) {
75
+ try {
76
+ components.forEach((e) => {
77
+ const component_path = path.resolve(src, `${e}.tsx`);
78
+ const dest_path = path.resolve(project_path, "src", "components", "ui");
79
+
80
+ if (fs.existsSync(component_path) === false) {
81
+ throw new Error(`Component ${e} not found`);
82
+ }
83
+
84
+ if (fs.existsSync(dest_path) === false) {
85
+ fs.mkdirSync(dest_path, { recursive: true });
86
+ }
87
+
88
+ fs.copyFileSync(component_path, path.join(dest_path, `${e}.tsx`));
89
+ });
90
+ } catch (error) {
91
+ throw error;
92
+ }
93
+ }
94
+
95
+ export function copy_hooks_files(
96
+ src,
97
+ components_path,
98
+ project_path,
99
+ components
100
+ ) {
101
+ const map = {};
102
+
103
+ try {
104
+ components.forEach((e) => {
105
+ const hook = map[e];
106
+ const component_path = path.resolve(components_path, `${e}.tsx`);
107
+
108
+ if (fs.existsSync(component_path) === false) {
109
+ throw new Error(`Component ${e} not found`);
110
+ }
111
+
112
+ if (hook === undefined) {
113
+ console.warn(`No hook mapping found for component ${e}, skipping.`);
114
+ return;
115
+ }
116
+
117
+ const hook_path = path.resolve(src, `${hook}.ts`);
118
+ const dest_path = path.resolve(project_path, "src", "hooks");
119
+
120
+ if (fs.existsSync(hook_path) === false) {
121
+ throw new Error(`Hook ${hook} not found for component ${e}`);
122
+ }
123
+
124
+ if (fs.existsSync(dest_path) === false) {
125
+ fs.mkdirSync(dest_path, { recursive: true });
126
+ }
127
+
128
+ fs.copyFileSync(hook_path, path.join(dest_path, `${hook}.ts`));
129
+ });
130
+ } catch (error) {
131
+ throw error;
132
+ }
133
+ }
134
+
135
+ export function run(cmd, cwd) {
136
+ try {
137
+ execSync(cmd, { cwd, stdio: "inherit" });
138
+ } catch (error) {
139
+ throw error;
140
+ }
141
+ }
package/src/main.js ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ copy_components_files,
4
+ copy_hooks_files,
5
+ copy_template_files,
6
+ get_prompts,
7
+ run,
8
+ } from "./lib/utils.js";
9
+ import fs from "node:fs";
10
+ import chalk from "chalk";
11
+ import path from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import { DEPENDENCIES, DEV_DEPENDENCIES } from "./lib/constants.js";
14
+
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+
17
+ // Manage Ctrl+C command
18
+ process.on("SIGINT", () => {
19
+ console.log(chalk.yellow("\n👋 Exiting..."));
20
+ process.exit(0);
21
+ });
22
+
23
+ async function main() {
24
+ let project_path = "";
25
+
26
+ try {
27
+ const { project_name, package_manager, components } = await get_prompts();
28
+
29
+ // Creating folder and copying files
30
+ project_path = path.resolve(project_name);
31
+
32
+ if (fs.existsSync(project_path)) {
33
+ console.error(chalk.red("❌ A folder with that name already exists"));
34
+ process.exit(1);
35
+ }
36
+
37
+ console.log(
38
+ chalk.blue("📁 Creating project in:"),
39
+ chalk.bold(project_name)
40
+ );
41
+
42
+ fs.mkdirSync(project_path, { recursive: true });
43
+ const template_path = path.join(__dirname, "template");
44
+ const components_path = path.join(__dirname, "ui", "components");
45
+ const hooks_path = path.join(__dirname, "ui", "hooks");
46
+
47
+ copy_template_files(template_path, project_path);
48
+ copy_components_files(components_path, project_path, components);
49
+ copy_hooks_files(hooks_path, components_path, project_path, components);
50
+
51
+ // Initialize package.json
52
+ console.log(chalk.cyan("📦 Initializing and configure package.json..."));
53
+
54
+ const package_json = {
55
+ name: project_name,
56
+ version: "1.0.0",
57
+ private: true,
58
+ scripts: {
59
+ dev: "next dev --turbopack",
60
+ build: "next build",
61
+ start: "next start",
62
+ lint: "next lint",
63
+ },
64
+ };
65
+
66
+ const package_json_path = path.join(project_path, "package.json");
67
+ fs.writeFileSync(package_json_path, JSON.stringify(package_json, null, 2));
68
+
69
+ // Install dependencies
70
+ console.log(chalk.cyan("⬇️ Installing dependencies..."));
71
+
72
+ components.forEach((e) => {
73
+ const component_path = path.resolve(components_path, `${e}.json`);
74
+
75
+ if (fs.existsSync(component_path) === false) {
76
+ throw new Error(`Component ${e} not found`);
77
+ }
78
+
79
+ const json = JSON.parse(fs.readFileSync(component_path, "utf-8"));
80
+
81
+ json.forEach((e) => {
82
+ DEPENDENCIES.push(e);
83
+ });
84
+ });
85
+
86
+ if (package_manager === "npm") {
87
+ run(`npm install ${DEPENDENCIES.join(" ")}`, project_path);
88
+ run(`npm install ${DEV_DEPENDENCIES.join(" ")} -D`, project_path);
89
+ }
90
+
91
+ if (package_manager === "pnpm") {
92
+ run(`pnpm add ${DEPENDENCIES.join(" ")}`, project_path);
93
+ run(`pnpm add ${DEV_DEPENDENCIES.join(" ")} -D`, project_path);
94
+ }
95
+
96
+ console.log(chalk.green("\n✅ Project created successfully! 🚀"));
97
+ process.exit(0);
98
+ } catch (error) {
99
+ console.error(chalk.red("❌ Internal error"), error.message);
100
+ if (fs.existsSync(project_path) === true) {
101
+ fs.rmSync(project_path, { recursive: true, force: true });
102
+ }
103
+ process.exit(1);
104
+ }
105
+ }
106
+
107
+ main();
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }
@@ -0,0 +1,16 @@
1
+ import { dirname } from "path";
2
+ import { fileURLToPath } from "url";
3
+ import { FlatCompat } from "@eslint/eslintrc";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+
8
+ const compat = new FlatCompat({
9
+ baseDirectory: __dirname,
10
+ });
11
+
12
+ const eslintConfig = [
13
+ ...compat.extends("next/core-web-vitals", "next/typescript"),
14
+ ];
15
+
16
+ export default eslintConfig;
@@ -0,0 +1,5 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ // NOTE: This file should not be edited
5
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ };
6
+
7
+ export default nextConfig;
@@ -0,0 +1,5 @@
1
+ const config = {
2
+ plugins: ["@tailwindcss/postcss"],
3
+ };
4
+
5
+ export default config;
File without changes
Binary file
@@ -0,0 +1,123 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ @custom-variant dark (&:is(.dark *));
5
+
6
+ @theme inline {
7
+ --color-background: var(--background);
8
+ --color-foreground: var(--foreground);
9
+ --font-sans: var(--font-geist-sans);
10
+ --font-mono: var(--font-geist-mono);
11
+ --color-sidebar-ring: var(--sidebar-ring);
12
+ --color-sidebar-border: var(--sidebar-border);
13
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
14
+ --color-sidebar-accent: var(--sidebar-accent);
15
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
16
+ --color-sidebar-primary: var(--sidebar-primary);
17
+ --color-sidebar-foreground: var(--sidebar-foreground);
18
+ --color-sidebar: var(--sidebar);
19
+ --color-chart-5: var(--chart-5);
20
+ --color-chart-4: var(--chart-4);
21
+ --color-chart-3: var(--chart-3);
22
+ --color-chart-2: var(--chart-2);
23
+ --color-chart-1: var(--chart-1);
24
+ --color-ring: var(--ring);
25
+ --color-input: var(--input);
26
+ --color-border: var(--border);
27
+ --color-destructive: var(--destructive);
28
+ --color-accent-foreground: var(--accent-foreground);
29
+ --color-accent: var(--accent);
30
+ --color-muted-foreground: var(--muted-foreground);
31
+ --color-muted: var(--muted);
32
+ --color-secondary-foreground: var(--secondary-foreground);
33
+ --color-secondary: var(--secondary);
34
+ --color-primary-foreground: var(--primary-foreground);
35
+ --color-primary: var(--primary);
36
+ --color-popover-foreground: var(--popover-foreground);
37
+ --color-popover: var(--popover);
38
+ --color-card-foreground: var(--card-foreground);
39
+ --color-card: var(--card);
40
+ --radius-sm: calc(var(--radius) - 4px);
41
+ --radius-md: calc(var(--radius) - 2px);
42
+ --radius-lg: var(--radius);
43
+ --radius-xl: calc(var(--radius) + 4px);
44
+ }
45
+
46
+ :root {
47
+ --radius: 0.65rem;
48
+ --background: oklch(1 0 0);
49
+ --foreground: oklch(0.145 0 0);
50
+ --card: oklch(1 0 0);
51
+ --card-foreground: oklch(0.145 0 0);
52
+ --popover: oklch(1 0 0);
53
+ --popover-foreground: oklch(0.145 0 0);
54
+ --primary: oklch(0.205 0 0);
55
+ --primary-foreground: oklch(0.985 0 0);
56
+ --secondary: oklch(0.97 0 0);
57
+ --secondary-foreground: oklch(0.205 0 0);
58
+ --muted: oklch(0.97 0 0);
59
+ --muted-foreground: oklch(0.556 0 0);
60
+ --accent: oklch(0.97 0 0);
61
+ --accent-foreground: oklch(0.205 0 0);
62
+ --destructive: oklch(0.577 0.245 27.325);
63
+ --border: oklch(0.922 0 0);
64
+ --input: oklch(0.922 0 0);
65
+ --ring: oklch(0.708 0 0);
66
+ --chart-1: oklch(0.646 0.222 41.116);
67
+ --chart-2: oklch(0.6 0.118 184.704);
68
+ --chart-3: oklch(0.398 0.07 227.392);
69
+ --chart-4: oklch(0.828 0.189 84.429);
70
+ --chart-5: oklch(0.769 0.188 70.08);
71
+ --radius: 0.625rem;
72
+ --sidebar: oklch(0.985 0 0);
73
+ --sidebar-foreground: oklch(0.145 0 0);
74
+ --sidebar-primary: oklch(0.205 0 0);
75
+ --sidebar-primary-foreground: oklch(0.985 0 0);
76
+ --sidebar-accent: oklch(0.97 0 0);
77
+ --sidebar-accent-foreground: oklch(0.205 0 0);
78
+ --sidebar-border: oklch(0.922 0 0);
79
+ --sidebar-ring: oklch(0.708 0 0);
80
+ }
81
+
82
+ .dark {
83
+ --background: oklch(0.145 0 0);
84
+ --foreground: oklch(0.985 0 0);
85
+ --card: oklch(0.205 0 0);
86
+ --card-foreground: oklch(0.985 0 0);
87
+ --popover: oklch(0.205 0 0);
88
+ --popover-foreground: oklch(0.985 0 0);
89
+ --primary: oklch(0.922 0 0);
90
+ --primary-foreground: oklch(0.205 0 0);
91
+ --secondary: oklch(0.269 0 0);
92
+ --secondary-foreground: oklch(0.985 0 0);
93
+ --muted: oklch(0.269 0 0);
94
+ --muted-foreground: oklch(0.708 0 0);
95
+ --accent: oklch(0.269 0 0);
96
+ --accent-foreground: oklch(0.985 0 0);
97
+ --destructive: oklch(0.704 0.191 22.216);
98
+ --border: oklch(1 0 0 / 10%);
99
+ --input: oklch(1 0 0 / 15%);
100
+ --ring: oklch(0.556 0 0);
101
+ --chart-1: oklch(0.488 0.243 264.376);
102
+ --chart-2: oklch(0.696 0.17 162.48);
103
+ --chart-3: oklch(0.769 0.188 70.08);
104
+ --chart-4: oklch(0.627 0.265 303.9);
105
+ --chart-5: oklch(0.645 0.246 16.439);
106
+ --sidebar: oklch(0.205 0 0);
107
+ --sidebar-foreground: oklch(0.985 0 0);
108
+ --sidebar-primary: oklch(0.488 0.243 264.376);
109
+ --sidebar-primary-foreground: oklch(0.985 0 0);
110
+ --sidebar-accent: oklch(0.269 0 0);
111
+ --sidebar-accent-foreground: oklch(0.985 0 0);
112
+ --sidebar-border: oklch(1 0 0 / 10%);
113
+ --sidebar-ring: oklch(0.556 0 0);
114
+ }
115
+
116
+ @layer base {
117
+ * {
118
+ @apply border-border outline-ring/50;
119
+ }
120
+ body {
121
+ @apply bg-background text-foreground;
122
+ }
123
+ }
@@ -0,0 +1,36 @@
1
+ import "./globals.css";
2
+ import type { Metadata } from "next";
3
+ import { Roboto } from "next/font/google";
4
+ import { QueryProvider } from "@/providers/query.provider";
5
+ import { ThemeProvider } from "@/providers/theme.provider";
6
+
7
+ const roboto = Roboto({
8
+ subsets: ["latin"],
9
+ weight: ["500", "600", "700", "800"],
10
+ });
11
+
12
+ export const metadata: Metadata = {
13
+ title: "Untitled",
14
+ };
15
+
16
+ interface Props {
17
+ children: React.ReactNode;
18
+ }
19
+
20
+ export default function Layout({ children }: Readonly<Props>) {
21
+ return (
22
+ <html lang="en" suppressHydrationWarning>
23
+ <body className={roboto.className}>
24
+ <ThemeProvider
25
+ attribute="class"
26
+ defaultTheme="system"
27
+ enableSystem
28
+ disableTransitionOnChange>
29
+ <QueryProvider>
30
+ <main className="w-full min-h-[calc(100vh-4rem)]">{children}</main>
31
+ </QueryProvider>
32
+ </ThemeProvider>
33
+ </body>
34
+ </html>
35
+ );
36
+ }
@@ -0,0 +1,5 @@
1
+ import { Fragment } from "react";
2
+
3
+ export default function Page() {
4
+ return <Fragment>Hello world</Fragment>;
5
+ }
@@ -0,0 +1 @@
1
+ export const API = process.env.NEXT_PUBLIC_API;
@@ -0,0 +1,29 @@
1
+ import { isAxiosError } from "axios";
2
+
3
+ export class CustomAxiosError extends Error {
4
+ constructor(error: unknown) {
5
+ let message = "";
6
+
7
+ if (isAxiosError(error) === true) {
8
+ const status = error.response?.status ?? 500;
9
+ const p = error.response?.data?.message;
10
+
11
+ if (status === 401 || status === 403) {
12
+ message = "Access denied";
13
+ } else if (p !== undefined) {
14
+ message =
15
+ Array.isArray(p?.message) === true ? p.message[0] : p.message ?? p;
16
+ } else {
17
+ message = "Server error";
18
+ }
19
+ } else if (error instanceof Error) {
20
+ message = error.message;
21
+ } else {
22
+ message = "Unknown error";
23
+ }
24
+
25
+ super(message);
26
+ this.name = "CustomAxiosError";
27
+ Object.setPrototypeOf(this, CustomAxiosError.prototype);
28
+ }
29
+ }
@@ -0,0 +1,10 @@
1
+ import axios from "axios";
2
+ import { API } from "@/lib/constants";
3
+
4
+ export const request = axios.create({
5
+ withCredentials: true,
6
+ baseURL: API,
7
+ headers: {
8
+ "Content-Type": "application/json",
9
+ },
10
+ });
@@ -0,0 +1,6 @@
1
+ import { twMerge } from "tailwind-merge";
2
+ import { clsx, type ClassValue } from "clsx";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
3
+
4
+ const client = new QueryClient();
5
+
6
+ interface Props {
7
+ children: React.ReactNode;
8
+ }
9
+
10
+ export function QueryProvider({ children }: Readonly<Props>) {
11
+ return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
12
+ }
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { ThemeProvider as NextThemesProvider } from "next-themes";
3
+
4
+ type Props = React.ComponentProps<typeof NextThemesProvider>;
5
+
6
+ export function ThemeProvider({ children, ...props }: Readonly<Props>) {
7
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
8
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
+ }
@@ -0,0 +1 @@
1
+ ["@radix-ui/react-accordion"]
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as AccordionPrimitive from "@radix-ui/react-accordion";
5
+ import { ChevronDownIcon } from "lucide-react";
6
+
7
+ import { cn } from "@/lib/utils";
8
+
9
+ function Accordion({
10
+ ...props
11
+ }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
12
+ return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
13
+ }
14
+
15
+ function AccordionItem({
16
+ className,
17
+ ...props
18
+ }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
19
+ return (
20
+ <AccordionPrimitive.Item
21
+ data-slot="accordion-item"
22
+ className={cn("border-b last:border-b-0", className)}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ function AccordionTrigger({
29
+ className,
30
+ children,
31
+ ...props
32
+ }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
33
+ return (
34
+ <AccordionPrimitive.Header className="flex">
35
+ <AccordionPrimitive.Trigger
36
+ data-slot="accordion-trigger"
37
+ className={cn(
38
+ "focus-visible:border-ring cursor-pointer focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
39
+ className
40
+ )}
41
+ {...props}>
42
+ {children}
43
+ <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
44
+ </AccordionPrimitive.Trigger>
45
+ </AccordionPrimitive.Header>
46
+ );
47
+ }
48
+
49
+ function AccordionContent({
50
+ className,
51
+ children,
52
+ ...props
53
+ }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
54
+ return (
55
+ <AccordionPrimitive.Content
56
+ data-slot="accordion-content"
57
+ className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
58
+ {...props}>
59
+ <div className={cn("pt-0 pb-4", className)}>{children}</div>
60
+ </AccordionPrimitive.Content>
61
+ );
62
+ }
63
+
64
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
@@ -0,0 +1,6 @@
1
+ import { twMerge } from "tailwind-merge";
2
+ import { clsx, type ClassValue } from "clsx";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
+ }