aethex-studio/PROJECT_BACKUP.md
2026-01-27 06:16:41 +00:00

7404 lines
212 KiB
Markdown

# Project Backup
This file contains a full backup of all your project files. You can copy the contents of this file to your local machine to recreate the project.
---
## FILE: .env
```
```
---
## FILE: .gitignore
```
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# archives
*.zip
```
---
## FILE: README.md
```md
# Firebase Studio
This is a NextJS starter in Firebase Studio.
To get started, take a look at src/app/page.tsx.
```
---
## FILE: apphosting.yaml
```yaml
# Settings to manage and configure a Firebase App Hosting backend.
# https://firebase.google.com/docs/app-hosting/configure
runConfig:
# Increase this value if you'd like to automatically spin up
# more instances in response to increased traffic.
maxInstances: 1
```
---
## FILE: components.json
```json
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"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"
}
```
---
## FILE: next-env.d.ts
```ts
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
```
---
## FILE: next.config.ts
```ts
import type {NextConfig} from 'next';
const nextConfig: NextConfig = {
/* config options here */
typescript: {
ignoreBuildErrors: true,
},
eslint: {
ignoreDuringBuilds: true,
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'placehold.co',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'images.unsplash.com',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'picsum.photos',
port: '',
pathname: '/**',
},
],
},
};
export default nextConfig;
```
---
## FILE: package.json
```json
{
"name": "nextn",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack -p 9002",
"genkit:dev": "genkit start -- tsx src/ai/dev.ts",
"genkit:watch": "genkit start -- tsx --watch src/ai/dev.ts",
"build": "NODE_ENV=production next build",
"start": "next start",
"lint": "next lint",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@genkit-ai/google-genai": "^1.20.0",
"@genkit-ai/next": "^1.20.0",
"@hookform/resolvers": "^4.1.3",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6",
"@radix-ui/react-avatar": "^1.1.3",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-menubar": "^1.1.6",
"@radix-ui/react-popover": "^1.1.6",
"@radix-ui/react-progress": "^1.1.2",
"@radix-ui/react-radio-group": "^1.2.3",
"@radix-ui/react-scroll-area": "^1.2.3",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slider": "^1.2.3",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.1.3",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toast": "^1.2.6",
"@radix-ui/react-tooltip": "^1.1.8",
"@tailwindcss/typography": "^0.5.13",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
"dotenv": "^16.5.0",
"embla-carousel-react": "^8.6.0",
"firebase": "^11.9.1",
"genkit": "^1.20.0",
"lucide-react": "^0.475.0",
"marked": "^12.0.2",
"next": "15.5.9",
"patch-package": "^8.0.0",
"react": "^19.2.1",
"react-day-picker": "^9.11.3",
"react-dom": "^19.2.1",
"react-hook-form": "^7.54.2",
"react-syntax-highlighter": "^15.5.0",
"recharts": "^2.15.1",
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^19.2.1",
"@types/react-dom": "^19.2.1",
"@types/react-syntax-highlighter": "^15.5.13",
"genkit-cli": "^1.20.0",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
```
---
## FILE: src/ai/dev.ts
```ts
import { config } from 'dotenv';
config();
import '@/ai/flows/ai-suggested-sync-conflict-resolution.ts';
import '@/ai/flows/contextual-code-suggestions.ts';
import '@/ai/flows/ai-help-from-prompt.ts';
```
---
## FILE: src/ai/flows/ai-help-from-prompt.ts
```ts
'use server';
/**
* @fileOverview This file defines a Genkit flow that helps new users by suggesting an initial set of code files
* and project structure based on a simple prompt describing the desired application.
*
* - aiHelpFromPrompt - A function that takes a prompt and returns suggested code files and project structure.
* - AIHelpFromPromptInput - The input type for the aiHelpFromPrompt function.
* - AIHelpFromPromptOutput - The return type for the aiHelpFromPrompt function.
*/
import {ai} from '@/ai/genkit';
import {z} from 'genkit';
const AIHelpFromPromptInputSchema = z.object({
prompt: z.string().describe('A prompt describing the type of application to build.'),
});
export type AIHelpFromPromptInput = z.infer<typeof AIHelpFromPromptInputSchema>;
const AIHelpFromPromptOutputSchema = z.object({
suggestedFiles: z.array(z.object({
filePath: z.string().describe('The path for the suggested file.'),
fileContent: z.string().describe('The content of the suggested file.'),
})).describe('An array of suggested code files and their content.'),
explanation: z.string().describe('An explanation of the suggested file structure and code.'),
});
export type AIHelpFromPromptOutput = z.infer<typeof AIHelpFromPromptOutputSchema>;
export async function aiHelpFromPrompt(input: AIHelpFromPromptInput): Promise<AIHelpFromPromptOutput> {
return aiHelpFromPromptFlow(input);
}
const prompt = ai.definePrompt({
name: 'aiHelpFromPromptPrompt',
input: {schema: AIHelpFromPromptInputSchema},
output: {schema: AIHelpFromPromptOutputSchema},
prompt: `You are an AI assistant designed to help new users quickly start developing applications.
Based on the user's prompt describing the desired application, suggest an initial set of code files and a project structure to get them started.
Provide the suggested files as an array of objects, each containing the file path and the file content.
Explain the suggested file structure and the code in detail so that the user understands the purpose of each file and how they fit together.
User Prompt: {{{prompt}}}
Example Output:
{
"suggestedFiles": [
{
"filePath": "src/components/MyComponent.tsx",
"fileContent": "// MyComponent.tsx\nimport React from 'react';\n\nconst MyComponent = () => {\n return (\n <div>\n <h1>Hello, world!</h1>\n </div>\n );\n};\n\nexport default MyComponent;"
},
{
"filePath": "src/pages/index.tsx",
"fileContent": "// index.tsx\nimport MyComponent from '../components/MyComponent';\n\nconst Home = () => {\n return (\n <div>\n <MyComponent />\n </div>\n );\n};\n\nexport default Home;"
}
],
"explanation": "This project structure includes a component (MyComponent.tsx) and a page (index.tsx) that uses the component. This is a basic structure for a React application."
}
`,
});
const aiHelpFromPromptFlow = ai.defineFlow(
{
name: 'aiHelpFromPromptFlow',
inputSchema: AIHelpFromPromptInputSchema,
outputSchema: AIHelpFromPromptOutputSchema,
},
async input => {
const {output} = await prompt(input, {
config: {
safetySettings: [
{
category: 'HARM_CATEGORY_HATE_SPEECH',
threshold: 'BLOCK_ONLY_HIGH',
},
{
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
threshold: 'BLOCK_NONE',
},
{
category: 'HARM_CATEGORY_HARASSMENT',
threshold: 'BLOCK_MEDIUM_AND_ABOVE',
},
{
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
threshold: 'BLOCK_LOW_AND_ABOVE',
},
],
},
});
return output!;
}
);
```
---
## FILE: src/ai/flows/ai-suggested-sync-conflict-resolution.ts
```ts
'use server';
/**
* @fileOverview An AI agent that detects synchronization conflicts between platform-specific code and data,
* and suggests solutions to resolve them.
*
* - aiSuggestedSyncConflictResolution - A function that handles the conflict resolution process.
* - AISuggestedSyncConflictResolutionInput - The input type for the aiSuggestedSyncConflictResolution function.
* - AISuggestedSyncConflictResolutionOutput - The return type for the aiSuggestedSyncConflictResolution function.
*/
import {ai} from '@/ai/genkit';
import {z} from 'genkit';
const AISuggestedSyncConflictResolutionInputSchema = z.object({
robloxCode: z.string().describe('The Lua code for the Roblox platform.'),
webCode: z.string().describe('The JavaScript code for the web platform.'),
mobileCode: z.string().describe('The React Native code for the mobile platform.'),
sharedState: z.string().describe('The shared state data in JSON format.'),
});
export type AISuggestedSyncConflictResolutionInput = z.infer<typeof AISuggestedSyncConflictResolutionInputSchema>;
const AISuggestedSyncConflictResolutionOutputSchema = z.object({
conflictDetected: z.boolean().describe('Whether a synchronization conflict was detected.'),
suggestedSolutions: z.array(z.string()).describe('An array of suggested solutions to resolve the conflicts.'),
explanation: z.string().describe('Explanation of the detected conflicts and suggested solutions.'),
});
export type AISuggestedSyncConflictResolutionOutput = z.infer<typeof AISuggestedSyncConflictResolutionOutputSchema>;
export async function aiSuggestedSyncConflictResolution(input: AISuggestedSyncConflictResolutionInput): Promise<AISuggestedSyncConflictResolutionOutput> {
return aiSuggestedSyncConflictResolutionFlow(input);
}
const prompt = ai.definePrompt({
name: 'aiSuggestedSyncConflictResolutionPrompt',
input: {schema: AISuggestedSyncConflictResolutionInputSchema},
output: {schema: AISuggestedSyncConflictResolutionOutputSchema},
prompt: `You are an AI assistant specialized in detecting synchronization conflicts between different platform codebases and suggesting solutions.
You are given the code for Roblox (Lua), Web (JavaScript), and Mobile (React Native), as well as the shared state data in JSON format. Analyze the code and the shared state to identify any inconsistencies or conflicts.
Based on your analysis, determine if there are any conflicts, and suggest solutions to resolve them. Explain the conflicts and the suggested solutions in detail.
Roblox Code:
{{robloxCode}}
Web Code:
{{webCode}}
Mobile Code:
{{mobileCode}}
Shared State:
{{sharedState}}`,
});
const aiSuggestedSyncConflictResolutionFlow = ai.defineFlow(
{
name: 'aiSuggestedSyncConflictResolutionFlow',
inputSchema: AISuggestedSyncConflictResolutionInputSchema,
outputSchema: AISuggestedSyncConflictResolutionOutputSchema,
},
async input => {
const {output} = await prompt(input, {
config: {
safetySettings: [
{
category: 'HARM_CATEGORY_HATE_SPEECH',
threshold: 'BLOCK_ONLY_HIGH',
},
{
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
threshold: 'BLOCK_NONE',
},
{
category: 'HARM_CATEGORY_HARASSMENT',
threshold: 'BLOCK_MEDIUM_AND_ABOVE',
},
{
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
threshold: 'BLOCK_LOW_AND_ABOVE',
},
],
},
});
return output!;
}
);
```
---
## FILE: src/ai/flows/contextual-code-suggestions.ts
```ts
'use server';
/**
* @fileOverview This file defines a Genkit flow for providing contextual code suggestions.
*
* - contextualCodeSuggestions - A function that takes the current file content and cursor position
* and returns code suggestions.
* - ContextualCodeSuggestionsInput - The input type for the contextualCodeSuggestions function.
* - ContextualCodeSuggestionsOutput - The return type for the contextualCodeSuggestions function.
*/
import {ai} from '@/ai/genkit';
import {z} from 'genkit';
const ContextualCodeSuggestionsInputSchema = z.object({
fileContent: z.string().describe('The content of the currently open file.'),
cursorPosition: z.number().describe('The cursor position within the file.'),
language: z.string().describe('The programming language of the file.'),
context: z.string().optional().describe('Additional context for code suggestions, e.g., error messages or related code snippets.'),
});
export type ContextualCodeSuggestionsInput = z.infer<
typeof ContextualCodeSuggestionsInputSchema
>;
const ContextualCodeSuggestionsOutputSchema = z.object({
suggestions: z
.array(z.string())
.describe('An array of code suggestions based on the context.'),
});
export type ContextualCodeSuggestionsOutput = z.infer<
typeof ContextualCodeSuggestionsOutputSchema
>;
export async function contextualCodeSuggestions(
input: ContextualCodeSuggestionsInput
): Promise<ContextualCodeSuggestionsOutput> {
return contextualCodeSuggestionsFlow(input);
}
const prompt = ai.definePrompt({
name: 'contextualCodeSuggestionsPrompt',
input: {schema: ContextualCodeSuggestionsInputSchema},
output: {schema: ContextualCodeSuggestionsOutputSchema},
prompt: `You are an AI assistant that provides code suggestions and autocompletions based on the context of the currently open file and cursor position.
Given the following file content, cursor position, programming language, and any available context, provide a list of code suggestions that would be helpful to the developer.
File Content:
{{fileContent}}
Cursor Position: {{cursorPosition}}
Programming Language: {{language}}
Context: {{context}}
Suggestions should be relevant to the current context, incorporate best practices, and avoid common mistakes. Return the suggestions as an array of strings.
Example:
[
"console.log('Hello, world!');",
"// Add a comment to explain the code",
"function myFunction() {\n // Function body\n }",
]`,
});
const contextualCodeSuggestionsFlow = ai.defineFlow(
{
name: 'contextualCodeSuggestionsFlow',
inputSchema: ContextualCodeSuggestionsInputSchema,
outputSchema: ContextualCodeSuggestionsOutputSchema,
},
async input => {
const {output} = await prompt(input);
return output!;
}
);
```
---
## FILE: src/ai/genkit.ts
```ts
import {genkit} from 'genkit';
import {googleAI} from '@genkit-ai/google-genai';
export const ai = genkit({
plugins: [googleAI()],
model: 'googleai/gemini-2.5-flash',
});
```
---
## FILE: src/app/dashboard/page.tsx
```tsx
import { DashboardPage } from "@/components/aethex/dashboard-page";
export default function Page() {
return <DashboardPage />;
}
```
---
## FILE: src/app/globals.css
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 278 52% 49%;
--primary-foreground: 0 0% 98%;
--secondary: 277 100% 25%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 180 100% 25%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 278 52% 49%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 13.3%;
--foreground: 0 0% 98%;
--card: 0 0% 20%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 13.3%;
--popover-foreground: 0 0% 98%;
--primary: 278 52% 49%;
--primary-foreground: 0 0% 98%;
--secondary: 277 100% 25%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 20%;
--muted-foreground: 0 0% 63.9%;
--accent: 180 100% 25%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 25%;
--input: 0 0% 25%;
--ring: 278 52% 49%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
```
---
## FILE: src/app/ide/page.tsx
```tsx
import { AethexStudio } from "@/components/aethex/aethex-studio";
export default function IdePage() {
return (
<main className="h-[100svh] w-screen overflow-hidden bg-background">
<AethexStudio />
</main>
);
}
```
---
## FILE: src/app/layout.tsx
```tsx
import type { Metadata } from "next";
import { Toaster } from "@/components/ui/toaster";
import "./globals.css";
export const metadata: Metadata = {
title: "AeThex Studio",
description: "The Next-Generation Cross-Platform IDE",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className="dark">
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="anonymous"
/>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&family=Source+Code+Pro:wght@400&family=Space+Grotesk:wght@400;700&display=swap"
rel="stylesheet"
/>
</head>
<body className="font-body antialiased">
{children}
<Toaster />
</body>
</html>
);
}
```
---
## FILE: src/app/page.tsx
```tsx
import { LoginPage } from "@/components/aethex/login-page";
export default function Page() {
return <LoginPage />;
}
```
---
## FILE: src/components/aethex/aethex-studio.tsx
```tsx
"use client";
import { useState } from "react";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
import { Navbar } from "./navbar";
import { FileNavigator } from "./file-navigator";
import { MainView } from "./main-view";
import { BottomPanel } from "./bottom-panel";
import { AiAssistant } from "./ai-assistant";
import {
openFiles as initialOpenFiles,
fileTree as initialFileTree,
File as OpenFileType,
FolderNode,
FileNode,
} from "@/lib/aethex-data";
import { NewProjectModal } from "./new-project-modal";
import {
ProjectTemplate,
generateFileContent,
} from "@/lib/templates";
import type { NewProjectFormValues } from "./new-project-modal";
export type { OpenFileType };
export function AethexStudio() {
const [openFiles, setOpenFiles] = useState<OpenFileType[]>(initialOpenFiles);
const [activeTab, setActiveTab] = useState<string>(openFiles[0]?.id || "");
const [fileTree, setFileTree] = useState<FolderNode>(initialFileTree);
const [isNewProjectModalOpen, setIsNewProjectModalOpen] = useState(false);
const handleOpenFile = (file: OpenFileType) => {
if (!openFiles.find((f) => f.id === file.id)) {
setOpenFiles((prev) => [...prev, file]);
}
setActiveTab(file.id);
};
const handleCloseFile = (fileId: string) => {
const newOpenFiles = openFiles.filter((file) => file.id !== fileId);
setOpenFiles(newOpenFiles);
if (activeTab === fileId) {
if (newOpenFiles.length > 0) {
setActiveTab(newOpenFiles[newOpenFiles.length - 1].id);
} else {
setActiveTab("");
}
}
};
const handleCreateProject = (
template: ProjectTemplate,
config: NewProjectFormValues
) => {
const newFileTree: FolderNode = {
...template.fileTree,
name: config.projectName,
};
setFileTree(newFileTree);
let mainFileToOpen: OpenFileType | undefined;
const findMainFile = (node: FolderNode | FileNode, currentPath: string) => {
if (mainFileToOpen) return;
const newPath = currentPath ? `${currentPath}/${node.name}` : node.name;
if (node.type === "file" && node.name === template.mainFile) {
mainFileToOpen = {
id: newPath,
name: node.name,
language: node.language,
content: generateFileContent(node.name, node.language),
};
} else if (node.type === "folder" && node.children) {
for (const child of node.children) {
findMainFile(child, newPath);
}
}
};
findMainFile(newFileTree, "");
if (mainFileToOpen) {
setOpenFiles([mainFileToOpen]);
setActiveTab(mainFileToOpen.id);
} else {
setOpenFiles([]);
setActiveTab("");
}
setIsNewProjectModalOpen(false);
};
const handleAiGeneratedFiles = (files: { filePath: string, fileContent: string }[]) => {
const addNodeToTree = (
root: FolderNode,
path: string
): FolderNode => {
const parts = path.split('/');
// The AI generates paths relative to the project root, e.g., "src/components/new.tsx"
// The file tree's root is the project folder itself, so we start traversing from its children.
let currentNode: FolderNode | undefined = root;
// Handle cases where AI gives a full path vs relative
const pathParts = parts[0] === root.name ? parts.slice(1) : parts;
for (let i = 0; i < pathParts.length - 1; i++) {
const part = pathParts[i];
let nextNode = currentNode.children.find(
(child) => child.name === part && child.type === 'folder'
) as FolderNode | undefined;
if (!nextNode) {
nextNode = { name: part, type: 'folder', children: [] };
currentNode.children.push(nextNode);
}
currentNode = nextNode;
}
// Add the file node if it doesn't exist
const fileName = pathParts[pathParts.length - 1];
if (currentNode && !currentNode.children.some((child) => child.name === fileName)) {
currentNode.children.push({
name: fileName,
type: 'file',
language: fileName.split('.').pop() || 'text',
});
}
return { ...root };
};
let newFileTree = fileTree;
files.forEach(file => {
newFileTree = addNodeToTree(newFileTree, file.filePath);
});
setFileTree(newFileTree);
const newFilesToOpen: OpenFileType[] = files.map(file => ({
id: file.filePath.startsWith(fileTree.name) ? file.filePath : `${fileTree.name}/${file.filePath}`,
name: file.filePath.split('/').pop() || 'untitled',
language: file.filePath.split('.').pop() || 'text',
content: file.fileContent,
}));
setOpenFiles(prevOpenFiles => {
const updatedOpenFiles = [...prevOpenFiles];
newFilesToOpen.forEach(newFile => {
const existingFileIndex = updatedOpenFiles.findIndex(f => f.id === newFile.id);
if (existingFileIndex !== -1) {
updatedOpenFiles[existingFileIndex].content = newFile.content;
} else {
updatedOpenFiles.push(newFile);
}
});
return updatedOpenFiles;
});
if (newFilesToOpen.length > 0) {
setActiveTab(newFilesToOpen[newFilesToOpen.length - 1].id);
}
};
return (
<>
<div className="flex h-full flex-col text-sm">
<Navbar onNewProjectClick={() => setIsNewProjectModalOpen(true)} />
<div className="flex-grow overflow-hidden">
<ResizablePanelGroup direction="horizontal" className="!h-full">
<ResizablePanel defaultSize={15} minSize={10} maxSize={25}>
<FileNavigator fileTree={fileTree} onOpenFile={handleOpenFile} />
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={60}>
<ResizablePanelGroup direction="vertical">
<ResizablePanel defaultSize={70}>
<MainView
openFiles={openFiles}
activeTab={activeTab}
setActiveTab={setActiveTab}
onCloseFile={handleCloseFile}
/>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={30} minSize={10} maxSize={40}>
<BottomPanel />
</ResizablePanel>
</ResizablePanelGroup>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={25} minSize={15} maxSize={35}>
<AiAssistant onFilesGenerated={handleAiGeneratedFiles} />
</ResizablePanel>
</ResizablePanelGroup>
</div>
</div>
<NewProjectModal
isOpen={isNewProjectModalOpen}
onClose={() => setIsNewProjectModalOpen(false)}
onCreateProject={handleCreateProject}
/>
</>
);
}
```
---
## FILE: src/components/aethex/ai-assistant.tsx
```tsx
"use client";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Send,
Bot,
Loader2,
Copy,
Code,
Sparkles,
MessageSquarePlus,
FlaskConical,
BookText,
} from "lucide-react";
import { AethexLogo } from "./icons";
import { useState, useRef, useEffect, memo } from "react";
import { aiHelpFromPrompt } from "@/ai/flows/ai-help-from-prompt";
import { cn } from "@/lib/utils";
import { marked } from "marked";
import SyntaxHighlighter from "react-syntax-highlighter";
import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
import { useToast } from "@/hooks/use-toast";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
type Message = {
id: string;
role: "user" | "assistant" | "system";
content: string;
};
const CodeBlock = memo(
({ language, code }: { language: string; code: string }) => {
const { toast } = useToast();
const handleCopy = () => {
navigator.clipboard.writeText(code);
toast({ title: "Code copied to clipboard!" });
};
const handleInsert = () => {
toast({
title: "Coming Soon!",
description: "Inserting code into the editor is not yet implemented.",
});
};
return (
<div className="group relative my-2 rounded-md bg-background">
<div className="flex items-center justify-between rounded-t-md bg-muted px-3 py-1.5 text-xs text-muted-foreground">
<span>{language}</span>
<div className="flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={handleInsert}
>
<Code className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Insert into editor</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={handleCopy}
>
<Copy className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Copy code</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
<SyntaxHighlighter
language={language}
style={atomOneDark}
customStyle={{
padding: "1rem",
margin: 0,
borderBottomLeftRadius: "0.375rem",
borderBottomRightRadius: "0.375rem",
}}
PreTag="div"
>
{code}
</SyntaxHighlighter>
</div>
);
}
);
CodeBlock.displayName = "CodeBlock";
const ChatMessage = memo(({ message }: { message: Message }) => {
if (message.role === "system") {
return (
<div className="text-center text-xs text-muted-foreground">
{message.content}
</div>
);
}
const isUser = message.role === "user";
const parts = message.content
.split(/(\`\`\`(?:[a-zA-Z0-9-]*)\n[\s\S]+?\n\`\`\`)/g)
.filter(Boolean);
return (
<div
className={cn(
"flex w-full items-start gap-3",
!isUser && "justify-end"
)}
>
{isUser && (
<Avatar className="h-8 w-8">
<AvatarFallback>U</AvatarFallback>
</Avatar>
)}
<div
className={cn(
"max-w-[85%] space-y-2 rounded-lg p-3 text-sm",
isUser
? "bg-primary text-primary-foreground"
: "border border-primary bg-card"
)}
>
{parts.map((part, index) => {
const codeBlockMatch = part.match(
/\`\`\`(.*?)\n([\s\S]+?)\n\`\`\`/
);
if (codeBlockMatch) {
const language = codeBlockMatch[1] || "text";
const code = codeBlockMatch[2];
return <CodeBlock key={index} language={language} code={code} />;
} else {
return (
<div
key={index}
className="prose prose-sm prose-invert max-w-none prose-p:my-0"
dangerouslySetInnerHTML={{
__html: marked(part, { gfm: true, breaks: true }),
}}
/>
);
}
})}
</div>
{!isUser && (
<Avatar className="h-8 w-8 border">
<AvatarFallback className="bg-transparent">
<AethexLogo className="h-5 w-5" />
</AvatarFallback>
</Avatar>
)}
</div>
);
});
ChatMessage.displayName = "ChatMessage";
type AiAssistantProps = {
onFilesGenerated: (files: { filePath: string, fileContent: string }[]) => void;
};
export function AiAssistant({ onFilesGenerated }: AiAssistantProps) {
const [messages, setMessages] = useState<Message[]>([
{
id: "1",
role: "assistant",
content:
"Hello! I'm your AI assistant. How can I help you with your project today? You can ask me to explain code, generate tests, or even create a new project structure.",
},
]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const scrollAreaRef = useRef<HTMLDivElement>(null);
const { toast } = useToast();
useEffect(() => {
if (scrollAreaRef.current) {
scrollAreaRef.current.scrollTo({
top: scrollAreaRef.current.scrollHeight,
behavior: "smooth",
});
}
}, [messages]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || loading) return;
const userMessage: Message = {
id: Date.now().toString(),
role: "user",
content: input,
};
setMessages((prev) => [...prev, userMessage]);
const currentInput = input;
setInput("");
setLoading(true);
try {
const result = await aiHelpFromPrompt({ prompt: currentInput });
let assistantContent = result.explanation;
if (result.suggestedFiles && result.suggestedFiles.length > 0) {
onFilesGenerated(result.suggestedFiles);
assistantContent += `\n\nHere are the files I've generated for you:\n`;
result.suggestedFiles.forEach((file) => {
const lang = file.filePath.split(".").pop() || "";
assistantContent += `\n**${file.filePath}**\n\`\`\`${lang}\n${file.fileContent}\n\`\`\``;
});
const systemMessage: Message = {
id: Date.now().toString() + "-system",
role: "system",
content: "File suggestions have been opened in the editor for you to review.",
};
setMessages((prev) => [...prev, systemMessage]);
}
const assistantMessage: Message = {
id: Date.now().toString(),
role: "assistant",
content: assistantContent,
};
setMessages((prev) => [...prev, assistantMessage]);
} catch (error) {
console.error("AI assistant error:", error);
const errorMessage: Message = {
id: Date.now().toString(),
role: "assistant",
content:
"Sorry, I encountered an issue while processing your request. Please try again.",
};
setMessages((prev) => [...prev, errorMessage]);
} finally {
setLoading(false);
}
};
const handleQuickAction = (action: string) => {
toast({
title: "Coming Soon!",
description: `The "${action}" feature is not yet implemented.`,
});
};
const quickActions = [
{ label: "Explain selected code", icon: BookText, action: "Explain selected code" },
{ label: "Add comments", icon: MessageSquarePlus, action: "Add comments" },
{ label: "Convert to cross-platform", icon: Sparkles, action: "Convert to cross-platform" },
{ label: "Generate tests", icon: FlaskConical, action: "Generate tests" },
]
return (
<div className="flex h-full flex-col bg-card">
<div className="flex items-center justify-between border-b p-3">
<div className="flex items-center gap-2">
<Bot className="h-6 w-6 text-primary" />
<h2 className="font-headline text-lg font-semibold">AI Assistant</h2>
</div>
<div className="flex items-center gap-2">
<div className="text-right text-xs">
<p className="text-muted-foreground">Token Usage</p>
<p className="font-medium">12.5K / 500K</p>
</div>
<Select defaultValue="claude-sonnet">
<SelectTrigger className="w-[150px]">
<SelectValue placeholder="Select a model" />
</SelectTrigger>
<SelectContent>
<SelectItem value="claude-sonnet">Claude 3.5 Sonnet</SelectItem>
<SelectItem value="gpt-4o" disabled>GPT-4o</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<ScrollArea className="flex-1" viewportRef={scrollAreaRef}>
<div className="space-y-6 p-4">
{messages.map((message) => (
<ChatMessage key={message.id} message={message} />
))}
{loading && (
<div className="flex w-full items-start gap-3">
<Avatar className="h-8 w-8">
<AvatarFallback>U</AvatarFallback>
</Avatar>
<div className="flex items-center space-x-2 rounded-lg bg-primary p-3 text-primary-foreground">
<Loader2 className="h-4 w-4 animate-spin" />
<span>Waiting for response...</span>
</div>
</div>
)}
</div>
</ScrollArea>
<div className="border-t p-3">
<div className="mb-2 flex flex-wrap gap-2">
{quickActions.map(({label, icon: Icon, action}) => (
<Button key={label} size="sm" variant="outline" onClick={() => handleQuickAction(action)}>
<Icon className="mr-2 h-3.5 w-3.5" />
{label}
</Button>
))}
</div>
<form onSubmit={handleSubmit} className="relative">
<Textarea
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSubmit(e);
}
}}
placeholder="Ask the AI assistant, or describe what you want to build..."
className="min-h-[60px] pr-12"
disabled={loading}
/>
<div className="absolute bottom-2 right-1 flex flex-col items-center gap-1">
<Button
variant="ghost"
size="icon"
type="submit"
disabled={loading || !input.trim()}
>
<Send className="h-4 w-4" />
<span className="sr-only">Send message</span>
</Button>
</div>
</form>
</div>
</div>
);
}
```
---
## FILE: src/components/aethex/bottom-panel.tsx
```tsx
"use client";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { consoleLogs } from "@/lib/aethex-data";
import { ChevronRight, HardDrive } from "lucide-react";
export function BottomPanel() {
return (
<div className="flex h-full flex-col">
<Tabs defaultValue="console" className="flex h-full flex-col">
<TabsList className="mx-2 mt-2 self-start rounded-md">
<TabsTrigger value="console">Console</TabsTrigger>
<TabsTrigger value="terminal">Terminal</TabsTrigger>
</TabsList>
<TabsContent value="console" className="flex-1 overflow-auto p-4 text-xs">
<div className="font-code">
{consoleLogs.map((log, index) => (
<div
key={index}
className={`flex items-start gap-2 border-b border-border/50 py-1 ${
log.type === "error"
? "text-destructive"
: log.type === "warn"
? "text-yellow-400"
: "text-muted-foreground"
}`}
>
<span className="w-20 shrink-0 text-foreground/50">
{log.timestamp}
</span>
<span
className={`w-12 shrink-0 font-bold ${
log.platform === "Roblox"
? "text-red-500"
: log.platform === "Web"
? "text-blue-500"
: "text-green-500"
}`}
>
[{log.platform}]
</span>
<p className="flex-1 whitespace-pre-wrap">{log.message}</p>
</div>
))}
</div>
</TabsContent>
<TabsContent value="terminal" className="h-full">
<div className="flex h-full flex-col bg-background p-4 font-code text-xs">
<p>AeThex Terminal</p>
<p>Copyright (c) 2024. All rights reserved.</p>
<div className="mt-4 flex items-center gap-2">
<HardDrive className="h-3 w-3 text-accent" />
<span className="text-accent">~/aethex-project</span>
<ChevronRight className="h-3 w-3" />
<span className="flex-1"></span>
</div>
</div>
</TabsContent>
</Tabs>
</div>
);
}
```
---
## FILE: src/components/aethex/code-editor.tsx
```tsx
"use client";
import type { Dispatch, SetStateAction } from "react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button";
import { X } from "lucide-react";
import { ScrollArea } from "@/components/ui/scroll-area";
import type { OpenFileType } from "./aethex-studio";
type CodeEditorProps = {
openFiles: OpenFileType[];
activeTab: string;
setActiveTab: Dispatch<SetStateAction<string>>;
onCloseFile: (fileId: string) => void;
};
export function CodeEditor({
openFiles,
activeTab,
setActiveTab,
onCloseFile,
}: CodeEditorProps) {
const handleCloseTab = (
e: React.MouseEvent<HTMLButtonElement>,
fileId: string
) => {
e.stopPropagation();
onCloseFile(fileId);
};
if (openFiles.length === 0) {
return (
<div className="flex h-full items-center justify-center bg-card text-muted-foreground">
<p>No files open. Select a file from the navigator.</p>
</div>
);
}
return (
<Tabs
value={activeTab}
onValueChange={setActiveTab}
className="flex h-full flex-col"
>
<TabsList className="m-0 flex h-auto justify-start rounded-none border-b bg-transparent p-0">
{openFiles.map((file) => (
<TabsTrigger
key={file.id}
value={file.id}
className="group relative h-10 rounded-none border-r border-t-2 border-t-transparent bg-card px-4 py-2 text-muted-foreground shadow-none data-[state=active]:border-t-primary data-[state=active]:bg-background data-[state=active]:text-foreground"
>
{file.name}
<Button
variant="ghost"
size="icon"
className="absolute right-1 top-1/2 h-5 w-5 -translate-y-1/2 opacity-0 group-hover:opacity-100"
onClick={(e) => handleCloseTab(e, file.id)}
>
<X className="h-3 w-3" />
</Button>
</TabsTrigger>
))}
</TabsList>
{openFiles.map((file) => (
<TabsContent
key={file.id}
value={file.id}
className="m-0 flex-1 overflow-hidden"
>
<ScrollArea className="h-full">
<pre className="p-4 font-code text-sm">
<code
dangerouslySetInnerHTML={{ __html: file.content }}
></code>
</pre>
</ScrollArea>
</TabsContent>
))}
</Tabs>
);
}
```
---
## FILE: src/components/aethex/cross-platform-view.tsx
```tsx
"use client";
import Image from "next/image";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
platformCode,
crossPlatformState,
} from "@/lib/aethex-data";
import { PlaceHolderImages } from "@/lib/placeholder-images";
import { MobileIcon, RobloxIcon, WebIcon } from "./icons";
import {
AlertCircle,
CheckCircle2,
Bot,
Loader2,
ServerCrash,
ChevronsUpDown,
} from "lucide-react";
import { Button } from "../ui/button";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { ScrollArea } from "../ui/scroll-area";
import { useState } from "react";
import {
aiSuggestedSyncConflictResolution,
AISuggestedSyncConflictResolutionOutput,
} from "@/ai/flows/ai-suggested-sync-conflict-resolution";
export function CrossPlatformView() {
const robloxViewport = PlaceHolderImages.find((p) => p.id === "roblox-vp");
const webViewport = PlaceHolderImages.find((p) => p.id === "web-vp");
const mobileViewport = PlaceHolderImages.find((p) => p.id === "mobile-vp");
return (
<ResizablePanelGroup direction="vertical" className="h-full w-full">
<ResizablePanel defaultSize={50} minSize={30}>
<div className="grid h-full grid-cols-3 gap-2 p-2">
{robloxViewport && (
<Viewport
platform="Roblox"
icon={<RobloxIcon />}
imageUrl={robloxViewport.imageUrl}
imageHint={robloxViewport.imageHint}
/>
)}
{webViewport && (
<Viewport
platform="Web"
icon={<WebIcon />}
imageUrl={webViewport.imageUrl}
imageHint={webViewport.imageHint}
/>
)}
{mobileViewport && (
<Viewport
platform="Mobile"
icon={<MobileIcon />}
imageUrl={mobileViewport.imageUrl}
imageHint={mobileViewport.imageHint}
/>
)}
</div>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={50} minSize={30}>
<div className="grid h-full grid-cols-3 gap-2 p-2">
<div className="col-span-2">
<PlatformCodeEditor />
</div>
<div className="flex flex-col">
<StateInspector />
</div>
</div>
</ResizablePanel>
</ResizablePanelGroup>
);
}
function Viewport({
platform,
icon,
imageUrl,
imageHint,
}: {
platform: string;
icon: React.ReactNode;
imageUrl: string;
imageHint: string;
}) {
return (
<Card className="flex flex-col">
<CardHeader className="flex flex-row items-center justify-between p-3">
<div className="flex items-center gap-2">
{icon}
<CardTitle className="text-base font-headline">{platform}</CardTitle>
</div>
<div className="flex items-center gap-1.5 text-green-400">
<CheckCircle2 className="h-3 w-3" />
<span className="text-xs">Synced</span>
</div>
</CardHeader>
<CardContent className="flex-1 p-0">
<div className="relative h-full w-full">
<Image
src={imageUrl}
alt={`${platform} viewport`}
fill
className="object-cover"
data-ai-hint={imageHint}
/>
</div>
</CardContent>
</Card>
);
}
function PlatformCodeEditor() {
return (
<Card className="h-full">
<Tabs defaultValue="lua" className="flex h-full flex-col">
<TabsList className="m-0 flex h-auto justify-start rounded-none border-b bg-card p-0">
{Object.entries(platformCode).map(([lang, { name }]) => (
<TabsTrigger
key={lang}
value={lang}
className="relative h-10 rounded-none border-r border-t-2 border-t-transparent bg-card px-4 py-2 text-muted-foreground shadow-none data-[state=active]:border-t-accent data-[state=active]:bg-background data-[state=active]:text-foreground"
>
{name}
</TabsTrigger>
))}
</TabsList>
{Object.entries(platformCode).map(([lang, { code }]) => (
<TabsContent
key={lang}
value={lang}
className="m-0 flex-1 overflow-hidden"
>
<ScrollArea className="h-full">
<pre className="p-4 font-code text-sm">
<code>{code}</code>
</pre>
</ScrollArea>
</TabsContent>
))}
</Tabs>
</Card>
);
}
function StateInspector() {
return (
<Card className="flex-1">
<CardHeader className="p-3">
<CardTitle className="text-base font-headline">
State Inspector
</CardTitle>
<CardDescription className="text-xs">
Real-time variable synchronization.
</CardDescription>
</CardHeader>
<CardContent className="p-0">
<ScrollArea className="h-[calc(100%-70px)]">
<Table>
<TableHeader>
<TableRow>
<TableHead className="pl-3">Variable</TableHead>
<TableHead>Value</TableHead>
<TableHead className="pr-3 text-right">Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{crossPlatformState.map((item) => (
<StateTableRow key={item.variable} item={item} />
))}
</TableBody>
</Table>
</ScrollArea>
</CardContent>
</Card>
);
}
function StateTableRow({
item,
}: {
item: (typeof crossPlatformState)[0];
}) {
const [isOpen, setIsOpen] = useState(false);
const [suggestion, setSuggestion] =
useState<AISuggestedSyncConflictResolutionOutput | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchSuggestion = async () => {
if (isLoading) return;
setIsLoading(true);
setError(null);
setSuggestion(null);
try {
const result = await aiSuggestedSyncConflictResolution({
robloxCode: platformCode.lua.code,
webCode: platformCode.javascript.code,
mobileCode: platformCode.typescript.code,
sharedState: JSON.stringify(
Object.fromEntries(
crossPlatformState.map((i) => [i.variable, i.web])
),
null,
2
),
});
setSuggestion(result);
} catch (e) {
setError("Failed to get AI suggestion. Please try again.");
console.error(e);
} finally {
setIsLoading(false);
}
};
const renderStatus = () => {
switch (item.status) {
case "synced":
return (
<div className="flex items-center justify-end gap-1.5 text-green-400">
<CheckCircle2 className="h-3 w-3" />
<span className="text-xs">Synced</span>
</div>
);
case "syncing":
return (
<div className="flex items-center justify-end gap-1.5 text-yellow-400">
<Loader2 className="h-3 w-3 animate-spin" />
<span className="text-xs">Syncing</span>
</div>
);
case "conflict":
return (
<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
<AlertDialogTrigger asChild>
<Button
variant="ghost"
size="sm"
onClick={fetchSuggestion}
disabled={isLoading}
className="h-auto p-1 text-red-500 hover:bg-red-500/10 hover:text-red-500"
>
<AlertCircle className="h-3 w-3" />
<span className="ml-1.5 text-xs">Conflict</span>
</Button>
</AlertDialogTrigger>
<AlertDialogContent className="max-w-2xl">
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2 font-headline">
<Bot /> AI Conflict Resolution
</AlertDialogTitle>
{isLoading && (
<div className="flex items-center justify-center p-12">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
)}
{error && (
<div className="flex flex-col items-center justify-center p-12 text-center">
<ServerCrash className="h-8 w-8 text-destructive" />
<p className="mt-4 text-destructive">{error}</p>
</div>
)}
{suggestion && (
<AlertDialogDescription>
{suggestion.explanation}
</AlertDialogDescription>
)}
</AlertDialogHeader>
{suggestion?.suggestedSolutions &&
suggestion.suggestedSolutions.length > 0 && (
<div className="my-4 rounded-md border bg-muted/50 p-4">
<h4 className="mb-2 font-semibold">Suggested Solutions:</h4>
<ul className="list-disc space-y-2 pl-5 font-code text-xs">
{suggestion.suggestedSolutions.map((solution, i) => (
<li key={i}>{solution}</li>
))}
</ul>
</div>
)}
{suggestion && (
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Apply Suggestion</AlertDialogAction>
</AlertDialogFooter>
)}
</AlertDialogContent>
</AlertDialog>
);
}
};
return (
<TableRow>
<TableCell className="pl-3 font-code text-xs font-medium">
{item.variable}
</TableCell>
<TableCell>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
size="sm"
className="h-auto w-full justify-between p-1.5 font-code text-xs"
>
<span className="truncate">{JSON.stringify(item.web)}</span>
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-3 font-code text-xs">
<div className="grid grid-cols-[auto_1fr] gap-x-2">
<span className="text-red-500">Roblox:</span>
<span className="text-purple-400">
{JSON.stringify(item.roblox)}
</span>
<span className="text-blue-500">Web:</span>
<span className="text-purple-400">
{JSON.stringify(item.web)}
</span>
<span className="text-green-500">Mobile:</span>
<span className="text-purple-400">
{JSON.stringify(item.mobile)}
</span>
</div>
</PopoverContent>
</Popover>
</TableCell>
<TableCell className="pr-3 text-right">{renderStatus()}</TableCell>
</TableRow>
);
}
```
---
## FILE: src/components/aethex/dashboard-page.tsx
```tsx
"use client";
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Gamepad2, Plus } from "lucide-react";
import { WorkspaceCard } from "./workspace-card";
import { WorkspaceCardSkeleton } from "./workspace-card-skeleton";
import { workspaces as initialWorkspaces } from "@/lib/workspaces";
import { NewProjectModal } from "./new-project-modal";
import { ProjectTemplate } from "@/lib/templates";
import { NewProjectFormValues } from "./new-project-modal";
import { MobileIcon, RobloxIcon, WebIcon } from "./icons";
type Workspace = typeof initialWorkspaces[0];
export function DashboardPage() {
const [workspaces, setWorkspaces] =
useState<Workspace[]>(initialWorkspaces);
const [loading, setLoading] = useState(true);
const [isNewProjectModalOpen, setIsNewProjectModalOpen] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
setLoading(false);
}, 1500);
return () => clearTimeout(timer);
}, []);
const handleCreateProject = (
template: ProjectTemplate,
config: NewProjectFormValues
) => {
// This is a mock implementation. In a real app, this would involve
// an API call to create a new project in the backend.
const newWorkspace: Workspace = {
id: `proj-${Date.now()}`,
name: config.projectName,
lastModified: "Just now",
platforms: config.platforms.map((p) => {
if (p === "roblox") return RobloxIcon;
if (p === "web") return WebIcon;
return MobileIcon;
}),
thumbnailUrlId: "workspace-thumb-4",
thumbnailImageHint: "futuristic city",
};
setWorkspaces((prev) => [newWorkspace, ...prev]);
setIsNewProjectModalOpen(false);
};
const renderContent = () => {
if (loading) {
return (
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{Array.from({ length: 3 }).map((_, i) => (
<WorkspaceCardSkeleton key={i} />
))}
</div>
);
}
if (workspaces.length === 0) {
return (
<div className="text-center">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-primary/10">
<Gamepad2 className="h-6 w-6 text-primary" />
</div>
<h3 className="mt-4 text-lg font-semibold text-foreground">
No projects yet
</h3>
<p className="mt-1 text-sm text-muted-foreground">
Get started by creating a new project.
</p>
<div className="mt-6">
<Button onClick={() => setIsNewProjectModalOpen(true)}>
<Plus className="-ml-0.5 mr-1.5 h-5 w-5" />
New Project
</Button>
</div>
</div>
);
}
return (
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{workspaces.map((ws) => (
<WorkspaceCard key={ws.id} workspace={ws} />
))}
</div>
);
};
return (
<>
<div className="min-h-screen bg-background">
<div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
<header className="mb-8 flex items-center justify-between">
<h1 className="font-headline text-3xl font-bold text-foreground">
My Workspaces
</h1>
<Button onClick={() => setIsNewProjectModalOpen(true)}>
<Plus className="-ml-1 mr-2" /> New Workspace
</Button>
</header>
<main>{renderContent()}</main>
</div>
</div>
<NewProjectModal
isOpen={isNewProjectModalOpen}
onClose={() => setIsNewProjectModalOpen(false)}
onCreateProject={handleCreateProject}
/>
</>
);
}
```
---
## FILE: src/components/aethex/file-navigator.tsx
```tsx
"use client";
import React from "react";
import {
File,
Folder,
ChevronRight,
FolderPlus,
FilePlus,
} from "lucide-react";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { FileNode, FolderNode, File as OpenFileType } from "@/lib/aethex-data";
import { generateFileContent } from "@/lib/templates";
type FileNavigatorProps = {
onOpenFile: (file: OpenFileType) => void;
fileTree: FolderNode;
};
export function FileNavigator({ onOpenFile, fileTree }: FileNavigatorProps) {
return (
<div className="flex h-full flex-col bg-card">
<div className="flex items-center justify-between border-b p-3">
<h2 className="font-headline text-lg font-semibold">Explorer</h2>
<div className="flex items-center gap-1">
<Button variant="ghost" size="icon" className="h-7 w-7">
<FilePlus className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" className="h-7 w-7">
<FolderPlus className="h-4 w-4" />
</Button>
</div>
</div>
<div className="flex-1 overflow-auto p-2">
<FileTree aKey="root" node={fileTree} onOpenFile={onOpenFile} path="" />
</div>
<div className="border-t p-2">
<Input placeholder="Search files..." />
</div>
</div>
);
}
type FileTreeProps = {
node: FolderNode | FileNode;
aKey: string;
onOpenFile: (file: OpenFileType) => void;
path: string;
};
function FileTree({ node, aKey, onOpenFile, path }: FileTreeProps) {
const handleFileClick = () => {
if (node.type === "file") {
const filePath = path ? `${path}/${node.name}` : node.name;
onOpenFile({
id: filePath,
name: node.name,
language: (node as FileNode).language,
content: generateFileContent(node.name, (node as FileNode).language),
});
}
};
if (node.type === "file") {
return (
<div
className="ml-5 flex cursor-pointer items-center justify-between gap-2 rounded-md py-1 pr-2 hover:bg-muted"
onClick={handleFileClick}
>
<div className="flex items-center gap-2 truncate pl-1">
<File className="h-4 w-4 shrink-0 text-muted-foreground" />
<span className="truncate text-sm">{node.name}</span>
</div>
</div>
);
}
const currentPath = path ? `${path}/${node.name}` : node.name;
return (
<Collapsible defaultOpen={aKey === "root" || node.name === "roblox"}>
<CollapsibleTrigger asChild>
<div className="group flex cursor-pointer items-center justify-between gap-2 rounded-md p-1 pr-2 hover:bg-muted">
<div className="flex items-center gap-2 truncate">
<ChevronRight className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200 group-data-[state=open]:rotate-90" />
<Folder className="h-4 w-4 shrink-0 text-muted-foreground" />
<span className="truncate font-semibold text-sm">{node.name}</span>
</div>
</div>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="pl-4">
{node.children.map((child, index) => (
<FileTree
key={index}
aKey={child.name}
node={child}
onOpenFile={onOpenFile}
path={currentPath}
/>
))}
</div>
</CollapsibleContent>
</Collapsible>
);
}
```
---
## FILE: src/components/aethex/icons.tsx
```tsx
import type { SVGProps } from "react";
export function AethexLogo(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M14.5 13.03a3 3 0 1 0-3.5-3.53" />
<path d="M12 2a10 10 0 1 0 10 10" />
</svg>
);
}
export function RobloxIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4 text-red-500"
{...props}
>
<path d="m11.9 2.7-8.2 3.4 3.4 8.2 8.2-3.4Z" />
<path d="m13.4 7.2-5.7 2.4" />
<path d="m19.2 8.5-8.2 3.4-3.4-8.2 8.2-3.4Z" />
</svg>
);
}
export function WebIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4 text-blue-500"
{...props}
>
<circle cx="12" cy="12" r="10" />
<path d="M2 12h20" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
);
}
export function MobileIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4 text-green-500"
{...props}
>
<rect width="14" height="20" x="5" y="2" rx="2" ry="2" />
<path d="M12 18h.01" />
</svg>
);
}
export function GoogleIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px" {...props}>
<path fill="#FFC107" d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12c0-6.627,5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24c0,11.045,8.955,20,20,20c11.045,0,20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"/>
<path fill="#FF3D00" d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"/>
<path fill="#4CAF50" d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"/>
<path fill="#1976D2" d="M43.611,20.083H42V20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.574l6.19,5.238C39.99,36.596,44,30.85,44,24C44,22.659,43.862,21.35,43.611,20.083z"/>
</svg>
)
}
```
---
## FILE: src/components/aethex/login-page.tsx
```tsx
"use client";
import Link from "next/link";
import Image from "next/image";
import { AethexLogo, GoogleIcon } from "@/components/aethex/icons";
import { Button } from "@/components/ui/button";
import { Github } from "lucide-react";
import { PlaceHolderImages } from "@/lib/placeholder-images";
export function LoginPage() {
const loginIllustration = PlaceHolderImages.find(
(p) => p.id === "login-illustration"
);
return (
<div className="flex min-h-screen w-full bg-background">
<div className="flex flex-1 flex-col justify-center px-4 py-12 sm:px-6 lg:flex-none lg:px-20 xl:px-24">
<div className="mx-auto w-full max-w-sm lg:w-96">
<div>
<AethexLogo className="h-10 w-auto text-primary" />
<h1 className="mt-6 font-headline text-3xl font-bold tracking-tight text-foreground">
Welcome to AeThex Studio
</h1>
<p className="mt-2 text-sm text-muted-foreground">
The Next-Generation Cross-Platform IDE.
</p>
</div>
<div className="mt-8">
<div className="space-y-3">
<Link href="/dashboard" passHref>
<Button
size="lg"
className="w-full bg-gradient-to-r from-primary via-purple-500 to-fuchsia-500 text-primary-foreground transition-all hover:opacity-90"
>
Sign in with AeThex Passport
</Button>
</Link>
<Link href="/dashboard" passHref>
<Button size="lg" variant="outline" className="w-full">
<GoogleIcon className="mr-3 h-5 w-5" />
Sign in with Google
</Button>
</Link>
<Link href="/dashboard" passHref>
<Button size="lg" variant="outline" className="w-full">
<Github className="mr-3 h-5 w-5" />
Sign in with GitHub
</Button>
</Link>
</div>
</div>
</div>
</div>
<div className="relative hidden w-0 flex-1 lg:block">
{loginIllustration && (
<Image
className="absolute inset-0 h-full w-full object-cover"
src={loginIllustration.imageUrl}
alt="Cross-platform game development illustration"
data-ai-hint={loginIllustration.imageHint}
fill
priority
/>
)}
</div>
</div>
);
}
```
---
## FILE: src/components/aethex/main-view.tsx
```tsx
"use client";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { CodeEditor } from "./code-editor";
import { CrossPlatformView } from "./cross-platform-view";
import type { Dispatch, SetStateAction } from "react";
import type { OpenFileType } from "./aethex-studio";
type MainViewProps = {
openFiles: OpenFileType[];
activeTab: string;
setActiveTab: Dispatch<SetStateAction<string>>;
onCloseFile: (fileId: string) => void;
};
export function MainView({ openFiles, activeTab, setActiveTab, onCloseFile }: MainViewProps) {
return (
<div className="h-full bg-background">
<Tabs defaultValue="editor" className="flex h-full flex-col">
<TabsList className="ml-2 mt-2 h-auto self-start rounded-md bg-card p-1">
<TabsTrigger value="editor">Editor</TabsTrigger>
<TabsTrigger value="cross-platform">Cross-Platform View</TabsTrigger>
</TabsList>
<TabsContent value="editor" className="m-0 flex-1 overflow-hidden">
<CodeEditor
openFiles={openFiles}
activeTab={activeTab}
setActiveTab={setActiveTab}
onCloseFile={onCloseFile}
/>
</TabsContent>
<TabsContent
value="cross-platform"
className="m-0 flex-1 overflow-hidden"
>
<CrossPlatformView />
</TabsContent>
</Tabs>
</div>
);
}
```
---
## FILE: src/components/aethex/navbar.tsx
```tsx
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Check, FilePlus, Loader, Play, Bot } from "lucide-react";
import { AethexLogo, MobileIcon, RobloxIcon, WebIcon } from "./icons";
import { cn } from "@/lib/utils";
type NavbarProps = {
onNewProjectClick: () => void;
};
export function Navbar({ onNewProjectClick }: NavbarProps) {
const [saveStatus, setSaveStatus] = useState("Saved");
const [syncStatus, setSyncStatus] = useState("synced");
useEffect(() => {
const interval = setInterval(() => {
setSaveStatus("Saving...");
setTimeout(() => setSaveStatus("Saved"), 1000);
}, 5000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
const statuses = ["synced", "syncing", "error"];
let i = 0;
const interval = setInterval(() => {
i = (i + 1) % statuses.length;
setSyncStatus(statuses[i]);
}, 7000);
return () => clearInterval(interval);
}, []);
return (
<header className="flex h-12 shrink-0 items-center justify-between border-b bg-card px-4">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<AethexLogo className="h-6 w-6 text-primary" />
<h1 className="font-headline text-xl font-bold">AeThex Studio</h1>
</div>
<Button variant="outline" size="sm" onClick={onNewProjectClick}>
<FilePlus className="mr-2 h-4 w-4" />
New Project
</Button>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
{saveStatus === "Saving..." ? (
<>
<Loader className="h-3 w-3 animate-spin" />
<span>Saving...</span>
</>
) : (
<>
<Check className="h-3 w-3" />
<span>Auto-Saved</span>
</>
)}
</div>
</div>
<div className="flex items-center gap-4">
<Button variant="secondary" size="sm">
<Bot className="mr-2 h-4 w-4" />
AI Actions
</Button>
<div
className="flex items-center gap-2"
title={`Status: ${syncStatus}`}
>
<span className="text-xs text-muted-foreground">Sync Status</span>
<div
className={cn("h-2.5 w-2.5 rounded-full", {
"bg-green-500": syncStatus === "synced",
"animate-pulse bg-yellow-500": syncStatus === "syncing",
"bg-red-500": syncStatus === "error",
})}
/>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm">
<RobloxIcon className="mr-2" />
Deploy
</Button>
<Button variant="outline" size="sm">
<WebIcon className="mr-2" />
Deploy
</Button>
<Button variant="outline" size="sm">
<MobileIcon className="mr-2" />
Deploy
</Button>
</div>
<Button size="sm">
<Play className="mr-2 h-4 w-4" />
Run All
</Button>
</div>
</header>
);
}
```
---
## FILE: src/components/aethex/new-project-modal.tsx
```tsx
"use client";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
DialogDescription,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Stepper } from "@/components/ui/stepper";
import {
Card,
CardContent,
CardHeader,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { Switch } from "@/components/ui/switch";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { projectTemplates, ProjectTemplate } from "@/lib/templates";
import { cn } from "@/lib/utils";
import { Crosshair, Fingerprint } from "lucide-react";
const steps = [
{ label: "Choose Template" },
{ label: "Configure" },
{ label: "Review & Create" },
];
const formSchema = z.object({
projectName: z.string().min(1, "Project name is required."),
platforms: z.array(z.string()).refine((value) => value.some((item) => item), {
message: "You have to select at least one platform.",
}),
enableNexus: z.boolean(),
enablePassport: z.boolean(),
});
export type NewProjectFormValues = z.infer<typeof formSchema>;
type NewProjectModalProps = {
isOpen: boolean;
onClose: () => void;
onCreateProject: (
template: ProjectTemplate,
config: NewProjectFormValues
) => void;
};
export function NewProjectModal({
isOpen,
onClose,
onCreateProject,
}: NewProjectModalProps) {
const [currentStep, setCurrentStep] = useState(1);
const [selectedTemplate, setSelectedTemplate] =
useState<ProjectTemplate | null>(null);
const form = useForm<NewProjectFormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
projectName: "",
platforms: ["web"],
enableNexus: true,
enablePassport: false,
},
});
const handleNext = () => {
if (currentStep === 1 && selectedTemplate) {
form.setValue(
"projectName",
selectedTemplate.id === "blank"
? ""
: selectedTemplate.name.toLowerCase().replace(/\s/g, "-")
);
setCurrentStep(2);
} else if (currentStep === 2) {
form.trigger().then((isValid) => {
if (isValid) {
setCurrentStep(3);
}
});
}
};
const handleBack = () => {
if (currentStep > 1) {
setCurrentStep(currentStep - 1);
}
};
const handleCreate = () => {
if (selectedTemplate) {
onCreateProject(selectedTemplate, form.getValues());
resetAndClose();
}
};
const resetAndClose = () => {
form.reset();
setCurrentStep(1);
setSelectedTemplate(null);
onClose();
};
const platforms = [
{ id: "roblox", label: "Roblox" },
{ id: "web", label: "Web" },
{ id: "mobile", label: "Mobile" },
];
return (
<Dialog open={isOpen} onOpenChange={resetAndClose}>
<DialogContent className="max-w-4xl">
<DialogHeader>
<DialogTitle>Create New Project</DialogTitle>
<DialogDescription>
Start a new project from a template or from scratch.
</DialogDescription>
</DialogHeader>
<div className="my-6">
<Stepper steps={steps} currentStep={currentStep} />
</div>
{currentStep === 1 && (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
{projectTemplates.map((template) => (
<Card
key={template.id}
onClick={() => setSelectedTemplate(template)}
className={cn(
"cursor-pointer hover:border-primary",
selectedTemplate?.id === template.id &&
"border-2 border-primary"
)}
>
<CardHeader>
<div className="flex items-center justify-between">
<template.icon className="h-8 w-8 text-primary" />
{template.isPopular && (
<Badge variant="secondary">Popular</Badge>
)}
</div>
</CardHeader>
<CardContent>
<h3 className="font-semibold">{template.name}</h3>
<p className="text-sm text-muted-foreground">
{template.description}
</p>
</CardContent>
</Card>
))}
</div>
)}
{currentStep === 2 && (
<Form {...form}>
<form className="space-y-8">
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<FormItem>
<FormLabel>Project Name</FormLabel>
<FormControl>
<Input placeholder="my-awesome-project" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="platforms"
render={() => (
<FormItem>
<FormLabel>Target Platforms</FormLabel>
<div className="flex items-center space-x-4 pt-2">
{platforms.map((item) => (
<FormField
key={item.id}
control={form.control}
name="platforms"
render={({ field }) => {
return (
<FormItem
key={item.id}
className="flex flex-row items-start space-x-3 space-y-0"
>
<FormControl>
<Checkbox
checked={field.value?.includes(item.id)}
onCheckedChange={(checked) => {
return checked
? field.onChange([
...field.value,
item.id,
])
: field.onChange(
field.value?.filter(
(value) => value !== item.id
)
);
}}
/>
</FormControl>
<FormLabel className="font-normal">
{item.label}
</FormLabel>
</FormItem>
);
}}
/>
))}
</div>
<FormMessage />
</FormItem>
)}
/>
<div className="space-y-4">
<FormLabel>Add-on Features</FormLabel>
<p className="text-sm text-muted-foreground">
Enhance your project with powerful AeThex services.
</p>
<div className="grid grid-cols-1 gap-4 pt-2 md:grid-cols-2">
<FormField
control={form.control}
name="enableNexus"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel className="flex items-center gap-2">
<Crosshair className="h-4 w-4 text-accent" />
Enable Nexus Engine
</FormLabel>
<p className="text-xs text-muted-foreground">
Real-time state synchronization.
</p>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="enablePassport"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel className="flex items-center gap-2">
<Fingerprint className="h-4 w-4 text-accent" />
Enable Passport Auth
</FormLabel>
<p className="text-xs text-muted-foreground">
Unified identity across platforms.
</p>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</div>
</div>
</form>
</Form>
)}
{currentStep === 3 && selectedTemplate && (
<div className="space-y-4">
<h3 className="text-lg font-semibold">Review your project</h3>
<Card>
<CardContent className="grid grid-cols-1 gap-y-6 p-6 sm:grid-cols-2 sm:gap-x-8">
<div>
<p className="text-sm font-semibold text-muted-foreground">
Project Name
</p>
<p className="font-medium">
{form.getValues("projectName")}
</p>
</div>
<div>
<p className="text-sm font-semibold text-muted-foreground">
Template
</p>
<p className="font-medium">{selectedTemplate.name}</p>
</div>
<div>
<p className="text-sm font-semibold text-muted-foreground">
Platforms
</p>
<p className="font-medium">
{form.getValues("platforms").join(", ")}
</p>
</div>
<div>
<p className="text-sm font-semibold text-muted-foreground">
Features
</p>
<div className="flex flex-col gap-2 pt-1">
{form.getValues("enableNexus") && (
<span className="flex items-center gap-2 font-medium">
<Crosshair className="h-4 w-4 text-accent" /> Nexus
Engine
</span>
)}
{form.getValues("enablePassport") && (
<span className="flex items-center gap-2 font-medium">
<Fingerprint className="h-4 w-4 text-accent" /> Passport
Auth
</span>
)}
{!form.getValues("enableNexus") &&
!form.getValues("enablePassport") && (
<p className="font-medium text-muted-foreground">
None
</p>
)}
</div>
</div>
</CardContent>
</Card>
</div>
)}
<DialogFooter>
{currentStep > 1 && (
<Button variant="outline" onClick={handleBack}>
Back
</Button>
)}
{currentStep < 3 && (
<Button
onClick={handleNext}
disabled={currentStep === 1 && !selectedTemplate}
>
Next
</Button>
)}
{currentStep === 3 && (
<Button onClick={handleCreate}>Create Project</Button>
)}
</DialogFooter>
</DialogContent>
</Dialog>
);
}
```
---
## FILE: src/components/aethex/workspace-card-skeleton.tsx
```tsx
"use client";
import {
Card,
CardContent,
CardFooter,
CardHeader,
} from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
export function WorkspaceCardSkeleton() {
return (
<Card className="overflow-hidden">
<CardHeader className="p-0">
<Skeleton className="aspect-[4/3] w-full" />
</CardHeader>
<CardContent className="p-4">
<Skeleton className="h-6 w-3/4" />
<Skeleton className="mt-2 h-3 w-1/2" />
</CardContent>
<CardFooter className="flex items-center justify-between p-4 pt-0">
<div className="flex -space-x-2">
<Skeleton className="h-6 w-6 rounded-full" />
<Skeleton className="h-6 w-6 rounded-full" />
</div>
<Skeleton className="h-8 w-8" />
</CardFooter>
</Card>
);
}
```
---
## FILE: src/components/aethex/workspace-card.tsx
```tsx
"use client";
import Link from "next/link";
import Image from "next/image";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { MoreHorizontal } from "lucide-react";
import type { Workspace } from "@/lib/workspaces";
import { PlaceHolderImages } from "@/lib/placeholder-images";
type WorkspaceCardProps = {
workspace: Workspace;
};
export function WorkspaceCard({ workspace }: WorkspaceCardProps) {
const thumbnail = PlaceHolderImages.find(
(p) => p.id === workspace.thumbnailUrlId
);
return (
<Card className="overflow-hidden transition-all hover:shadow-lg hover:shadow-primary/10">
<CardHeader className="p-0">
<Link href="/ide" className="block aspect-[4/3] w-full">
<div className="relative h-full w-full">
{thumbnail && (
<Image
src={thumbnail.imageUrl}
alt={workspace.name}
fill
className="object-cover"
data-ai-hint={thumbnail.imageHint}
/>
)}
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
</div>
</Link>
</CardHeader>
<CardContent className="p-4">
<CardTitle className="text-lg font-bold tracking-normal">
<Link href="/ide" className="hover:text-primary">
{workspace.name}
</Link>
</CardTitle>
<p className="text-xs text-muted-foreground">
Last modified {workspace.lastModified}
</p>
</CardContent>
<CardFooter className="flex items-center justify-between p-4 pt-0">
<div className="flex -space-x-2">
{workspace.platforms.map((Icon, index) => (
<div
key={index}
className="z-10 flex h-6 w-6 items-center justify-center rounded-full border-2 border-card bg-card"
>
<Icon className="h-4 w-4" />
</div>
))}
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Rename</DropdownMenuItem>
<DropdownMenuItem>Share</DropdownMenuItem>
<DropdownMenuItem className="text-destructive focus:text-destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</CardFooter>
</Card>
);
}
```
---
## FILE: src/components/ui/accordion.tsx
```tsx
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
```
---
## FILE: src/components/ui/alert-dialog.tsx
```tsx
"use client"
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
const AlertDialog = AlertDialogPrimitive.Root
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
const AlertDialogPortal = AlertDialogPrimitive.Portal
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
/>
</AlertDialogPortal>
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
AlertDialogHeader.displayName = "AlertDialogHeader"
const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
AlertDialogFooter.displayName = "AlertDialogFooter"
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold", className)}
{...props}
/>
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)}
{...props}
/>
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
className
)}
{...props}
/>
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}
```
---
## FILE: src/components/ui/alert.tsx
```tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = "Alert"
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"
export { Alert, AlertTitle, AlertDescription }
```
---
## FILE: src/components/ui/avatar.tsx
```tsx
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }
```
---
## FILE: src/components/ui/badge.tsx
```tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }
```
---
## FILE: src/components/ui/button.tsx
```tsx
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
```
---
## FILE: src/components/ui/calendar.tsx
```tsx
"use client"
import * as React from "react"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { DayPicker } from "react-day-picker"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
export type CalendarProps = React.ComponentProps<typeof DayPicker>
function Calendar({
className,
classNames,
showOutsideDays = true,
...props
}: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn("p-3", className)}
classNames={{
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
month: "space-y-4",
caption: "flex justify-center pt-1 relative items-center",
caption_label: "text-sm font-medium",
nav: "space-x-1 flex items-center",
nav_button: cn(
buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
),
nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1",
table: "w-full border-collapse space-y-1",
head_row: "flex",
head_cell:
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
row: "flex w-full mt-2",
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
day: cn(
buttonVariants({ variant: "ghost" }),
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
),
day_range_end: "day-range-end",
day_selected:
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
day_today: "bg-accent text-accent-foreground",
day_outside:
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
day_disabled: "text-muted-foreground opacity-50",
day_range_middle:
"aria-selected:bg-accent aria-selected:text-accent-foreground",
day_hidden: "invisible",
...classNames,
}}
components={{
IconLeft: ({ className, ...props }) => (
<ChevronLeft className={cn("h-4 w-4", className)} {...props} />
),
IconRight: ({ className, ...props }) => (
<ChevronRight className={cn("h-4 w-4", className)} {...props} />
),
}}
{...props}
/>
)
}
Calendar.displayName = "Calendar"
export { Calendar }
```
---
## FILE: src/components/ui/card.tsx
```tsx
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
```
---
## FILE: src/components/ui/carousel.tsx
```tsx
"use client"
import * as React from "react"
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react"
import { ArrowLeft, ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
type CarouselOptions = UseCarouselParameters[0]
type CarouselPlugin = UseCarouselParameters[1]
type CarouselProps = {
opts?: CarouselOptions
plugins?: CarouselPlugin
orientation?: "horizontal" | "vertical"
setApi?: (api: CarouselApi) => void
}
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
api: ReturnType<typeof useEmblaCarousel>[1]
scrollPrev: () => void
scrollNext: () => void
canScrollPrev: boolean
canScrollNext: boolean
} & CarouselProps
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
function useCarousel() {
const context = React.useContext(CarouselContext)
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />")
}
return context
}
const Carousel = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & CarouselProps
>(
(
{
orientation = "horizontal",
opts,
setApi,
plugins,
className,
children,
...props
},
ref
) => {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) {
return
}
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
}, [api])
const scrollNext = React.useCallback(() => {
api?.scrollNext()
}, [api])
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
},
[scrollPrev, scrollNext]
)
React.useEffect(() => {
if (!api || !setApi) {
return
}
setApi(api)
}, [api, setApi])
React.useEffect(() => {
if (!api) {
return
}
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
return () => {
api?.off("select", onSelect)
}
}, [api, onSelect])
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<div
ref={ref}
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
)
}
)
Carousel.displayName = "Carousel"
const CarouselContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel()
return (
<div ref={carouselRef} className="overflow-hidden">
<div
ref={ref}
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className
)}
{...props}
/>
</div>
)
})
CarouselContent.displayName = "CarouselContent"
const CarouselItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { orientation } = useCarousel()
return (
<div
ref={ref}
role="group"
aria-roledescription="slide"
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className
)}
{...props}
/>
)
})
CarouselItem.displayName = "CarouselItem"
const CarouselPrevious = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-left-12 top-1/2 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
>
<ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
)
})
CarouselPrevious.displayName = "CarouselPrevious"
const CarouselNext = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel()
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-right-12 top-1/2 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}
>
<ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
)
})
CarouselNext.displayName = "CarouselNext"
export {
type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
}
```
---
## FILE: src/components/ui/chart.tsx
```tsx
"use client"
import * as React from "react"
import * as RechartsPrimitive from "recharts"
import { cn } from "@/lib/utils"
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const
export type ChartConfig = {
[k in string]: {
label?: React.ReactNode
icon?: React.ComponentType
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
)
}
type ChartContextProps = {
config: ChartConfig
}
const ChartContext = React.createContext<ChartContextProps | null>(null)
function useChart() {
const context = React.useContext(ChartContext)
if (!context) {
throw new Error("useChart must be used within a <ChartContainer />")
}
return context
}
const ChartContainer = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
config: ChartConfig
children: React.ComponentProps<
typeof RechartsPrimitive.ResponsiveContainer
>["children"]
}
>(({ id, className, children, config, ...props }, ref) => {
const uniqueId = React.useId()
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
return (
<ChartContext.Provider value={{ config }}>
<div
data-chart={chartId}
ref={ref}
className={cn(
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
className
)}
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer>
{children}
</RechartsPrimitive.ResponsiveContainer>
</div>
</ChartContext.Provider>
)
})
ChartContainer.displayName = "Chart"
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(
([, config]) => config.theme || config.color
)
if (!colorConfig.length) {
return null
}
return (
<style
dangerouslySetInnerHTML={{
__html: Object.entries(THEMES)
.map(
([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color =
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
itemConfig.color
return color ? ` --color-${key}: ${color};` : null
})
.join("\n")}
}
`
)
.join("\n"),
}}
/>
)
}
const ChartTooltip = RechartsPrimitive.Tooltip
const ChartTooltipContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
React.ComponentProps<"div"> & {
hideLabel?: boolean
hideIndicator?: boolean
indicator?: "line" | "dot" | "dashed"
nameKey?: string
labelKey?: string
}
>(
(
{
active,
payload,
className,
indicator = "dot",
hideLabel = false,
hideIndicator = false,
label,
labelFormatter,
labelClassName,
formatter,
color,
nameKey,
labelKey,
},
ref
) => {
const { config } = useChart()
const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) {
return null
}
const [item] = payload
const key = `${labelKey || item.dataKey || item.name || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const value =
!labelKey && typeof label === "string"
? config[label as keyof typeof config]?.label || label
: itemConfig?.label
if (labelFormatter) {
return (
<div className={cn("font-medium", labelClassName)}>
{labelFormatter(value, payload)}
</div>
)
}
if (!value) {
return null
}
return <div className={cn("font-medium", labelClassName)}>{value}</div>
}, [
label,
labelFormatter,
payload,
hideLabel,
labelClassName,
config,
labelKey,
])
if (!active || !payload?.length) {
return null
}
const nestLabel = payload.length === 1 && indicator !== "dot"
return (
<div
ref={ref}
className={cn(
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
className
)}
>
{!nestLabel ? tooltipLabel : null}
<div className="grid gap-1.5">
{payload.map((item, index) => {
const key = `${nameKey || item.name || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const indicatorColor = color || item.payload.fill || item.color
return (
<div
key={item.dataKey}
className={cn(
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
indicator === "dot" && "items-center"
)}
>
{formatter && item?.value !== undefined && item.name ? (
formatter(item.value, item.name, item, index, item.payload)
) : (
<>
{itemConfig?.icon ? (
<itemConfig.icon />
) : (
!hideIndicator && (
<div
className={cn(
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
{
"h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line",
"w-0 border-[1.5px] border-dashed bg-transparent":
indicator === "dashed",
"my-0.5": nestLabel && indicator === "dashed",
}
)}
style={
{
"--color-bg": indicatorColor,
"--color-border": indicatorColor,
} as React.CSSProperties
}
/>
)
)}
<div
className={cn(
"flex flex-1 justify-between leading-none",
nestLabel ? "items-end" : "items-center"
)}
>
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-muted-foreground">
{itemConfig?.label || item.name}
</span>
</div>
{item.value && (
<span className="font-mono font-medium tabular-nums text-foreground">
{item.value.toLocaleString()}
</span>
)}
</div>
</>
)}
</div>
)
})}
</div>
</div>
)
}
)
ChartTooltipContent.displayName = "ChartTooltip"
const ChartLegend = RechartsPrimitive.Legend
const ChartLegendContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> &
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean
nameKey?: string
}
>(
(
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
ref
) => {
const { config } = useChart()
if (!payload?.length) {
return null
}
return (
<div
ref={ref}
className={cn(
"flex items-center justify-center gap-4",
verticalAlign === "top" ? "pb-3" : "pt-3",
className
)}
>
{payload.map((item) => {
const key = `${nameKey || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
return (
<div
key={item.value}
className={cn(
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
)}
>
{itemConfig?.icon && !hideIcon ? (
<itemConfig.icon />
) : (
<div
className="h-2 w-2 shrink-0 rounded-[2px]"
style={{
backgroundColor: item.color,
}}
/>
)}
{itemConfig?.label}
</div>
)
})}
</div>
)
}
)
ChartLegendContent.displayName = "ChartLegend"
// Helper to extract item config from a payload.
function getPayloadConfigFromPayload(
config: ChartConfig,
payload: unknown,
key: string
) {
if (typeof payload !== "object" || payload === null) {
return undefined
}
const payloadPayload =
"payload" in payload &&
typeof payload.payload === "object" &&
payload.payload !== null
? payload.payload
: undefined
let configLabelKey: string = key
if (
key in payload &&
typeof payload[key as keyof typeof payload] === "string"
) {
configLabelKey = payload[key as keyof typeof payload] as string
} else if (
payloadPayload &&
key in payloadPayload &&
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
) {
configLabelKey = payloadPayload[
key as keyof typeof payloadPayload
] as string
}
return configLabelKey in config
? config[configLabelKey]
: config[key as keyof typeof config]
}
export {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
ChartStyle,
}
```
---
## FILE: src/components/ui/checkbox.tsx
```tsx
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }
```
---
## FILE: src/components/ui/collapsible.tsx
```tsx
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
const Collapsible = CollapsiblePrimitive.Root
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
```
---
## FILE: src/components/ui/dialog.tsx
```tsx
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
```
---
## FILE: src/components/ui/dropdown-menu.tsx
```tsx
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}
```
---
## FILE: src/components/ui/form.tsx
```tsx
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
Controller,
FormProvider,
useFormContext,
type ControllerProps,
type FieldPath,
type FieldValues,
} from "react-hook-form"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
type FormItemContextValue = {
id: string
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
)
})
FormItem.displayName = "FormItem"
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = "FormLabel"
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = "FormControl"
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
})
FormDescription.displayName = "FormDescription"
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message ?? "") : children
if (!body) {
return null
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-sm font-medium text-destructive", className)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = "FormMessage"
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}
```
---
## FILE: src/components/ui/input.tsx
```tsx
import * as React from "react"
import { cn } from "@/lib/utils"
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }
```
---
## FILE: src/components/ui/label.tsx
```tsx
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }
```
---
## FILE: src/components/ui/menubar.tsx
```tsx
"use client"
import * as React from "react"
import * as MenubarPrimitive from "@radix-ui/react-menubar"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
function MenubarMenu({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
return <MenubarPrimitive.Menu {...props} />
}
function MenubarGroup({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Group>) {
return <MenubarPrimitive.Group {...props} />
}
function MenubarPortal({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
return <MenubarPrimitive.Portal {...props} />
}
function MenubarRadioGroup({
...props
}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
return <MenubarPrimitive.RadioGroup {...props} />
}
function MenubarSub({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />
}
const Menubar = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Root
ref={ref}
className={cn(
"flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
className
)}
{...props}
/>
))
Menubar.displayName = MenubarPrimitive.Root.displayName
const MenubarTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Trigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
className
)}
{...props}
/>
))
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
const MenubarSubTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<MenubarPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger>
))
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
const MenubarSubContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
className
)}
{...props}
/>
))
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
const MenubarContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
>(
(
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
ref
) => (
<MenubarPrimitive.Portal>
<MenubarPrimitive.Content
ref={ref}
align={align}
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
className
)}
{...props}
/>
</MenubarPrimitive.Portal>
)
)
MenubarContent.displayName = MenubarPrimitive.Content.displayName
const MenubarItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
MenubarItem.displayName = MenubarPrimitive.Item.displayName
const MenubarCheckboxItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<MenubarPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.CheckboxItem>
))
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
const MenubarRadioItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<MenubarPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.RadioItem>
))
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
const MenubarLabel = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
MenubarLabel.displayName = MenubarPrimitive.Label.displayName
const MenubarSeparator = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
const MenubarShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
MenubarShortcut.displayname = "MenubarShortcut"
export {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
MenubarSeparator,
MenubarLabel,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarPortal,
MenubarSubContent,
MenubarSubTrigger,
MenubarGroup,
MenubarSub,
MenubarShortcut,
}
```
---
## FILE: src/components/ui/popover.tsx
```tsx
"use client"
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent }
```
---
## FILE: src/components/ui/progress.tsx
```tsx
"use client"
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@/lib/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }
```
---
## FILE: src/components/ui/radio-group.tsx
```tsx
"use client"
import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cn("grid gap-2", className)}
{...props}
ref={ref}
/>
)
})
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="h-2.5 w-2.5 fill-current text-current" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
})
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
export { RadioGroup, RadioGroupItem }
```
---
## FILE: src/components/ui/resizable.tsx
```tsx
"use client";
import { cn } from "@/lib/utils";
import { GripVertical } from "lucide-react";
import * as React from "react";
type Direction = "horizontal" | "vertical";
type ResizablePanelGroupContextType = {
direction: Direction;
sizes: number[];
setSizes: (sizes: number[]) => void;
minSizes: number[];
maxSizes: number[];
};
const ResizablePanelGroupContext = React.createContext<ResizablePanelGroupContextType | null>(null);
const useResizablePanelGroup = () => {
const context = React.useContext(ResizablePanelGroupContext);
if (!context) {
throw new Error("useResizablePanelGroup must be used within a ResizablePanelGroup");
}
return context;
};
export const ResizablePanelGroup = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & {
direction: Direction;
}
>(({ direction, children, className, ...props }, ref) => {
const [sizes, setSizes] = React.useState<number[]>([]);
const [minSizes, setMinSizes] = React.useState<number[]>([]);
const [maxSizes, setMaxSizes] = React.useState<number[]>([]);
React.useEffect(() => {
const panels = React.Children.toArray(children).filter(
(child) => React.isValidElement(child) && child.type === ResizablePanel
);
const panelCount = panels.length;
if (panelCount > 0) {
const initialSizes = panels.map(
(child) => (React.isValidElement(child) && child.props.defaultSize ? child.props.defaultSize : 100 / panelCount)
);
const sum = initialSizes.reduce((a, b) => a + b, 0);
if (sum > 100.1 || sum < 99.9) {
const factor = 100 / sum;
setSizes(initialSizes.map(s => s * factor));
} else {
setSizes(initialSizes);
}
setMinSizes(panels.map(
(child) => (React.isValidElement(child) ? child.props.minSize || 0 : 0)
));
setMaxSizes(panels.map(
(child) => (React.isValidElement(child) ? child.props.maxSize || 100 : 100)
));
}
}, [children]);
let panelIndex = 0;
const childrenWithInjectedProps = React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
if (child.type === ResizablePanel) {
const index = panelIndex;
panelIndex++;
return React.cloneElement(child, { panelIndex: index });
}
if (child.type === ResizableHandle) {
return React.cloneElement(child, { handleIndex: panelIndex - 1 });
}
}
return child;
});
return (
<ResizablePanelGroupContext.Provider value={{ direction, sizes, setSizes, minSizes, maxSizes }}>
<div
ref={ref}
className={cn(
"flex w-full h-full",
direction === "vertical" && "flex-col",
className
)}
{...props}
>
{childrenWithInjectedProps}
</div>
</ResizablePanelGroupContext.Provider>
);
});
ResizablePanelGroup.displayName = "ResizablePanelGroup";
export const ResizablePanel = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & {
defaultSize?: number;
minSize?: number;
maxSize?: number;
panelIndex?: number; // Injected prop
}
>(({ className, children, style, panelIndex, ...props }, ref) => {
const { direction, sizes } = useResizablePanelGroup();
const size = (panelIndex !== undefined && sizes[panelIndex]) ? sizes[panelIndex] : undefined;
if (size === undefined) {
return null;
}
return (
<div
ref={ref}
className={cn("overflow-hidden", className)}
style={{
...style,
flexGrow: size,
flexShrink: 1,
flexBasis: 0,
}}
{...props}
>
{children}
</div>
);
});
ResizablePanel.displayName = "ResizablePanel";
export const ResizableHandle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & {
withHandle?: boolean;
handleIndex?: number; // Injected prop
}
>(({ className, withHandle, handleIndex, ...props }, ref) => {
const { direction, sizes, setSizes, minSizes, maxSizes } = useResizablePanelGroup();
const [isDragging, setIsDragging] = React.useState(false);
const handleRef = (ref as React.RefObject<HTMLDivElement>) || React.createRef<HTMLDivElement>();
const onDrag = React.useCallback(
(e: MouseEvent | TouchEvent) => {
if (!isDragging || handleIndex === undefined) return;
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
const groupElement = handleRef.current?.parentElement;
if (!groupElement) return;
const { left, top, width, height } = groupElement.getBoundingClientRect();
const cursorPosition = direction === 'horizontal' ? clientX - left : clientY - top;
const groupSize = direction === 'horizontal' ? width : height;
const cursorPercentage = (cursorPosition / groupSize) * 100;
const prevPanelsSize = sizes.slice(0, handleIndex + 1).reduce((sum, s) => sum + s, 0);
const delta = cursorPercentage - prevPanelsSize;
let prevPanelSize = sizes[handleIndex];
let nextPanelSize = sizes[handleIndex + 1];
let newPrevSize = prevPanelSize + delta;
let newNextSize = nextPanelSize - delta;
const minPrev = minSizes[handleIndex];
const maxPrev = maxSizes[handleIndex];
const minNext = minSizes[handleIndex + 1];
const maxNext = maxSizes[handleIndex + 1];
if (newPrevSize < minPrev) {
newPrevSize = minPrev;
newNextSize = prevPanelSize + nextPanelSize - newPrevSize;
} else if (newNextSize < minNext) {
newNextSize = minNext;
newPrevSize = prevPanelSize + nextPanelSize - newNextSize;
} else if (newPrevSize > maxPrev) {
newPrevSize = maxPrev;
newNextSize = prevPanelSize + nextPanelSize - newPrevSize;
} else if (newNextSize > maxNext) {
newNextSize = maxNext;
newPrevSize = prevPanelSize + nextPanelSize - newNextSize;
}
const newSizes = [...sizes];
newSizes[handleIndex] = newPrevSize;
newSizes[handleIndex + 1] = newNextSize;
setSizes(newSizes);
},
[isDragging, direction, sizes, handleIndex, setSizes, minSizes, maxSizes, handleRef]
);
React.useEffect(() => {
const stopDragging = () => setIsDragging(false);
const ownerDocument = handleRef.current?.ownerDocument || document;
if (isDragging) {
ownerDocument.addEventListener("mousemove", onDrag);
ownerDocument.addEventListener("touchmove", onDrag);
ownerDocument.addEventListener("mouseup", stopDragging);
ownerDocument.addEventListener("touchend", stopDragging);
ownerDocument.body.style.cursor = direction === 'horizontal' ? 'col-resize' : 'row-resize';
ownerDocument.body.style.userSelect = 'none';
}
return () => {
ownerDocument.removeEventListener("mousemove", onDrag);
ownerDocument.removeEventListener("touchmove", onDrag);
ownerDocument.removeEventListener("mouseup", stopDragging);
ownerDocument.removeEventListener("touchend", stopDragging);
ownerDocument.body.style.cursor = '';
ownerDocument.body.style.userSelect = '';
};
}, [isDragging, onDrag, direction, handleRef]);
return (
<div
ref={handleRef}
onMouseDown={(e) => { e.preventDefault(); setIsDragging(true); }}
onTouchStart={(e) => { e.preventDefault(); setIsDragging(true); }}
className={cn(
"relative flex-shrink-0 items-center justify-center bg-transparent transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
direction === "horizontal"
? "w-2 mx-[-1px] cursor-col-resize"
: "h-2 my-[-1px] cursor-row-resize",
className
)}
{...props}
>
<div
className={cn(
"z-10 bg-border transition-colors",
direction === "horizontal" ? "h-full w-[1px]" : "w-full h-[1px]",
isDragging && "bg-primary"
)}
/>
{withHandle && (
<div className="z-20 absolute flex items-center justify-center rounded-full border bg-card p-1 shadow-sm">
<GripVertical
className={cn(
"h-2.5 w-2.5 text-muted-foreground",
direction === "vertical" && "rotate-90"
)}
/>
</div>
)}
</div>
);
});
ResizableHandle.displayName = "ResizableHandle";
```
---
## FILE: src/components/ui/scroll-area.tsx
```tsx
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }
```
---
## FILE: src/components/ui/select.tsx
```tsx
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "@/lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}
```
---
## FILE: src/components/ui/separator.tsx
```tsx
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }
```
---
## FILE: src/components/ui/sheet.tsx
```tsx
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Sheet = SheetPrimitive.Root
const SheetTrigger = SheetPrimitive.Trigger
const SheetClose = SheetPrimitive.Close
const SheetPortal = SheetPrimitive.Portal
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
}
)
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
))
SheetContent.displayName = SheetPrimitive.Content.displayName
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
SheetHeader.displayName = "SheetHeader"
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
SheetFooter.displayName = "SheetFooter"
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-foreground", className)}
{...props}
/>
))
SheetTitle.displayName = SheetPrimitive.Title.displayName
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
SheetDescription.displayName = SheetPrimitive.Description.displayName
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}
```
---
## FILE: src/components/ui/skeleton.tsx
```tsx
import { cn } from "@/lib/utils"
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
)
}
export { Skeleton }
```
---
## FILE: src/components/ui/slider.tsx
```tsx
"use client"
import * as React from "react"
import * as SliderPrimitive from "@radix-ui/react-slider"
import { cn } from "@/lib/utils"
const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => (
<SliderPrimitive.Root
ref={ref}
className={cn(
"relative flex w-full touch-none select-none items-center",
className
)}
{...props}
>
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
<SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root>
))
Slider.displayName = SliderPrimitive.Root.displayName
export { Slider }
```
---
## FILE: src/components/ui/stepper.tsx
```tsx
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
import { Check } from "lucide-react";
interface Step {
label: string;
}
interface StepperProps {
steps: Step[];
currentStep: number;
}
export function Stepper({ steps, currentStep }: StepperProps) {
return (
<div className="flex w-full items-center justify-center">
{steps.map((step, index) => {
const stepNumber = index + 1;
const isActive = stepNumber === currentStep;
const isCompleted = stepNumber < currentStep;
return (
<React.Fragment key={index}>
<div className="flex flex-col items-center">
<div
className={cn(
"flex h-8 w-8 items-center justify-center rounded-full border-2 text-sm font-semibold",
isActive
? "border-primary bg-primary text-primary-foreground"
: isCompleted
? "border-primary bg-primary text-primary-foreground"
: "border-border bg-card"
)}
>
{isCompleted ? <Check className="h-5 w-5" /> : stepNumber}
</div>
<p
className={cn(
"mt-2 w-24 text-center text-xs",
isActive ? "font-semibold text-primary" : "text-muted-foreground",
isCompleted ? "font-semibold" : ""
)}
>
{step.label}
</p>
</div>
{index < steps.length - 1 && (
<div className="mb-6 h-px w-full flex-1 bg-border" />
)}
</React.Fragment>
);
})}
</div>
);
}
```
---
## FILE: src/components/ui/switch.tsx
```tsx
"use client"
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cn } from "@/lib/utils"
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName
export { Switch }
```
---
## FILE: src/components/ui/table.tsx
```tsx
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}
```
---
## FILE: src/components/ui/tabs.tsx
```tsx
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }
```
---
## FILE: src/components/ui/textarea.tsx
```tsx
import * as React from 'react';
import {cn} from '@/lib/utils';
const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<'textarea'>>(
({className, ...props}, ref) => {
return (
<textarea
className={cn(
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className
)}
ref={ref}
{...props}
/>
);
}
);
Textarea.displayName = 'Textarea';
export {Textarea};
```
---
## FILE: src/components/ui/toast.tsx
```tsx
"use client"
import * as React from "react"
import * as ToastPrimitives from "@radix-ui/react-toast"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const ToastProvider = ToastPrimitives.Provider
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className
)}
{...props}
/>
))
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{
variants: {
variant: {
default: "border bg-background text-foreground",
destructive:
"destructive group border-destructive bg-destructive text-destructive-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
)
})
Toast.displayName = ToastPrimitives.Root.displayName
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className
)}
{...props}
/>
))
ToastAction.displayName = ToastPrimitives.Action.displayName
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className
)}
toast-close=""
{...props}
>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
))
ToastClose.displayName = ToastPrimitives.Close.displayName
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn("text-sm font-semibold", className)}
{...props}
/>
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn("text-sm opacity-90", className)}
{...props}
/>
))
ToastDescription.displayName = ToastPrimitives.Description.displayName
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastActionElement = React.ReactElement<typeof ToastAction>
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
}
```
---
## FILE: src/components/ui/toaster.tsx
```tsx
"use client"
import { useToast } from "@/hooks/use-toast"
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@/components/ui/toast"
export function Toaster() {
const { toasts } = useToast()
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
)
})}
<ToastViewport />
</ToastProvider>
)
}
```
---
## FILE: src/components/ui/tooltip.tsx
```tsx
"use client"
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { cn } from "@/lib/utils"
const TooltipProvider = TooltipPrimitive.Provider
const Tooltip = TooltipPrimitive.Root
const TooltipTrigger = TooltipPrimitive.Trigger
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md 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",
className
)}
{...props}
/>
))
TooltipContent.displayName = TooltipPrimitive.Content.displayName
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
```
---
## FILE: src/hooks/use-toast.ts
```ts
"use client"
// Inspired by react-hot-toast library
import * as React from "react"
import type {
ToastActionElement,
ToastProps,
} from "@/components/ui/toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
case "DISMISS_TOAST": {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
}
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit<ToasterToast, "id">
function toast({ ...props }: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
}
export { useToast, toast }
```
---
## FILE: src/lib/aethex-data.ts
```ts
export type FileNode = { name: string; type: "file"; language: string };
export type FolderNode = {
name: string;
type: "folder";
children: (FileNode | FolderNode)[];
};
export type File = {
id: string;
name: string;
language: string;
content: string;
};
export type { File as OpenFileType };
export const fileTree: FolderNode = {
name: "aethex-project",
type: "folder",
children: [
{
name: "roblox",
type: "folder",
children: [
{ name: "main.lua", type: "file", language: "lua" },
{ name: "player.lua", type: "file", language: "lua" },
],
},
{
name: "web",
type: "folder",
children: [
{ name: "index.js", type: "file", language: "javascript" },
{ name: "style.css", type: "file", language: "css" },
],
},
{
name: "mobile",
type: "folder",
children: [{ name: "App.tsx", type: "file", language: "typescript" }],
},
{ name: "README.md", type: "file", language: "markdown" },
],
};
export const openFiles: File[] = [
{
id: "aethex-project/roblox/main.lua",
name: "main.lua",
language: "lua",
content: `-- Roblox main script
local Players = game:GetService("Players")
local function onPlayerAdded(player)
print("Player " .. player.Name .. " has joined the game.")
-- Initialize player data
end
Players.PlayerAdded:Connect(onPlayerAdded)
`,
},
{
id: "aethex-project/web/index.js",
name: "index.js",
language: "javascript",
content: `// Web client entry point
document.addEventListener('DOMContentLoaded', () => {
const app = document.getElementById('app');
app.innerHTML = '<h1>Welcome to AeThex on the Web!</h1>';
console.log('Web client initialized.');
});
`,
},
{
id: "aethex-project/mobile/App.tsx",
name: "App.tsx",
language: "typescript",
content: `// React Native App Component
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
const App = () => {
return (
<View style={styles.container}>
<Text style={styles.text}>AeThex Mobile</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
fontSize: 24,
},
});
export default App;
`,
},
];
export const consoleLogs = [
{ timestamp: "14:02:10.112", platform: "Web", type: "log", message: "Web client initialized." },
{ timestamp: "14:02:10.453", platform: "Roblox", type: "log", message: "Player AeThexDev has joined the game." },
{ timestamp: "14:02:11.231", platform: "Mobile", type: "log", message: "React Native running in development mode." },
{ timestamp: "14:02:15.899", platform: "Roblox", type: "warn", message: "DeprecationWarning: Use 'Task.Wait' instead of 'wait'." },
{ timestamp: "14:02:18.050", platform: "Web", type: "error", message: "Uncaught TypeError: Cannot read properties of null (reading 'innerHTML')" },
{ timestamp: "14:02:20.333", platform: "Mobile", type: "log", message: "User tapped the screen." },
];
export const platformCode = {
lua: {
name: "player.lua",
code: `local PlayerState = {
position = Vector3.new(0, 5, 0),
health = 100,
inventory = {}
}
-- Other Lua code...
`,
},
javascript: {
name: "player.js",
code: `let playerState = {
position: { x: 0, y: 5, z: 0 },
health: 100,
inventory: []
}
// Other JS code...
`,
},
typescript: {
name: "player.ts",
code: `interface PlayerState {
position: { x: number; y: number; z: number };
health: number;
inventory: string[];
}
const playerState: PlayerState = {
position: { x: 0, y: 5, z: 0 },
health: 100,
inventory: []
};
// Other RN/TS code...
`,
},
};
export const sharedState = {
playerScore: 1250,
gameTime: "15:32",
levelName: "CyberCity",
isActive: true,
};
export const crossPlatformState = [
{ variable: 'playerScore', roblox: 1250, web: 1250, mobile: 1250, status: 'synced' as const },
{ variable: 'gameTime', roblox: '15:32', web: '15:33', mobile: '15:32', status: 'syncing' as const },
{ variable: 'levelName', roblox: 'CyberCity', web: 'CyberCity', mobile: 'CyberCity', status: 'synced' as const },
{ variable: 'isActive', roblox: true, web: true, mobile: true, status: 'synced' as const },
{ variable: 'inventory', roblox: 'table', web: 'any[]', mobile: 'string[]', status: 'conflict' as const },
];
export const aiConflictResolutionSuggestion = {
conflictDetected: true,
suggestedSolutions: [
"In `player.lua`, change `inventory = {}` to an array-like table to match JS/TS.",
"In `player.js`, add type checks or use a class to ensure `inventory` items are strings like in `player.ts`.",
"Unify the `position` object/Vector3 structure across all platforms. Suggest using a simple `{x, y, z}` object in the shared state and converting to platform-specific types like `Vector3` within each environment."
],
explanation: "A synchronization conflict was detected in the `inventory` field of the player state. The TypeScript definition expects an array of strings (`string[]`), while the Lua code initializes it as a generic table (`{}`), and the JavaScript code as a generic array (`[]`). This can lead to runtime errors when synchronizing state. It is recommended to enforce consistent data types across all platforms."
}
```
---
## FILE: src/lib/nexus.ts
```ts
export type Position = { x: number; y: number; z: number };
/**
* Generates platform-specific code snippets to sync player position.
* This is a mock implementation for the "Nexus Engine".
* @param position - The new position of the player.
* @returns An object with code snippets for each platform.
*/
export const syncPlayerPosition = (position: Position) => {
console.log("NEXUS ENGINE: Syncing player position", position);
return {
roblox: `-- Sync position for player
player.Character.HumanoidRootPart.CFrame = CFrame.new(${position.x}, ${position.y}, ${position.z})`,
web: `// Sync position for web
playerState.position = { x: ${position.x}, y: ${position.y}, z: ${position.z} };
updatePlayerOnMap(playerState.position);`,
mobile: `// Sync position for mobile
setPlayerState(prevState => ({
...prevState,
position: { x: ${position.x}, y: ${position.y}, z: ${position.z} },
}));`,
};
};
```
---
## FILE: src/lib/placeholder-images.json
```json
{
"placeholderImages": [
{
"id": "roblox-vp",
"description": "A futuristic cityscape rendered in a blocky, stylized aesthetic typical of Roblox games.",
"imageUrl": "https://images.unsplash.com/photo-1675326570919-946d728e9a25?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHwyfHxmdXR1cmlzdGljJTIwY2l0eXxlbnwwfHx8fDE3Njg1NTM4OTl8MA&ixlib=rb-4.1.0&q=80&w=1080",
"imageHint": "futuristic city"
},
{
"id": "web-vp",
"description": "A sleek, modern web interface with glowing neon elements and a dark theme, showing a game dashboard.",
"imageUrl": "https://images.unsplash.com/photo-1722834228772-01d16b9bf83b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHwzfHxuZW9uJTIwaW50ZXJmYWNlfGVufDB8fHx8MTc2ODYxNDYwOHww&ixlib=rb-4.1.0&q=80&w=1080",
"imageHint": "neon interface"
},
{
"id": "mobile-vp",
"description": "A mobile game screen with touch controls overlaid on a vibrant, abstract world.",
"imageUrl": "https://images.unsplash.com/photo-1565870100382-f0a510db3cd1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHw0fHxtb2JpbGUlMjBnYW1lfGVufDB8fHx8MTc2ODU2MjY4MHww&ixlib=rb-4.1.0&q=80&w=1080",
"imageHint": "mobile game"
},
{
"id": "login-illustration",
"description": "An abstract visualization of cross-platform code and interfaces.",
"imageUrl": "https://images.unsplash.com/photo-1681130315503-f0709a634ee3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHw4fHxjcm9zcy1wbGF0Zm9ybSUyMGRldmVsb3BtZW50fGVufDB8fHx8MTc2ODYxNjg3M3ww&ixlib=rb-4.1.0&q=80&w=1080",
"imageHint": "cross-platform development"
},
{
"id": "workspace-thumb-1",
"description": "Abstract neon lights in a dark environment.",
"imageUrl": "https://images.unsplash.com/photo-1642538302053-ad49ca70936d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHw1fHxhYnN0cmFjdCUyMG5lb258ZW58MHx8fHwxNzY4NTQ0NzIwfDA&ixlib=rb-4.1.0&q=80&w=1080",
"imageHint": "abstract neon"
},
{
"id": "workspace-thumb-2",
"description": "A vast and strange sci-fi landscape under a colorful sky.",
"imageUrl": "https://images.unsplash.com/photo-1641200658067-b5a56ebba866?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHw2fHxzY2ktZmklMjBsYW5kc2NhcGV8ZW58MHx8fHwxNzY4NTUyNTAzfDA&ixlib=rb-4.1.0&q=80&w=1080",
"imageHint": "sci-fi landscape"
},
{
"id": "workspace-thumb-3",
"description": "A cluster of glowing cubes in a digital space.",
"imageUrl": "https://images.unsplash.com/photo-1758775212970-30458a9df7ae?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHwzfHxnbG93aW5nJTIwY3ViZXN8ZW58MHx8fHwxNzY4NjE2ODczfDA&ixlib=rb-4.1.0&q=80&w=1080",
"imageHint": "glowing cubes"
},
{
"id": "workspace-thumb-4",
"description": "A neon-lit futuristic city at night.",
"imageUrl": "https://images.unsplash.com/photo-1536768139911-e290a59011e4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3NDE5ODJ8MHwxfHNlYXJjaHwxMHx8ZnV0dXJpc3RpYyUyMGNpdHl8ZW58MHx8fHwxNzY4NTUzODk5fDA&ixlib=rb-4.1.0&q=80&w=1080",
"imageHint": "futuristic city"
}
]
}
```
---
## FILE: src/lib/placeholder-images.ts
```ts
import data from './placeholder-images.json';
export type ImagePlaceholder = {
id: string;
description: string;
imageUrl: string;
imageHint: string;
};
export const PlaceHolderImages: ImagePlaceholder[] = data.placeholderImages;
```
---
## FILE: src/lib/templates.ts
```ts
import { Gamepad2, Users, Film, FileCode } from "lucide-react";
import type { FolderNode, FileNode } from "./aethex-data";
import type { ElementType } from "react";
export interface ProjectTemplate {
id: string;
name: string;
description: string;
icon: ElementType;
isPopular?: boolean;
fileTree: FolderNode;
mainFile: string;
}
const fileContents: Record<string, string> = {
"main.server.lua": `-- Roblox Server Script\nprint("Hello from the server!")`,
"player.client.lua": `-- Roblox Client Script\nprint("Hello from the client!")`,
"main.lua": `-- Roblox main script
local Players = game:GetService("Players")
local function onPlayerAdded(player)
print("Player " .. player.Name .. " has joined the game.")
-- Initialize player data
end
Players.PlayerAdded:Connect(onPlayerAdded)
`,
"player.lua": `-- Player script for Roblox`,
"index.js": `// Web client entry point
document.addEventListener('DOMContentLoaded', () => {
const app = document.getElementById('app');
app.innerHTML = '<h1>Welcome to your new AeThex Project!</h1>';
console.log('Web client initialized.');
});
`,
"style.css": `/* Add your web styles here */
body {
background-color: #1a1a1a;
color: #fafafa;
font-family: sans-serif;
display: grid;
place-content: center;
min-height: 100vh;
}`,
"App.tsx": `// React Native App Component
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
const App = () => {
return (
<View style={styles.container}>
<Text style={styles.text}>Welcome to your AeThex Project!</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#1a1a1a',
},
text: {
fontSize: 24,
color: '#fafafa',
},
});
export default App;
`,
"README.md": `# New Project
Welcome to your new AeThex Studio project!
This project was generated by AeThex Studio. Start by exploring the files in the explorer.
`,
"story.json": `{
"title": "My Awesome Story",
"author": "",
"chapters": [
{
"id": "chapter-1",
"title": "The Beginning",
"scenes": []
}
]
}
`,
"viewer.js": `// Story viewer for web
document.addEventListener('DOMContentLoaded', () => {
console.log('Story viewer initialized.');
});
`,
};
export function generateFileContent(fileName: string, language: string): string {
return fileContents[fileName] || `// ${fileName} - ${language} file`;
}
export const projectTemplates: ProjectTemplate[] = [
{
id: "roblox-starter",
name: "Roblox Game Starter",
description: "A basic setup for a new Roblox game with player scripts.",
icon: Gamepad2,
fileTree: {
name: "roblox-game",
type: "folder",
children: [
{
name: "roblox",
type: "folder",
children: [
{ name: "main.server.lua", type: "file", language: "lua" },
{ name: "player.client.lua", type: "file", language: "lua" },
],
},
{ name: "README.md", type: "file", language: "markdown" },
],
},
mainFile: "main.server.lua",
},
{
id: "cross-platform-multiplayer",
name: "Cross-Platform Multiplayer",
description: "Multiplayer game structure for Roblox, Web, and Mobile.",
icon: Users,
isPopular: true,
fileTree: {
name: "multiplayer-project",
type: "folder",
children: [
{
name: "roblox",
type: "folder",
children: [
{ name: "main.lua", type: "file", language: "lua" },
{ name: "player.lua", type: "file", language: "lua" },
],
},
{
name: "web",
type: "folder",
children: [
{ name: "index.js", type: "file", language: "javascript" },
{ name: "style.css", type: "file", language: "css" },
],
},
{
name: "mobile",
type: "folder",
children: [{ name: "App.tsx", type: "file", language: "typescript" }],
},
{ name: "README.md", type: "file", language: "markdown" },
],
},
mainFile: "README.md",
},
{
id: "transmedia-story",
name: "Transmedia Story Project",
description: "A project for interactive stories across multiple platforms.",
icon: Film,
fileTree: {
name: "story-project",
type: "folder",
children: [
{ name: "story.json", type: "file", language: "json" },
{
name: "web",
type: "folder",
children: [
{ name: "viewer.js", type: "file", language: "javascript" },
],
},
{ name: "README.md", type: "file", language: "markdown" },
],
},
mainFile: "story.json",
},
{
id: "blank",
name: "Blank Project",
description: "Start fresh with an empty project directory.",
icon: FileCode,
fileTree: {
name: "blank-project",
type: "folder",
children: [{ name: "README.md", type: "file", language: "markdown" }],
},
mainFile: "README.md",
},
];
```
---
## FILE: src/lib/utils.ts
```ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
```
---
## FILE: src/lib/workspaces.ts
```ts
import {
MobileIcon,
RobloxIcon,
WebIcon,
} from "@/components/aethex/icons";
import type { ElementType } from "react";
export type Workspace = {
id: string;
name: string;
lastModified: string;
platforms: ElementType[];
thumbnailUrlId: string;
thumbnailImageHint: string;
};
export const workspaces: Workspace[] = [
{
id: "proj-1",
name: "Cyber Runner",
lastModified: "2 days ago",
platforms: [RobloxIcon, WebIcon, MobileIcon],
thumbnailUrlId: "workspace-thumb-1",
thumbnailImageHint: "abstract neon",
},
{
id: "proj-2",
name: "Project Chimera",
lastModified: "5 days ago",
platforms: [RobloxIcon, WebIcon],
thumbnailUrlId: "workspace-thumb-2",
thumbnailImageHint: "sci-fi landscape",
},
{
id: "proj-3",
name: "Story-Verse",
lastModified: "1 week ago",
platforms: [WebIcon],
thumbnailUrlId: "workspace-thumb-3",
thumbnailImageHint: "glowing cubes",
},
];
```
---
## FILE: tailwind.config.ts
```ts
import type {Config} from 'tailwindcss';
export default {
darkMode: ['class'],
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
fontFamily: {
body: ['Inter', 'sans-serif'],
headline: ['Space Grotesk', 'sans-serif'],
code: ['Source Code Pro', 'monospace'],
},
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))',
},
sidebar: {
DEFAULT: 'hsl(var(--sidebar-background))',
foreground: 'hsl(var(--sidebar-foreground))',
primary: 'hsl(var(--sidebar-primary))',
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
accent: 'hsl(var(--sidebar-accent))',
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
border: 'hsl(var(--sidebar-border))',
ring: 'hsl(var(--sidebar-ring))',
},
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
keyframes: {
'accordion-down': {
from: {
height: '0',
},
to: {
height: 'var(--radix-accordion-content-height)',
},
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)',
},
to: {
height: '0',
},
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
},
},
},
plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')],
} satisfies Config;
```
---
## FILE: tsconfig.json
```json
{
"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"]
}
```