first commit
This commit is contained in:
		
						commit
						00c8d83ab4
					
				
							
								
								
									
										41
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# dependencies
 | 
				
			||||||
 | 
					/node_modules
 | 
				
			||||||
 | 
					/.pnp
 | 
				
			||||||
 | 
					.pnp.*
 | 
				
			||||||
 | 
					.yarn/*
 | 
				
			||||||
 | 
					!.yarn/patches
 | 
				
			||||||
 | 
					!.yarn/plugins
 | 
				
			||||||
 | 
					!.yarn/releases
 | 
				
			||||||
 | 
					!.yarn/versions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# testing
 | 
				
			||||||
 | 
					/coverage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# next.js
 | 
				
			||||||
 | 
					/.next/
 | 
				
			||||||
 | 
					/out/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# production
 | 
				
			||||||
 | 
					/build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# misc
 | 
				
			||||||
 | 
					.DS_Store
 | 
				
			||||||
 | 
					*.pem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# debug
 | 
				
			||||||
 | 
					npm-debug.log*
 | 
				
			||||||
 | 
					yarn-debug.log*
 | 
				
			||||||
 | 
					yarn-error.log*
 | 
				
			||||||
 | 
					.pnpm-debug.log*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# env files (can opt-in for committing if needed)
 | 
				
			||||||
 | 
					.env*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# vercel
 | 
				
			||||||
 | 
					.vercel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# typescript
 | 
				
			||||||
 | 
					*.tsbuildinfo
 | 
				
			||||||
 | 
					next-env.d.ts
 | 
				
			||||||
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					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
 | 
				
			||||||
 | 
					pnpm i
 | 
				
			||||||
 | 
					pnpm run dev
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
 | 
				
			||||||
							
								
								
									
										21
									
								
								components.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								components.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$schema": "https://ui.shadcn.com/schema.json",
 | 
				
			||||||
 | 
					  "style": "new-york",
 | 
				
			||||||
 | 
					  "rsc": true,
 | 
				
			||||||
 | 
					  "tsx": true,
 | 
				
			||||||
 | 
					  "tailwind": {
 | 
				
			||||||
 | 
					    "config": "",
 | 
				
			||||||
 | 
					    "css": "src/app/globals.css",
 | 
				
			||||||
 | 
					    "baseColor": "neutral",
 | 
				
			||||||
 | 
					    "cssVariables": true,
 | 
				
			||||||
 | 
					    "prefix": ""
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "aliases": {
 | 
				
			||||||
 | 
					    "components": "@/components",
 | 
				
			||||||
 | 
					    "utils": "@/lib/utils",
 | 
				
			||||||
 | 
					    "ui": "@/components/ui",
 | 
				
			||||||
 | 
					    "lib": "@/lib",
 | 
				
			||||||
 | 
					    "hooks": "@/hooks"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "iconLibrary": "lucide"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										16
									
								
								eslint.config.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								eslint.config.mjs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					import { dirname } from "path";
 | 
				
			||||||
 | 
					import { fileURLToPath } from "url";
 | 
				
			||||||
 | 
					import { FlatCompat } from "@eslint/eslintrc";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const __filename = fileURLToPath(import.meta.url);
 | 
				
			||||||
 | 
					const __dirname = dirname(__filename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const compat = new FlatCompat({
 | 
				
			||||||
 | 
					  baseDirectory: __dirname,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const eslintConfig = [
 | 
				
			||||||
 | 
					  ...compat.extends("next/core-web-vitals", "next/typescript"),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default eslintConfig;
 | 
				
			||||||
							
								
								
									
										7
									
								
								next.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								next.config.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					import type { NextConfig } from "next";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const nextConfig: NextConfig = {
 | 
				
			||||||
 | 
					  /* config options here */
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default nextConfig;
 | 
				
			||||||
							
								
								
									
										5475
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										5475
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										39
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "konva-sql",
 | 
				
			||||||
 | 
					  "version": "0.1.0",
 | 
				
			||||||
 | 
					  "private": true,
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "dev": "next dev",
 | 
				
			||||||
 | 
					    "build": "next build",
 | 
				
			||||||
 | 
					    "start": "next start",
 | 
				
			||||||
 | 
					    "lint": "next lint"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "@radix-ui/react-dialog": "^1.1.11",
 | 
				
			||||||
 | 
					    "@radix-ui/react-separator": "^1.1.4",
 | 
				
			||||||
 | 
					    "@radix-ui/react-slot": "^1.2.0",
 | 
				
			||||||
 | 
					    "@radix-ui/react-tooltip": "^1.2.4",
 | 
				
			||||||
 | 
					    "canvas": "^3.1.0",
 | 
				
			||||||
 | 
					    "class-variance-authority": "^0.7.1",
 | 
				
			||||||
 | 
					    "clsx": "^2.1.1",
 | 
				
			||||||
 | 
					    "konva": "^9.3.20",
 | 
				
			||||||
 | 
					    "lucide-react": "^0.503.0",
 | 
				
			||||||
 | 
					    "next": "15.3.1",
 | 
				
			||||||
 | 
					    "react": "^19.0.0",
 | 
				
			||||||
 | 
					    "react-dom": "^19.0.0",
 | 
				
			||||||
 | 
					    "react-konva": "^19.0.3",
 | 
				
			||||||
 | 
					    "tailwind-merge": "^3.2.0"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "@eslint/eslintrc": "^3",
 | 
				
			||||||
 | 
					    "@tailwindcss/postcss": "^4",
 | 
				
			||||||
 | 
					    "@types/node": "^20",
 | 
				
			||||||
 | 
					    "@types/react": "^19",
 | 
				
			||||||
 | 
					    "@types/react-dom": "^19",
 | 
				
			||||||
 | 
					    "eslint": "^9",
 | 
				
			||||||
 | 
					    "eslint-config-next": "15.3.1",
 | 
				
			||||||
 | 
					    "tailwindcss": "^4",
 | 
				
			||||||
 | 
					    "tw-animate-css": "^1.2.8",
 | 
				
			||||||
 | 
					    "typescript": "^5"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										4594
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										4594
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										5
									
								
								postcss.config.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								postcss.config.mjs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					const config = {
 | 
				
			||||||
 | 
					  plugins: ["@tailwindcss/postcss"],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default config;
 | 
				
			||||||
							
								
								
									
										1
									
								
								public/file.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/file.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 391 B  | 
							
								
								
									
										1
									
								
								public/globe.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/globe.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 1.0 KiB  | 
							
								
								
									
										1
									
								
								public/next.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/next.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 1.3 KiB  | 
							
								
								
									
										1
									
								
								public/vercel.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/vercel.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 128 B  | 
							
								
								
									
										1
									
								
								public/window.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/window.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 385 B  | 
							
								
								
									
										144
									
								
								src/app/globals.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								src/app/globals.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,144 @@
 | 
				
			|||||||
 | 
					@import "tailwindcss";
 | 
				
			||||||
 | 
					@import "tw-animate-css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@custom-variant dark (&:is(.dark *));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@theme inline {
 | 
				
			||||||
 | 
					  --radius-sm: calc(var(--radius) - 4px);
 | 
				
			||||||
 | 
					  --radius-md: calc(var(--radius) - 2px);
 | 
				
			||||||
 | 
					  --radius-lg: var(--radius);
 | 
				
			||||||
 | 
					  --radius-xl: calc(var(--radius) + 4px);
 | 
				
			||||||
 | 
					  --color-background: var(--background);
 | 
				
			||||||
 | 
					  --color-foreground: var(--foreground);
 | 
				
			||||||
 | 
					  --color-card: var(--card);
 | 
				
			||||||
 | 
					  --color-card-foreground: var(--card-foreground);
 | 
				
			||||||
 | 
					  --color-popover: var(--popover);
 | 
				
			||||||
 | 
					  --color-popover-foreground: var(--popover-foreground);
 | 
				
			||||||
 | 
					  --color-primary: var(--primary);
 | 
				
			||||||
 | 
					  --color-primary-foreground: var(--primary-foreground);
 | 
				
			||||||
 | 
					  --color-secondary: var(--secondary);
 | 
				
			||||||
 | 
					  --color-secondary-foreground: var(--secondary-foreground);
 | 
				
			||||||
 | 
					  --color-muted: var(--muted);
 | 
				
			||||||
 | 
					  --color-muted-foreground: var(--muted-foreground);
 | 
				
			||||||
 | 
					  --color-accent: var(--accent);
 | 
				
			||||||
 | 
					  --color-accent-foreground: var(--accent-foreground);
 | 
				
			||||||
 | 
					  --color-destructive: var(--destructive);
 | 
				
			||||||
 | 
					  --color-border: var(--border);
 | 
				
			||||||
 | 
					  --color-input: var(--input);
 | 
				
			||||||
 | 
					  --color-ring: var(--ring);
 | 
				
			||||||
 | 
					  --color-chart-1: var(--chart-1);
 | 
				
			||||||
 | 
					  --color-chart-2: var(--chart-2);
 | 
				
			||||||
 | 
					  --color-chart-3: var(--chart-3);
 | 
				
			||||||
 | 
					  --color-chart-4: var(--chart-4);
 | 
				
			||||||
 | 
					  --color-chart-5: var(--chart-5);
 | 
				
			||||||
 | 
					  --color-sidebar: var(--sidebar);
 | 
				
			||||||
 | 
					  --color-sidebar-foreground: var(--sidebar-foreground);
 | 
				
			||||||
 | 
					  --color-sidebar-primary: var(--sidebar-primary);
 | 
				
			||||||
 | 
					  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
 | 
				
			||||||
 | 
					  --color-sidebar-accent: var(--sidebar-accent);
 | 
				
			||||||
 | 
					  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
 | 
				
			||||||
 | 
					  --color-sidebar-border: var(--sidebar-border);
 | 
				
			||||||
 | 
					  --color-sidebar-ring: var(--sidebar-ring);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
					  --radius: 0.625rem;
 | 
				
			||||||
 | 
					  --background: oklch(1 0 0);
 | 
				
			||||||
 | 
					  --foreground: oklch(0.145 0 0);
 | 
				
			||||||
 | 
					  --card: oklch(1 0 0);
 | 
				
			||||||
 | 
					  --card-foreground: oklch(0.145 0 0);
 | 
				
			||||||
 | 
					  --popover: oklch(1 0 0);
 | 
				
			||||||
 | 
					  --popover-foreground: oklch(0.145 0 0);
 | 
				
			||||||
 | 
					  --primary: oklch(0.205 0 0);
 | 
				
			||||||
 | 
					  --primary-foreground: oklch(0.985 0 0);
 | 
				
			||||||
 | 
					  --secondary: oklch(0.97 0 0);
 | 
				
			||||||
 | 
					  --secondary-foreground: oklch(0.205 0 0);
 | 
				
			||||||
 | 
					  --muted: oklch(0.97 0 0);
 | 
				
			||||||
 | 
					  --muted-foreground: oklch(0.556 0 0);
 | 
				
			||||||
 | 
					  --accent: oklch(0.97 0 0);
 | 
				
			||||||
 | 
					  --accent-foreground: oklch(0.205 0 0);
 | 
				
			||||||
 | 
					  --destructive: oklch(0.577 0.245 27.325);
 | 
				
			||||||
 | 
					  --border: oklch(0.922 0 0);
 | 
				
			||||||
 | 
					  --input: oklch(0.922 0 0);
 | 
				
			||||||
 | 
					  --ring: oklch(0.708 0 0);
 | 
				
			||||||
 | 
					  --chart-1: oklch(0.646 0.222 41.116);
 | 
				
			||||||
 | 
					  --chart-2: oklch(0.6 0.118 184.704);
 | 
				
			||||||
 | 
					  --chart-3: oklch(0.398 0.07 227.392);
 | 
				
			||||||
 | 
					  --chart-4: oklch(0.828 0.189 84.429);
 | 
				
			||||||
 | 
					  --chart-5: oklch(0.769 0.188 70.08);
 | 
				
			||||||
 | 
					  --sidebar: oklch(0.985 0 0);
 | 
				
			||||||
 | 
					  --sidebar-foreground: oklch(0.145 0 0);
 | 
				
			||||||
 | 
					  --sidebar-primary: oklch(0.205 0 0);
 | 
				
			||||||
 | 
					  --sidebar-primary-foreground: oklch(0.985 0 0);
 | 
				
			||||||
 | 
					  --sidebar-accent: oklch(0.97 0 0);
 | 
				
			||||||
 | 
					  --sidebar-accent-foreground: oklch(0.205 0 0);
 | 
				
			||||||
 | 
					  --sidebar-border: oklch(0.922 0 0);
 | 
				
			||||||
 | 
					  --sidebar-ring: oklch(0.708 0 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.dark {
 | 
				
			||||||
 | 
					  --background: oklch(0.145 0 0);
 | 
				
			||||||
 | 
					  --foreground: oklch(0.985 0 0);
 | 
				
			||||||
 | 
					  --card: oklch(0.205 0 0);
 | 
				
			||||||
 | 
					  --card-foreground: oklch(0.985 0 0);
 | 
				
			||||||
 | 
					  --popover: oklch(0.205 0 0);
 | 
				
			||||||
 | 
					  --popover-foreground: oklch(0.985 0 0);
 | 
				
			||||||
 | 
					  --primary: oklch(0.922 0 0);
 | 
				
			||||||
 | 
					  --primary-foreground: oklch(0.205 0 0);
 | 
				
			||||||
 | 
					  --secondary: oklch(0.269 0 0);
 | 
				
			||||||
 | 
					  --secondary-foreground: oklch(0.985 0 0);
 | 
				
			||||||
 | 
					  --muted: oklch(0.269 0 0);
 | 
				
			||||||
 | 
					  --muted-foreground: oklch(0.708 0 0);
 | 
				
			||||||
 | 
					  --accent: oklch(0.269 0 0);
 | 
				
			||||||
 | 
					  --accent-foreground: oklch(0.985 0 0);
 | 
				
			||||||
 | 
					  --destructive: oklch(0.704 0.191 22.216);
 | 
				
			||||||
 | 
					  --border: oklch(1 0 0 / 10%);
 | 
				
			||||||
 | 
					  --input: oklch(1 0 0 / 15%);
 | 
				
			||||||
 | 
					  --ring: oklch(0.556 0 0);
 | 
				
			||||||
 | 
					  --chart-1: oklch(0.488 0.243 264.376);
 | 
				
			||||||
 | 
					  --chart-2: oklch(0.696 0.17 162.48);
 | 
				
			||||||
 | 
					  --chart-3: oklch(0.769 0.188 70.08);
 | 
				
			||||||
 | 
					  --chart-4: oklch(0.627 0.265 303.9);
 | 
				
			||||||
 | 
					  --chart-5: oklch(0.645 0.246 16.439);
 | 
				
			||||||
 | 
					  --sidebar: oklch(0.205 0 0);
 | 
				
			||||||
 | 
					  --sidebar-foreground: oklch(0.985 0 0);
 | 
				
			||||||
 | 
					  --sidebar-primary: oklch(0.488 0.243 264.376);
 | 
				
			||||||
 | 
					  --sidebar-primary-foreground: oklch(0.985 0 0);
 | 
				
			||||||
 | 
					  --sidebar-accent: oklch(0.269 0 0);
 | 
				
			||||||
 | 
					  --sidebar-accent-foreground: oklch(0.985 0 0);
 | 
				
			||||||
 | 
					  --sidebar-border: oklch(1 0 0 / 10%);
 | 
				
			||||||
 | 
					  --sidebar-ring: oklch(0.556 0 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@layer base {
 | 
				
			||||||
 | 
					  * {
 | 
				
			||||||
 | 
					    @apply border-border outline-ring/50;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  body {
 | 
				
			||||||
 | 
					    @apply bg-background text-foreground;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@layer base {
 | 
				
			||||||
 | 
					  :root {
 | 
				
			||||||
 | 
					    --sidebar-background: 0 0% 98%;
 | 
				
			||||||
 | 
					    --sidebar-foreground: 240 5.3% 26.1%;
 | 
				
			||||||
 | 
					    --sidebar-primary: 240 5.9% 10%;
 | 
				
			||||||
 | 
					    --sidebar-primary-foreground: 0 0% 98%;
 | 
				
			||||||
 | 
					    --sidebar-accent: 240 4.8% 95.9%;
 | 
				
			||||||
 | 
					    --sidebar-accent-foreground: 240 5.9% 10%;
 | 
				
			||||||
 | 
					    --sidebar-border: 220 13% 91%;
 | 
				
			||||||
 | 
					    --sidebar-ring: 217.2 91.2% 59.8%;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .dark {
 | 
				
			||||||
 | 
					    --sidebar-background: 240 5.9% 10%;
 | 
				
			||||||
 | 
					    --sidebar-foreground: 240 4.8% 95.9%;
 | 
				
			||||||
 | 
					    --sidebar-primary: 224.3 76.3% 48%;
 | 
				
			||||||
 | 
					    --sidebar-primary-foreground: 0 0% 100%;
 | 
				
			||||||
 | 
					    --sidebar-accent: 240 3.7% 15.9%;
 | 
				
			||||||
 | 
					    --sidebar-accent-foreground: 240 4.8% 95.9%;
 | 
				
			||||||
 | 
					    --sidebar-border: 240 3.7% 15.9%;
 | 
				
			||||||
 | 
					    --sidebar-ring: 217.2 91.2% 59.8%;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										42
									
								
								src/app/layout.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/app/layout.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					import type { Metadata } from 'next';
 | 
				
			||||||
 | 
					import { Geist, Geist_Mono } from 'next/font/google';
 | 
				
			||||||
 | 
					import './globals.css';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { SidebarProvider } from '@/components/ui/sidebar';
 | 
				
			||||||
 | 
					import { AppSidebar } from '@/components/app-sidebar';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const geistSans = Geist({
 | 
				
			||||||
 | 
					  variable: '--font-geist-sans',
 | 
				
			||||||
 | 
					  subsets: ['latin'],
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const geistMono = Geist_Mono({
 | 
				
			||||||
 | 
					  variable: '--font-geist-mono',
 | 
				
			||||||
 | 
					  subsets: ['latin'],
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const metadata: Metadata = {
 | 
				
			||||||
 | 
					  title: 'Create Next App',
 | 
				
			||||||
 | 
					  description: 'Generated by create next app',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function RootLayout({
 | 
				
			||||||
 | 
					  children,
 | 
				
			||||||
 | 
					}: Readonly<{
 | 
				
			||||||
 | 
					  children: React.ReactNode;
 | 
				
			||||||
 | 
					}>) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <html lang="en" className="h-full ">
 | 
				
			||||||
 | 
					      <body
 | 
				
			||||||
 | 
					        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <SidebarProvider>
 | 
				
			||||||
 | 
					          <AppSidebar />
 | 
				
			||||||
 | 
					          <main className="flex-1 flex justify-center items-center bg-primary-foreground dark">
 | 
				
			||||||
 | 
					            {children}
 | 
				
			||||||
 | 
					          </main>
 | 
				
			||||||
 | 
					        </SidebarProvider>
 | 
				
			||||||
 | 
					      </body>
 | 
				
			||||||
 | 
					    </html>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										38
									
								
								src/app/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/app/page.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					'use client';
 | 
				
			||||||
 | 
					import { Stage, Layer } from 'react-konva';
 | 
				
			||||||
 | 
					import { useEffect, useRef, useState } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Konva() {
 | 
				
			||||||
 | 
					  const containerRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
 | 
					  const [dimensions, setDimensions] = useState({ width: 800, height: 600 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    setDimensions({
 | 
				
			||||||
 | 
					      width: window.innerWidth - 500,
 | 
				
			||||||
 | 
					      height: window.innerHeight - 200,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function handleDrop(e: React.DragEvent<HTMLDivElement>) {
 | 
				
			||||||
 | 
					    e.preventDefault();
 | 
				
			||||||
 | 
					    const data = e.dataTransfer.getData('text/plain');
 | 
				
			||||||
 | 
					    console.log(JSON.parse(data));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function handleDragOver(e: React.DragEvent<HTMLDivElement>) {
 | 
				
			||||||
 | 
					    e.preventDefault();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      ref={containerRef}
 | 
				
			||||||
 | 
					      onDrop={handleDrop}
 | 
				
			||||||
 | 
					      onDragOver={handleDragOver}
 | 
				
			||||||
 | 
					      className="w-4/5 h-4/5 border-[1px] rounded-md shadow-sm bg-neutral-800"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <Stage width={dimensions.width} height={dimensions.height} className="">
 | 
				
			||||||
 | 
					        <Layer></Layer>
 | 
				
			||||||
 | 
					      </Stage>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										54
									
								
								src/components/app-sidebar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/components/app-sidebar.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					'use client';
 | 
				
			||||||
 | 
					import { testSelects } from './testData';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Sidebar,
 | 
				
			||||||
 | 
					  SidebarContent,
 | 
				
			||||||
 | 
					  SidebarGroup,
 | 
				
			||||||
 | 
					  SidebarGroupContent,
 | 
				
			||||||
 | 
					  SidebarMenu,
 | 
				
			||||||
 | 
					  SidebarMenuButton,
 | 
				
			||||||
 | 
					  SidebarMenuItem,
 | 
				
			||||||
 | 
					  SidebarFooter,
 | 
				
			||||||
 | 
					} from '@/components/ui/sidebar';
 | 
				
			||||||
 | 
					import { Separator } from '@/components/ui/separator';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function AppSidebar() {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Sidebar>
 | 
				
			||||||
 | 
					      <SidebarContent className="bg-primary-foreground/95 dark flex flex-col justify-between">
 | 
				
			||||||
 | 
					        <SidebarGroup>
 | 
				
			||||||
 | 
					          <div className="text-3xl text-center text-orange-500  font-semibold pb-1">
 | 
				
			||||||
 | 
					            VQL
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <Separator />
 | 
				
			||||||
 | 
					          <SidebarGroupContent>
 | 
				
			||||||
 | 
					            <SidebarMenu>
 | 
				
			||||||
 | 
					              {testSelects.map((item) => (
 | 
				
			||||||
 | 
					                // @ts-expect-error: data needs id's before this point
 | 
				
			||||||
 | 
					                <SidebarMenuItem key={item.conditionals}>
 | 
				
			||||||
 | 
					                  <SidebarMenuButton
 | 
				
			||||||
 | 
					                    asChild
 | 
				
			||||||
 | 
					                    draggable
 | 
				
			||||||
 | 
					                    onDragStart={(e) => {
 | 
				
			||||||
 | 
					                      e.dataTransfer.setData(
 | 
				
			||||||
 | 
					                        'text/plain',
 | 
				
			||||||
 | 
					                        JSON.stringify(item)
 | 
				
			||||||
 | 
					                      ); // send the table name
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    className="text-white p-4 cursor-pointer text-xl text-center w-full"
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    <div>{item.table.name}</div>
 | 
				
			||||||
 | 
					                  </SidebarMenuButton>
 | 
				
			||||||
 | 
					                </SidebarMenuItem>
 | 
				
			||||||
 | 
					              ))}
 | 
				
			||||||
 | 
					            </SidebarMenu>
 | 
				
			||||||
 | 
					          </SidebarGroupContent>
 | 
				
			||||||
 | 
					        </SidebarGroup>
 | 
				
			||||||
 | 
					        <SidebarFooter>
 | 
				
			||||||
 | 
					          <div className="text-white text-center p-5">Beitzah</div>
 | 
				
			||||||
 | 
					        </SidebarFooter>
 | 
				
			||||||
 | 
					      </SidebarContent>
 | 
				
			||||||
 | 
					    </Sidebar>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								src/components/testData.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/components/testData.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					import { Select } from './types/types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const testSelects: Select[] = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    table: {
 | 
				
			||||||
 | 
					      alias: 'Over 30',
 | 
				
			||||||
 | 
					      name: 'Users_Over_30',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    columns: [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: 'column',
 | 
				
			||||||
 | 
					        alias: 'columnAlias',
 | 
				
			||||||
 | 
					        aggregateFunction: 'MAX',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    isWildcard: true,
 | 
				
			||||||
 | 
					    conditionals: [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        key: 'age',
 | 
				
			||||||
 | 
					        operator: '>=',
 | 
				
			||||||
 | 
					        value: '30',
 | 
				
			||||||
 | 
					        extension: '',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    isDistinct: true,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
							
								
								
									
										79
									
								
								src/components/types/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/components/types/types.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					// export type Conditional = {
 | 
				
			||||||
 | 
					// 	Key: string;
 | 
				
			||||||
 | 
					// 	Operator: string;
 | 
				
			||||||
 | 
					// 	Value: string;
 | 
				
			||||||
 | 
					// 	Extension: string; 
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// export type Select = {
 | 
				
			||||||
 | 
					//   id: string;
 | 
				
			||||||
 | 
					//   Table: string;
 | 
				
			||||||
 | 
					//   IsWildcard: boolean;
 | 
				
			||||||
 | 
					//   Conditionals: Conditional[];
 | 
				
			||||||
 | 
					// };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Aggregatefunction =
 | 
				
			||||||
 | 
					  | 'MIN'
 | 
				
			||||||
 | 
					  | 'MAX'
 | 
				
			||||||
 | 
					  | 'COUNT'
 | 
				
			||||||
 | 
					  | 'SUM'
 | 
				
			||||||
 | 
					  | 'AVG'
 | 
				
			||||||
 | 
					  | 'UNKNOWN';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Table = {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  alias: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Join = {
 | 
				
			||||||
 | 
					  type: string;
 | 
				
			||||||
 | 
					  mainTable: Table;
 | 
				
			||||||
 | 
					  joinTable: Table;
 | 
				
			||||||
 | 
					  ons: Conditional[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export type Column = {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  alias: string;
 | 
				
			||||||
 | 
					  aggregateFunction: Aggregatefunction;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Conditional = {
 | 
				
			||||||
 | 
					  key: string;
 | 
				
			||||||
 | 
					  operator: string;
 | 
				
			||||||
 | 
					  value: string;
 | 
				
			||||||
 | 
					  extension: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Frontend specific types?
 | 
				
			||||||
 | 
					export type NodeType = 'JOIN' | 'TABLE';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Node = {
 | 
				
			||||||
 | 
					  x: number;
 | 
				
			||||||
 | 
					  y: number;
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					  childId: string | undefined;
 | 
				
			||||||
 | 
					  type: NodeType;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type JoinNode = Node & Join;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type JoinNodeReadOnly = {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					  parentId: string;
 | 
				
			||||||
 | 
					  childId: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type OrderBy = {
 | 
				
			||||||
 | 
					  key: string;
 | 
				
			||||||
 | 
					  isDescending: boolean;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Select = {
 | 
				
			||||||
 | 
					  table: Table;
 | 
				
			||||||
 | 
					  columns: Column[];
 | 
				
			||||||
 | 
					  conditionals: Conditional[];
 | 
				
			||||||
 | 
					  ordeBys?: OrderBy[];
 | 
				
			||||||
 | 
					  joins?: Join[];
 | 
				
			||||||
 | 
					  isWildcard: boolean;
 | 
				
			||||||
 | 
					  isDistinct: boolean;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										59
									
								
								src/components/ui/button.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/components/ui/button.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					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 transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    variants: {
 | 
				
			||||||
 | 
					      variant: {
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					          "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
 | 
				
			||||||
 | 
					        destructive:
 | 
				
			||||||
 | 
					          "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
 | 
				
			||||||
 | 
					        outline:
 | 
				
			||||||
 | 
					          "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
 | 
				
			||||||
 | 
					        secondary:
 | 
				
			||||||
 | 
					          "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
 | 
				
			||||||
 | 
					        ghost:
 | 
				
			||||||
 | 
					          "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
 | 
				
			||||||
 | 
					        link: "text-primary underline-offset-4 hover:underline",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      size: {
 | 
				
			||||||
 | 
					        default: "h-9 px-4 py-2 has-[>svg]:px-3",
 | 
				
			||||||
 | 
					        sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
 | 
				
			||||||
 | 
					        lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
 | 
				
			||||||
 | 
					        icon: "size-9",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    defaultVariants: {
 | 
				
			||||||
 | 
					      variant: "default",
 | 
				
			||||||
 | 
					      size: "default",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function Button({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  variant,
 | 
				
			||||||
 | 
					  size,
 | 
				
			||||||
 | 
					  asChild = false,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<"button"> &
 | 
				
			||||||
 | 
					  VariantProps<typeof buttonVariants> & {
 | 
				
			||||||
 | 
					    asChild?: boolean
 | 
				
			||||||
 | 
					  }) {
 | 
				
			||||||
 | 
					  const Comp = asChild ? Slot : "button"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Comp
 | 
				
			||||||
 | 
					      data-slot="button"
 | 
				
			||||||
 | 
					      className={cn(buttonVariants({ variant, size, className }))}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Button, buttonVariants }
 | 
				
			||||||
							
								
								
									
										21
									
								
								src/components/ui/input.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/components/ui/input.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function Input({ className, type, ...props }: React.ComponentProps<"input">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <input
 | 
				
			||||||
 | 
					      type={type}
 | 
				
			||||||
 | 
					      data-slot="input"
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
 | 
				
			||||||
 | 
					        "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
 | 
				
			||||||
 | 
					        "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Input }
 | 
				
			||||||
							
								
								
									
										28
									
								
								src/components/ui/separator.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/components/ui/separator.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as SeparatorPrimitive from "@radix-ui/react-separator"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function Separator({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  orientation = "horizontal",
 | 
				
			||||||
 | 
					  decorative = true,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <SeparatorPrimitive.Root
 | 
				
			||||||
 | 
					      data-slot="separator-root"
 | 
				
			||||||
 | 
					      decorative={decorative}
 | 
				
			||||||
 | 
					      orientation={orientation}
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Separator }
 | 
				
			||||||
							
								
								
									
										139
									
								
								src/components/ui/sheet.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								src/components/ui/sheet.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,139 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as SheetPrimitive from "@radix-ui/react-dialog"
 | 
				
			||||||
 | 
					import { XIcon } from "lucide-react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
 | 
				
			||||||
 | 
					  return <SheetPrimitive.Root data-slot="sheet" {...props} />
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SheetTrigger({
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
 | 
				
			||||||
 | 
					  return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SheetClose({
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof SheetPrimitive.Close>) {
 | 
				
			||||||
 | 
					  return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SheetPortal({
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
 | 
				
			||||||
 | 
					  return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SheetOverlay({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <SheetPrimitive.Overlay
 | 
				
			||||||
 | 
					      data-slot="sheet-overlay"
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SheetContent({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  children,
 | 
				
			||||||
 | 
					  side = "right",
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof SheetPrimitive.Content> & {
 | 
				
			||||||
 | 
					  side?: "top" | "right" | "bottom" | "left"
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <SheetPortal>
 | 
				
			||||||
 | 
					      <SheetOverlay />
 | 
				
			||||||
 | 
					      <SheetPrimitive.Content
 | 
				
			||||||
 | 
					        data-slot="sheet-content"
 | 
				
			||||||
 | 
					        className={cn(
 | 
				
			||||||
 | 
					          "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
 | 
				
			||||||
 | 
					          side === "right" &&
 | 
				
			||||||
 | 
					            "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
 | 
				
			||||||
 | 
					          side === "left" &&
 | 
				
			||||||
 | 
					            "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
 | 
				
			||||||
 | 
					          side === "top" &&
 | 
				
			||||||
 | 
					            "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
 | 
				
			||||||
 | 
					          side === "bottom" &&
 | 
				
			||||||
 | 
					            "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
 | 
				
			||||||
 | 
					          className
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					        {...props}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {children}
 | 
				
			||||||
 | 
					        <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
 | 
				
			||||||
 | 
					          <XIcon className="size-4" />
 | 
				
			||||||
 | 
					          <span className="sr-only">Close</span>
 | 
				
			||||||
 | 
					        </SheetPrimitive.Close>
 | 
				
			||||||
 | 
					      </SheetPrimitive.Content>
 | 
				
			||||||
 | 
					    </SheetPortal>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      data-slot="sheet-header"
 | 
				
			||||||
 | 
					      className={cn("flex flex-col gap-1.5 p-4", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      data-slot="sheet-footer"
 | 
				
			||||||
 | 
					      className={cn("mt-auto flex flex-col gap-2 p-4", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SheetTitle({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof SheetPrimitive.Title>) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <SheetPrimitive.Title
 | 
				
			||||||
 | 
					      data-slot="sheet-title"
 | 
				
			||||||
 | 
					      className={cn("text-foreground font-semibold", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SheetDescription({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof SheetPrimitive.Description>) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <SheetPrimitive.Description
 | 
				
			||||||
 | 
					      data-slot="sheet-description"
 | 
				
			||||||
 | 
					      className={cn("text-muted-foreground text-sm", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  Sheet,
 | 
				
			||||||
 | 
					  SheetTrigger,
 | 
				
			||||||
 | 
					  SheetClose,
 | 
				
			||||||
 | 
					  SheetContent,
 | 
				
			||||||
 | 
					  SheetHeader,
 | 
				
			||||||
 | 
					  SheetFooter,
 | 
				
			||||||
 | 
					  SheetTitle,
 | 
				
			||||||
 | 
					  SheetDescription,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										726
									
								
								src/components/ui/sidebar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										726
									
								
								src/components/ui/sidebar.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,726 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import { Slot } from "@radix-ui/react-slot"
 | 
				
			||||||
 | 
					import { VariantProps, cva } from "class-variance-authority"
 | 
				
			||||||
 | 
					import { PanelLeftIcon } from "lucide-react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useIsMobile } from "@/hooks/use-mobile"
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils"
 | 
				
			||||||
 | 
					import { Button } from "@/components/ui/button"
 | 
				
			||||||
 | 
					import { Input } from "@/components/ui/input"
 | 
				
			||||||
 | 
					import { Separator } from "@/components/ui/separator"
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Sheet,
 | 
				
			||||||
 | 
					  SheetContent,
 | 
				
			||||||
 | 
					  SheetDescription,
 | 
				
			||||||
 | 
					  SheetHeader,
 | 
				
			||||||
 | 
					  SheetTitle,
 | 
				
			||||||
 | 
					} from "@/components/ui/sheet"
 | 
				
			||||||
 | 
					import { Skeleton } from "@/components/ui/skeleton"
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Tooltip,
 | 
				
			||||||
 | 
					  TooltipContent,
 | 
				
			||||||
 | 
					  TooltipProvider,
 | 
				
			||||||
 | 
					  TooltipTrigger,
 | 
				
			||||||
 | 
					} from "@/components/ui/tooltip"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SIDEBAR_COOKIE_NAME = "sidebar_state"
 | 
				
			||||||
 | 
					const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
 | 
				
			||||||
 | 
					const SIDEBAR_WIDTH = "16rem"
 | 
				
			||||||
 | 
					const SIDEBAR_WIDTH_MOBILE = "18rem"
 | 
				
			||||||
 | 
					const SIDEBAR_WIDTH_ICON = "3rem"
 | 
				
			||||||
 | 
					const SIDEBAR_KEYBOARD_SHORTCUT = "b"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SidebarContextProps = {
 | 
				
			||||||
 | 
					  state: "expanded" | "collapsed"
 | 
				
			||||||
 | 
					  open: boolean
 | 
				
			||||||
 | 
					  setOpen: (open: boolean) => void
 | 
				
			||||||
 | 
					  openMobile: boolean
 | 
				
			||||||
 | 
					  setOpenMobile: (open: boolean) => void
 | 
				
			||||||
 | 
					  isMobile: boolean
 | 
				
			||||||
 | 
					  toggleSidebar: () => void
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SidebarContext = React.createContext<SidebarContextProps | null>(null)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function useSidebar() {
 | 
				
			||||||
 | 
					  const context = React.useContext(SidebarContext)
 | 
				
			||||||
 | 
					  if (!context) {
 | 
				
			||||||
 | 
					    throw new Error("useSidebar must be used within a SidebarProvider.")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return context
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarProvider({
 | 
				
			||||||
 | 
					  defaultOpen = true,
 | 
				
			||||||
 | 
					  open: openProp,
 | 
				
			||||||
 | 
					  onOpenChange: setOpenProp,
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  style,
 | 
				
			||||||
 | 
					  children,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<"div"> & {
 | 
				
			||||||
 | 
					  defaultOpen?: boolean
 | 
				
			||||||
 | 
					  open?: boolean
 | 
				
			||||||
 | 
					  onOpenChange?: (open: boolean) => void
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  const isMobile = useIsMobile()
 | 
				
			||||||
 | 
					  const [openMobile, setOpenMobile] = React.useState(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // This is the internal state of the sidebar.
 | 
				
			||||||
 | 
					  // We use openProp and setOpenProp for control from outside the component.
 | 
				
			||||||
 | 
					  const [_open, _setOpen] = React.useState(defaultOpen)
 | 
				
			||||||
 | 
					  const open = openProp ?? _open
 | 
				
			||||||
 | 
					  const setOpen = React.useCallback(
 | 
				
			||||||
 | 
					    (value: boolean | ((value: boolean) => boolean)) => {
 | 
				
			||||||
 | 
					      const openState = typeof value === "function" ? value(open) : value
 | 
				
			||||||
 | 
					      if (setOpenProp) {
 | 
				
			||||||
 | 
					        setOpenProp(openState)
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        _setOpen(openState)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // This sets the cookie to keep the sidebar state.
 | 
				
			||||||
 | 
					      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    [setOpenProp, open]
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Helper to toggle the sidebar.
 | 
				
			||||||
 | 
					  const toggleSidebar = React.useCallback(() => {
 | 
				
			||||||
 | 
					    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
 | 
				
			||||||
 | 
					  }, [isMobile, setOpen, setOpenMobile])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Adds a keyboard shortcut to toggle the sidebar.
 | 
				
			||||||
 | 
					  React.useEffect(() => {
 | 
				
			||||||
 | 
					    const handleKeyDown = (event: KeyboardEvent) => {
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
 | 
				
			||||||
 | 
					        (event.metaKey || event.ctrlKey)
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        event.preventDefault()
 | 
				
			||||||
 | 
					        toggleSidebar()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window.addEventListener("keydown", handleKeyDown)
 | 
				
			||||||
 | 
					    return () => window.removeEventListener("keydown", handleKeyDown)
 | 
				
			||||||
 | 
					  }, [toggleSidebar])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // We add a state so that we can do data-state="expanded" or "collapsed".
 | 
				
			||||||
 | 
					  // This makes it easier to style the sidebar with Tailwind classes.
 | 
				
			||||||
 | 
					  const state = open ? "expanded" : "collapsed"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const contextValue = React.useMemo<SidebarContextProps>(
 | 
				
			||||||
 | 
					    () => ({
 | 
				
			||||||
 | 
					      state,
 | 
				
			||||||
 | 
					      open,
 | 
				
			||||||
 | 
					      setOpen,
 | 
				
			||||||
 | 
					      isMobile,
 | 
				
			||||||
 | 
					      openMobile,
 | 
				
			||||||
 | 
					      setOpenMobile,
 | 
				
			||||||
 | 
					      toggleSidebar,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <SidebarContext.Provider value={contextValue}>
 | 
				
			||||||
 | 
					      <TooltipProvider delayDuration={0}>
 | 
				
			||||||
 | 
					        <div
 | 
				
			||||||
 | 
					          data-slot="sidebar-wrapper"
 | 
				
			||||||
 | 
					          style={
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              "--sidebar-width": SIDEBAR_WIDTH,
 | 
				
			||||||
 | 
					              "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
 | 
				
			||||||
 | 
					              ...style,
 | 
				
			||||||
 | 
					            } as React.CSSProperties
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          className={cn(
 | 
				
			||||||
 | 
					            "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
 | 
				
			||||||
 | 
					            className
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					          {...props}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          {children}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </TooltipProvider>
 | 
				
			||||||
 | 
					    </SidebarContext.Provider>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function Sidebar({
 | 
				
			||||||
 | 
					  side = "left",
 | 
				
			||||||
 | 
					  variant = "sidebar",
 | 
				
			||||||
 | 
					  collapsible = "offcanvas",
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  children,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<"div"> & {
 | 
				
			||||||
 | 
					  side?: "left" | "right"
 | 
				
			||||||
 | 
					  variant?: "sidebar" | "floating" | "inset"
 | 
				
			||||||
 | 
					  collapsible?: "offcanvas" | "icon" | "none"
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (collapsible === "none") {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <div
 | 
				
			||||||
 | 
					        data-slot="sidebar"
 | 
				
			||||||
 | 
					        className={cn(
 | 
				
			||||||
 | 
					          "bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
 | 
				
			||||||
 | 
					          className
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					        {...props}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {children}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (isMobile) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
 | 
				
			||||||
 | 
					        <SheetContent
 | 
				
			||||||
 | 
					          data-sidebar="sidebar"
 | 
				
			||||||
 | 
					          data-slot="sidebar"
 | 
				
			||||||
 | 
					          data-mobile="true"
 | 
				
			||||||
 | 
					          className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
 | 
				
			||||||
 | 
					          style={
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
 | 
				
			||||||
 | 
					            } as React.CSSProperties
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          side={side}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <SheetHeader className="sr-only">
 | 
				
			||||||
 | 
					            <SheetTitle>Sidebar</SheetTitle>
 | 
				
			||||||
 | 
					            <SheetDescription>Displays the mobile sidebar.</SheetDescription>
 | 
				
			||||||
 | 
					          </SheetHeader>
 | 
				
			||||||
 | 
					          <div className="flex h-full w-full flex-col">{children}</div>
 | 
				
			||||||
 | 
					        </SheetContent>
 | 
				
			||||||
 | 
					      </Sheet>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      className="group peer text-sidebar-foreground hidden md:block"
 | 
				
			||||||
 | 
					      data-state={state}
 | 
				
			||||||
 | 
					      data-collapsible={state === "collapsed" ? collapsible : ""}
 | 
				
			||||||
 | 
					      data-variant={variant}
 | 
				
			||||||
 | 
					      data-side={side}
 | 
				
			||||||
 | 
					      data-slot="sidebar"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      {/* This is what handles the sidebar gap on desktop */}
 | 
				
			||||||
 | 
					      <div
 | 
				
			||||||
 | 
					        data-slot="sidebar-gap"
 | 
				
			||||||
 | 
					        className={cn(
 | 
				
			||||||
 | 
					          "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
 | 
				
			||||||
 | 
					          "group-data-[collapsible=offcanvas]:w-0",
 | 
				
			||||||
 | 
					          "group-data-[side=right]:rotate-180",
 | 
				
			||||||
 | 
					          variant === "floating" || variant === "inset"
 | 
				
			||||||
 | 
					            ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
 | 
				
			||||||
 | 
					            : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      <div
 | 
				
			||||||
 | 
					        data-slot="sidebar-container"
 | 
				
			||||||
 | 
					        className={cn(
 | 
				
			||||||
 | 
					          "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
 | 
				
			||||||
 | 
					          side === "left"
 | 
				
			||||||
 | 
					            ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
 | 
				
			||||||
 | 
					            : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
 | 
				
			||||||
 | 
					          // Adjust the padding for floating and inset variants.
 | 
				
			||||||
 | 
					          variant === "floating" || variant === "inset"
 | 
				
			||||||
 | 
					            ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
 | 
				
			||||||
 | 
					            : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
 | 
				
			||||||
 | 
					          className
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					        {...props}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <div
 | 
				
			||||||
 | 
					          data-sidebar="sidebar"
 | 
				
			||||||
 | 
					          data-slot="sidebar-inner"
 | 
				
			||||||
 | 
					          className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          {children}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarTrigger({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  onClick,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof Button>) {
 | 
				
			||||||
 | 
					  const { toggleSidebar } = useSidebar()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Button
 | 
				
			||||||
 | 
					      data-sidebar="trigger"
 | 
				
			||||||
 | 
					      data-slot="sidebar-trigger"
 | 
				
			||||||
 | 
					      variant="ghost"
 | 
				
			||||||
 | 
					      size="icon"
 | 
				
			||||||
 | 
					      className={cn("size-7", className)}
 | 
				
			||||||
 | 
					      onClick={(event) => {
 | 
				
			||||||
 | 
					        onClick?.(event)
 | 
				
			||||||
 | 
					        toggleSidebar()
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <PanelLeftIcon />
 | 
				
			||||||
 | 
					      <span className="sr-only">Toggle Sidebar</span>
 | 
				
			||||||
 | 
					    </Button>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
 | 
				
			||||||
 | 
					  const { toggleSidebar } = useSidebar()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <button
 | 
				
			||||||
 | 
					      data-sidebar="rail"
 | 
				
			||||||
 | 
					      data-slot="sidebar-rail"
 | 
				
			||||||
 | 
					      aria-label="Toggle Sidebar"
 | 
				
			||||||
 | 
					      tabIndex={-1}
 | 
				
			||||||
 | 
					      onClick={toggleSidebar}
 | 
				
			||||||
 | 
					      title="Toggle Sidebar"
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
 | 
				
			||||||
 | 
					        "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
 | 
				
			||||||
 | 
					        "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
 | 
				
			||||||
 | 
					        "hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
 | 
				
			||||||
 | 
					        "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
 | 
				
			||||||
 | 
					        "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <main
 | 
				
			||||||
 | 
					      data-slot="sidebar-inset"
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "bg-background relative flex w-full flex-1 flex-col",
 | 
				
			||||||
 | 
					        "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarInput({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof Input>) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Input
 | 
				
			||||||
 | 
					      data-slot="sidebar-input"
 | 
				
			||||||
 | 
					      data-sidebar="input"
 | 
				
			||||||
 | 
					      className={cn("bg-background h-8 w-full shadow-none", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      data-slot="sidebar-header"
 | 
				
			||||||
 | 
					      data-sidebar="header"
 | 
				
			||||||
 | 
					      className={cn("flex flex-col gap-2 p-2", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      data-slot="sidebar-footer"
 | 
				
			||||||
 | 
					      data-sidebar="footer"
 | 
				
			||||||
 | 
					      className={cn("flex flex-col gap-2 p-2", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarSeparator({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof Separator>) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Separator
 | 
				
			||||||
 | 
					      data-slot="sidebar-separator"
 | 
				
			||||||
 | 
					      data-sidebar="separator"
 | 
				
			||||||
 | 
					      className={cn("bg-sidebar-border mx-2 w-auto", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      data-slot="sidebar-content"
 | 
				
			||||||
 | 
					      data-sidebar="content"
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      data-slot="sidebar-group"
 | 
				
			||||||
 | 
					      data-sidebar="group"
 | 
				
			||||||
 | 
					      className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarGroupLabel({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  asChild = false,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<"div"> & { asChild?: boolean }) {
 | 
				
			||||||
 | 
					  const Comp = asChild ? Slot : "div"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Comp
 | 
				
			||||||
 | 
					      data-slot="sidebar-group-label"
 | 
				
			||||||
 | 
					      data-sidebar="group-label"
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
 | 
				
			||||||
 | 
					        "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarGroupAction({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  asChild = false,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<"button"> & { asChild?: boolean }) {
 | 
				
			||||||
 | 
					  const Comp = asChild ? Slot : "button"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Comp
 | 
				
			||||||
 | 
					      data-slot="sidebar-group-action"
 | 
				
			||||||
 | 
					      data-sidebar="group-action"
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
 | 
				
			||||||
 | 
					        // Increases the hit area of the button on mobile.
 | 
				
			||||||
 | 
					        "after:absolute after:-inset-2 md:after:hidden",
 | 
				
			||||||
 | 
					        "group-data-[collapsible=icon]:hidden",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarGroupContent({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<"div">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      data-slot="sidebar-group-content"
 | 
				
			||||||
 | 
					      data-sidebar="group-content"
 | 
				
			||||||
 | 
					      className={cn("w-full text-sm", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ul
 | 
				
			||||||
 | 
					      data-slot="sidebar-menu"
 | 
				
			||||||
 | 
					      data-sidebar="menu"
 | 
				
			||||||
 | 
					      className={cn("flex w-full min-w-0 flex-col gap-1", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <li
 | 
				
			||||||
 | 
					      data-slot="sidebar-menu-item"
 | 
				
			||||||
 | 
					      data-sidebar="menu-item"
 | 
				
			||||||
 | 
					      className={cn("group/menu-item relative", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const sidebarMenuButtonVariants = cva(
 | 
				
			||||||
 | 
					  "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    variants: {
 | 
				
			||||||
 | 
					      variant: {
 | 
				
			||||||
 | 
					        default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
 | 
				
			||||||
 | 
					        outline:
 | 
				
			||||||
 | 
					          "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      size: {
 | 
				
			||||||
 | 
					        default: "h-8 text-sm",
 | 
				
			||||||
 | 
					        sm: "h-7 text-xs",
 | 
				
			||||||
 | 
					        lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    defaultVariants: {
 | 
				
			||||||
 | 
					      variant: "default",
 | 
				
			||||||
 | 
					      size: "default",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarMenuButton({
 | 
				
			||||||
 | 
					  asChild = false,
 | 
				
			||||||
 | 
					  isActive = false,
 | 
				
			||||||
 | 
					  variant = "default",
 | 
				
			||||||
 | 
					  size = "default",
 | 
				
			||||||
 | 
					  tooltip,
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<"button"> & {
 | 
				
			||||||
 | 
					  asChild?: boolean
 | 
				
			||||||
 | 
					  isActive?: boolean
 | 
				
			||||||
 | 
					  tooltip?: string | React.ComponentProps<typeof TooltipContent>
 | 
				
			||||||
 | 
					} & VariantProps<typeof sidebarMenuButtonVariants>) {
 | 
				
			||||||
 | 
					  const Comp = asChild ? Slot : "button"
 | 
				
			||||||
 | 
					  const { isMobile, state } = useSidebar()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const button = (
 | 
				
			||||||
 | 
					    <Comp
 | 
				
			||||||
 | 
					      data-slot="sidebar-menu-button"
 | 
				
			||||||
 | 
					      data-sidebar="menu-button"
 | 
				
			||||||
 | 
					      data-size={size}
 | 
				
			||||||
 | 
					      data-active={isActive}
 | 
				
			||||||
 | 
					      className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!tooltip) {
 | 
				
			||||||
 | 
					    return button
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (typeof tooltip === "string") {
 | 
				
			||||||
 | 
					    tooltip = {
 | 
				
			||||||
 | 
					      children: tooltip,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Tooltip>
 | 
				
			||||||
 | 
					      <TooltipTrigger asChild>{button}</TooltipTrigger>
 | 
				
			||||||
 | 
					      <TooltipContent
 | 
				
			||||||
 | 
					        side="right"
 | 
				
			||||||
 | 
					        align="center"
 | 
				
			||||||
 | 
					        hidden={state !== "collapsed" || isMobile}
 | 
				
			||||||
 | 
					        {...tooltip}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </Tooltip>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarMenuAction({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  asChild = false,
 | 
				
			||||||
 | 
					  showOnHover = false,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<"button"> & {
 | 
				
			||||||
 | 
					  asChild?: boolean
 | 
				
			||||||
 | 
					  showOnHover?: boolean
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  const Comp = asChild ? Slot : "button"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Comp
 | 
				
			||||||
 | 
					      data-slot="sidebar-menu-action"
 | 
				
			||||||
 | 
					      data-sidebar="menu-action"
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
 | 
				
			||||||
 | 
					        // Increases the hit area of the button on mobile.
 | 
				
			||||||
 | 
					        "after:absolute after:-inset-2 md:after:hidden",
 | 
				
			||||||
 | 
					        "peer-data-[size=sm]/menu-button:top-1",
 | 
				
			||||||
 | 
					        "peer-data-[size=default]/menu-button:top-1.5",
 | 
				
			||||||
 | 
					        "peer-data-[size=lg]/menu-button:top-2.5",
 | 
				
			||||||
 | 
					        "group-data-[collapsible=icon]:hidden",
 | 
				
			||||||
 | 
					        showOnHover &&
 | 
				
			||||||
 | 
					          "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarMenuBadge({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<"div">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      data-slot="sidebar-menu-badge"
 | 
				
			||||||
 | 
					      data-sidebar="menu-badge"
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none",
 | 
				
			||||||
 | 
					        "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
 | 
				
			||||||
 | 
					        "peer-data-[size=sm]/menu-button:top-1",
 | 
				
			||||||
 | 
					        "peer-data-[size=default]/menu-button:top-1.5",
 | 
				
			||||||
 | 
					        "peer-data-[size=lg]/menu-button:top-2.5",
 | 
				
			||||||
 | 
					        "group-data-[collapsible=icon]:hidden",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarMenuSkeleton({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  showIcon = false,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<"div"> & {
 | 
				
			||||||
 | 
					  showIcon?: boolean
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  // Random width between 50 to 90%.
 | 
				
			||||||
 | 
					  const width = React.useMemo(() => {
 | 
				
			||||||
 | 
					    return `${Math.floor(Math.random() * 40) + 50}%`
 | 
				
			||||||
 | 
					  }, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      data-slot="sidebar-menu-skeleton"
 | 
				
			||||||
 | 
					      data-sidebar="menu-skeleton"
 | 
				
			||||||
 | 
					      className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      {showIcon && (
 | 
				
			||||||
 | 
					        <Skeleton
 | 
				
			||||||
 | 
					          className="size-4 rounded-md"
 | 
				
			||||||
 | 
					          data-sidebar="menu-skeleton-icon"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      <Skeleton
 | 
				
			||||||
 | 
					        className="h-4 max-w-(--skeleton-width) flex-1"
 | 
				
			||||||
 | 
					        data-sidebar="menu-skeleton-text"
 | 
				
			||||||
 | 
					        style={
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "--skeleton-width": width,
 | 
				
			||||||
 | 
					          } as React.CSSProperties
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ul
 | 
				
			||||||
 | 
					      data-slot="sidebar-menu-sub"
 | 
				
			||||||
 | 
					      data-sidebar="menu-sub"
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5",
 | 
				
			||||||
 | 
					        "group-data-[collapsible=icon]:hidden",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarMenuSubItem({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<"li">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <li
 | 
				
			||||||
 | 
					      data-slot="sidebar-menu-sub-item"
 | 
				
			||||||
 | 
					      data-sidebar="menu-sub-item"
 | 
				
			||||||
 | 
					      className={cn("group/menu-sub-item relative", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SidebarMenuSubButton({
 | 
				
			||||||
 | 
					  asChild = false,
 | 
				
			||||||
 | 
					  size = "md",
 | 
				
			||||||
 | 
					  isActive = false,
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<"a"> & {
 | 
				
			||||||
 | 
					  asChild?: boolean
 | 
				
			||||||
 | 
					  size?: "sm" | "md"
 | 
				
			||||||
 | 
					  isActive?: boolean
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  const Comp = asChild ? Slot : "a"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Comp
 | 
				
			||||||
 | 
					      data-slot="sidebar-menu-sub-button"
 | 
				
			||||||
 | 
					      data-sidebar="menu-sub-button"
 | 
				
			||||||
 | 
					      data-size={size}
 | 
				
			||||||
 | 
					      data-active={isActive}
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
 | 
				
			||||||
 | 
					        "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
 | 
				
			||||||
 | 
					        size === "sm" && "text-xs",
 | 
				
			||||||
 | 
					        size === "md" && "text-sm",
 | 
				
			||||||
 | 
					        "group-data-[collapsible=icon]:hidden",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  Sidebar,
 | 
				
			||||||
 | 
					  SidebarContent,
 | 
				
			||||||
 | 
					  SidebarFooter,
 | 
				
			||||||
 | 
					  SidebarGroup,
 | 
				
			||||||
 | 
					  SidebarGroupAction,
 | 
				
			||||||
 | 
					  SidebarGroupContent,
 | 
				
			||||||
 | 
					  SidebarGroupLabel,
 | 
				
			||||||
 | 
					  SidebarHeader,
 | 
				
			||||||
 | 
					  SidebarInput,
 | 
				
			||||||
 | 
					  SidebarInset,
 | 
				
			||||||
 | 
					  SidebarMenu,
 | 
				
			||||||
 | 
					  SidebarMenuAction,
 | 
				
			||||||
 | 
					  SidebarMenuBadge,
 | 
				
			||||||
 | 
					  SidebarMenuButton,
 | 
				
			||||||
 | 
					  SidebarMenuItem,
 | 
				
			||||||
 | 
					  SidebarMenuSkeleton,
 | 
				
			||||||
 | 
					  SidebarMenuSub,
 | 
				
			||||||
 | 
					  SidebarMenuSubButton,
 | 
				
			||||||
 | 
					  SidebarMenuSubItem,
 | 
				
			||||||
 | 
					  SidebarProvider,
 | 
				
			||||||
 | 
					  SidebarRail,
 | 
				
			||||||
 | 
					  SidebarSeparator,
 | 
				
			||||||
 | 
					  SidebarTrigger,
 | 
				
			||||||
 | 
					  useSidebar,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								src/components/ui/skeleton.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/components/ui/skeleton.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import { cn } from "@/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      data-slot="skeleton"
 | 
				
			||||||
 | 
					      className={cn("bg-accent animate-pulse rounded-md", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Skeleton }
 | 
				
			||||||
							
								
								
									
										61
									
								
								src/components/ui/tooltip.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/components/ui/tooltip.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as TooltipPrimitive from "@radix-ui/react-tooltip"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function TooltipProvider({
 | 
				
			||||||
 | 
					  delayDuration = 0,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <TooltipPrimitive.Provider
 | 
				
			||||||
 | 
					      data-slot="tooltip-provider"
 | 
				
			||||||
 | 
					      delayDuration={delayDuration}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function Tooltip({
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <TooltipProvider>
 | 
				
			||||||
 | 
					      <TooltipPrimitive.Root data-slot="tooltip" {...props} />
 | 
				
			||||||
 | 
					    </TooltipProvider>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function TooltipTrigger({
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
 | 
				
			||||||
 | 
					  return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function TooltipContent({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  sideOffset = 0,
 | 
				
			||||||
 | 
					  children,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <TooltipPrimitive.Portal>
 | 
				
			||||||
 | 
					      <TooltipPrimitive.Content
 | 
				
			||||||
 | 
					        data-slot="tooltip-content"
 | 
				
			||||||
 | 
					        sideOffset={sideOffset}
 | 
				
			||||||
 | 
					        className={cn(
 | 
				
			||||||
 | 
					          "bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
 | 
				
			||||||
 | 
					          className
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					        {...props}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {children}
 | 
				
			||||||
 | 
					        <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
 | 
				
			||||||
 | 
					      </TooltipPrimitive.Content>
 | 
				
			||||||
 | 
					    </TooltipPrimitive.Portal>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
 | 
				
			||||||
							
								
								
									
										19
									
								
								src/hooks/use-mobile.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/hooks/use-mobile.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MOBILE_BREAKPOINT = 768
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useIsMobile() {
 | 
				
			||||||
 | 
					  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  React.useEffect(() => {
 | 
				
			||||||
 | 
					    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
 | 
				
			||||||
 | 
					    const onChange = () => {
 | 
				
			||||||
 | 
					      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    mql.addEventListener("change", onChange)
 | 
				
			||||||
 | 
					    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
 | 
				
			||||||
 | 
					    return () => mql.removeEventListener("change", onChange)
 | 
				
			||||||
 | 
					  }, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return !!isMobile
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								src/lib/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/lib/utils.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					import { clsx, type ClassValue } from "clsx"
 | 
				
			||||||
 | 
					import { twMerge } from "tailwind-merge"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function cn(...inputs: ClassValue[]) {
 | 
				
			||||||
 | 
					  return twMerge(clsx(inputs))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "compilerOptions": {
 | 
				
			||||||
 | 
					    "target": "ES2017",
 | 
				
			||||||
 | 
					    "lib": ["dom", "dom.iterable", "esnext"],
 | 
				
			||||||
 | 
					    "allowJs": true,
 | 
				
			||||||
 | 
					    "skipLibCheck": true,
 | 
				
			||||||
 | 
					    "strict": true,
 | 
				
			||||||
 | 
					    "noEmit": true,
 | 
				
			||||||
 | 
					    "esModuleInterop": true,
 | 
				
			||||||
 | 
					    "module": "esnext",
 | 
				
			||||||
 | 
					    "moduleResolution": "bundler",
 | 
				
			||||||
 | 
					    "resolveJsonModule": true,
 | 
				
			||||||
 | 
					    "isolatedModules": true,
 | 
				
			||||||
 | 
					    "jsx": "preserve",
 | 
				
			||||||
 | 
					    "incremental": true,
 | 
				
			||||||
 | 
					    "plugins": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "name": "next"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "paths": {
 | 
				
			||||||
 | 
					      "@/*": ["./src/*"]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
 | 
				
			||||||
 | 
					  "exclude": ["node_modules"]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user