From c948fa883b4d52882ee41b59293f446ee0fdc943 Mon Sep 17 00:00:00 2001 From: Rodrigo Luglio Date: Thu, 10 Oct 2024 23:28:00 -0300 Subject: [PATCH] refactor some components --- src/app/(private)/generator/loading.tsx | 13 + src/app/(private)/generator/page.tsx | 26 +- src/app/(private)/layout.tsx | 1 - src/app/(private)/saved-themes/page.tsx | 4 +- src/app/discover/page.tsx | 8 +- src/app/layout.tsx | 17 +- src/components/Discover.tsx | 25 + src/components/EditorSmall.tsx | 213 +++++++ src/components/SavedThemes.tsx | 31 +- src/components/ThemeCard.tsx | 1 + src/components/ThemeCardPublic.tsx | 7 +- src/components/ThemeGenerator.tsx | 2 + src/components/ThemePreview.tsx | 716 +----------------------- src/components/ThemePreviewSmall.tsx | 118 ++++ src/contexts/ThemeContext.tsx | 7 + src/lib/generator/uiColors.ts | 24 +- src/lib/utils/codeSnippets.ts | 488 ++++++++++++++++ 17 files changed, 950 insertions(+), 751 deletions(-) create mode 100644 src/app/(private)/generator/loading.tsx create mode 100644 src/components/Discover.tsx create mode 100644 src/components/EditorSmall.tsx create mode 100644 src/components/ThemePreviewSmall.tsx create mode 100644 src/lib/utils/codeSnippets.ts diff --git a/src/app/(private)/generator/loading.tsx b/src/app/(private)/generator/loading.tsx new file mode 100644 index 0000000..0c1cf02 --- /dev/null +++ b/src/app/(private)/generator/loading.tsx @@ -0,0 +1,13 @@ +import { Loader2 } from 'lucide-react' + +// src/app/saved-themes/loading.tsx +export default function Loading() { + return ( +
+
+ +

Loading theme generator...

+
+
+ ) +} diff --git a/src/app/(private)/generator/page.tsx b/src/app/(private)/generator/page.tsx index 845f862..8d9b0dd 100644 --- a/src/app/(private)/generator/page.tsx +++ b/src/app/(private)/generator/page.tsx @@ -1,26 +1,12 @@ -'use client' - -import { useUser } from '@clerk/nextjs' -import { ThemeProvider } from '@/contexts/ThemeContext' import { ThemeGenerator } from '@/components/ThemeGenerator' -import Navigation from '@/components/Navigation' - -export default function GeneratorPage() { - const { isLoaded, isSignedIn, user } = useUser() - - if (!isLoaded) { - return
Loading...
- } +import { auth } from '@clerk/nextjs/server' - if (!isSignedIn) { - return
Please sign in to access the theme generator.
- } +export default async function GeneratorPage() { + const { userId } = auth() return ( - -
- -
-
+
+ +
) } diff --git a/src/app/(private)/layout.tsx b/src/app/(private)/layout.tsx index 95fbaa8..1f0bd1b 100644 --- a/src/app/(private)/layout.tsx +++ b/src/app/(private)/layout.tsx @@ -1,4 +1,3 @@ -import Navigation from '@/components/Navigation' import { auth } from '@clerk/nextjs/server' export default async function PrivateLayout({ diff --git a/src/app/(private)/saved-themes/page.tsx b/src/app/(private)/saved-themes/page.tsx index 11692fd..955e1df 100644 --- a/src/app/(private)/saved-themes/page.tsx +++ b/src/app/(private)/saved-themes/page.tsx @@ -8,9 +8,9 @@ export default async function SavedThemesPage() { const themes = userId ? await getThemesByUserId(userId) : [] return ( -
+

Your Saved Themes

-
+ ) } diff --git a/src/app/discover/page.tsx b/src/app/discover/page.tsx index 4d4c087..ece834d 100644 --- a/src/app/discover/page.tsx +++ b/src/app/discover/page.tsx @@ -1,4 +1,4 @@ -import ThemeCardPublic from '@/components/ThemeCardPublic' +import Discover from '@/components/Discover' import { getPublicThemes } from '@/lib/db/themes' @@ -9,11 +9,7 @@ export default async function DiscoverPage() {

Discover Themes

-
- {themes.map((theme) => ( - - ))} -
+
) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 4c2d1b4..4400f36 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,15 +4,11 @@ import './globals.css' import { Inter } from 'next/font/google' import { ThemeProvider } from 'next-themes' -import { - ClerkProvider, - SignInButton, - SignedIn, - SignedOut, - UserButton, -} from '@clerk/nextjs' +import { ClerkProvider } from '@clerk/nextjs' +import { auth } from '@clerk/nextjs/server' import { dark } from '@clerk/themes' import Navigation from '@/components/Navigation' +import { ThemeProvider as ThemeProviderContext } from '@/contexts/ThemeContext' const inter = Inter({ subsets: ['latin'] }) @@ -26,6 +22,7 @@ export default function RootLayout({ }: Readonly<{ children: React.ReactNode }>) { + const { userId } = auth() return ( - - {children} + + + {children} + diff --git a/src/components/Discover.tsx b/src/components/Discover.tsx new file mode 100644 index 0000000..bd28b9c --- /dev/null +++ b/src/components/Discover.tsx @@ -0,0 +1,25 @@ +'use client' + +import { SavedTheme } from '@/lib/types/colors' +import ThemeCardPublic from './ThemeCardPublic' +import ThemePreviewSmall from './ThemePreviewSmall' +import { useState } from 'react' + +export default function Discover({ themes }: { themes: SavedTheme[] }) { + const [selectedTheme, setSelectedTheme] = useState(themes[0]) + return ( +
+ + +
+ {themes.map((theme) => ( + + ))} +
+
+ ) +} diff --git a/src/components/EditorSmall.tsx b/src/components/EditorSmall.tsx new file mode 100644 index 0000000..e99cabb --- /dev/null +++ b/src/components/EditorSmall.tsx @@ -0,0 +1,213 @@ +import { useCallback, useRef, useState, useEffect } from 'react' +import dynamic from 'next/dynamic' + +import type { editor } from 'monaco-editor' +import { loadWASM } from 'onigasm' +import { IGrammarDefinition, Registry, RegistryOptions } from 'monaco-textmate' +import { wireTmGrammars } from 'monaco-editor-textmate' +import { generateSemanticThemeJSON } from '@/lib/generator/export' +import { convertTheme } from '@/lib/utils/convertTheme' +import { SavedTheme } from '@/lib/types/colors' +import { useTheme } from '@/contexts/ThemeContext' +import { + type CodeSnippetKey, + codeSnippets as snippets, +} from '@/lib/utils/codeSnippets' + +const MonacoEditor = dynamic(() => import('@monaco-editor/react'), { + ssr: false, + loading: () =>

Loading editor...

, +}) + +export default function EditorSmall({ + theme, + selectedFile = 'typescript.tsx', +}: { + theme: SavedTheme + selectedFile: CodeSnippetKey +}) { + const editorRef = useRef<{ + editor: editor.IStandaloneCodeEditor + monaco: typeof import('monaco-editor') + } | null>(null) + const { isOnigasmInitialized, setIsOnigasmInitialized } = useTheme() + const [isEditorReady, setIsEditorReady] = useState(false) + const getTheme = useCallback((): editor.IStandaloneThemeData => { + const { themeObject } = generateSemanticThemeJSON( + 'Generated Color Theme', + theme.uiColors, + theme.syntaxColors, + theme.ansiColors + ) + + return convertTheme(themeObject) + }, [theme]) + + const updateTheme = useCallback(() => { + if (editorRef.current) { + const { monaco, editor } = editorRef.current + const theme = getTheme() + const model = editor.getModel() + if (model) { + model.updateOptions({ + bracketColorizationOptions: { + enabled: false, + independentColorPoolPerBracketType: false, + }, + }) + } + monaco.editor.defineTheme('custom-theme', theme) + monaco.editor.setTheme('custom-theme') + } + }, [getTheme]) + + const setupTextmate = useCallback(async () => { + if (!editorRef.current) return + + if (!isOnigasmInitialized) { + await loadWASM('onigasm.wasm') + setIsOnigasmInitialized(true) + } + + const registry = new Registry({ + getGrammarDefinition: async ( + scopeName: string + ): Promise => { + const grammarMap: { [key: string]: string } = { + 'source.tsx': 'TypeScriptReact.tmLanguage.json', + 'source.js.jsx': 'JavaScriptReact.tmLanguage.json', + 'source.ts': 'TypeScript.tmLanguage.json', + 'source.js': 'JavaScript.tmLanguage.json', + 'source.css': 'css.tmLanguage.json', + 'text.html.markdown': 'markdown.tmLanguage.json', + 'text.html.basic': 'html.tmLanguage.json', + 'source.python': 'MagicPython.tmLanguage.json', + 'source.yaml': 'yaml.tmLanguage.json', + } + + if (scopeName in grammarMap) { + try { + const response = await fetch(grammarMap[scopeName]) + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + const content = await response.text() + + return { + format: 'json' as const, + content, + } + } catch (error) { + console.error(`Failed to load grammar for ${scopeName}:`, error) + } + } + + return { + format: 'json', + content: JSON.stringify({ + name: 'Default', + scopeName: scopeName, + patterns: [], + }), + } + }, + } as RegistryOptions) + + try { + await wireTmGrammars( + editorRef.current.monaco, + registry, + new Map([ + ['typescript', 'source.tsx'], + ['javascript', 'source.jsx'], + ['typescript', 'source.ts'], + ['javascript', 'source.js'], + ['css', 'source.css'], + ['markdown', 'text.html.markdown'], + ['html', 'text.html.basic'], + ['python', 'source.python'], + ['yaml', 'source.yaml'], + ]) + ) + } catch (error) { + console.error('Error setting up TextMate:', error) + } + }, [isOnigasmInitialized, setIsOnigasmInitialized]) + + const handleEditorDidMount = useCallback( + ( + editor: editor.IStandaloneCodeEditor, + monaco: typeof import('monaco-editor') + ) => { + editorRef.current = { editor, monaco } + const model = editor.getModel() + if (model) { + model.updateOptions({ + bracketColorizationOptions: { + enabled: false, + independentColorPoolPerBracketType: false, + }, + }) + } + setIsEditorReady(true) + setupTextmate() + updateTheme() + }, + [setupTextmate, updateTheme] + ) + + useEffect(() => { + if (isEditorReady && editorRef.current) { + updateTheme() + } + }, [isEditorReady, updateTheme]) + + const getLanguage = (filename: string) => { + const extension = filename.split('.').pop() + switch (extension) { + case 'js': + case 'jsx': + return 'javascript' + case 'ts': + case 'tsx': + return 'typescript' + case 'py': + return 'python' + case 'html': + return 'html' + case 'css': + return 'css' + case 'md': + return 'markdown' + case 'yaml': + return 'yaml' + default: + return 'plaintext' + } + } + + return ( +
+
+ +
+
+ ) +} diff --git a/src/components/SavedThemes.tsx b/src/components/SavedThemes.tsx index 19498d4..3fcd125 100644 --- a/src/components/SavedThemes.tsx +++ b/src/components/SavedThemes.tsx @@ -3,6 +3,7 @@ import { useState } from 'react' import ThemeCard from '@/components/ThemeCard' import { SavedTheme } from '@/lib/types/colors' +import ThemePreviewSmall from './ThemePreviewSmall' export function SavedThemesContent({ initialThemes, @@ -10,6 +11,7 @@ export function SavedThemesContent({ initialThemes: SavedTheme[] }) { const [themes, setThemes] = useState(initialThemes) + const [theme, setTheme] = useState(initialThemes[0]) const handleEdit = (theme: SavedTheme) => { // Implement theme editing logic @@ -20,7 +22,7 @@ export function SavedThemesContent({ } const handlePreview = (theme: SavedTheme) => { - // Implement theme preview logic + setTheme(theme) } const handleShare = (theme: SavedTheme) => { @@ -28,17 +30,20 @@ export function SavedThemesContent({ } return ( -
- {themes.map((theme) => ( - - ))} -
+
+ +
+ {themes.map((theme) => ( + + ))} +
+
) } diff --git a/src/components/ThemeCard.tsx b/src/components/ThemeCard.tsx index fb404ed..573dfec 100644 --- a/src/components/ThemeCard.tsx +++ b/src/components/ThemeCard.tsx @@ -26,6 +26,7 @@ const ThemeCard: React.FC = ({ backgroundColor: theme.uiColors.BG1, }} className="rounded-lg shadow-md overflow-hidden" + onClick={() => onPreview(theme)} >
{/*
> } -const ThemeCardPublic: React.FC = ({ theme }) => { +const ThemeCardPublic: React.FC = ({ + theme, + onClick, +}) => { const handleDownload = (theme: SavedTheme) => { console.log('Downloading theme:', theme) } @@ -27,6 +31,7 @@ const ThemeCardPublic: React.FC = ({ theme }) => { backgroundColor: theme.uiColors.BG1, }} className="rounded-lg shadow-md overflow-hidden" + onClick={() => onClick(theme)} >
{/*
import('@monaco-editor/react'), { - ssr: false, - loading: () =>

Loading editor...

, -}) - -const codeSnippets = { - 'typescript.tsx': - "import React, { useState, useEffect } from 'react';\n" + - '\n' + - '// Interface and Type\n' + - 'interface User {\n' + - ' id: number;\n' + - ' name: string;\n' + - '}\n' + - '\n' + - 'type UserProps = {\n' + - ' user: User;\n' + - '};\n' + - '\n' + - '// Decorator\n' + - 'function logged(target: any, key: string, descriptor: PropertyDescriptor) {\n' + - ' const original = descriptor.value;\n' + - ' descriptor.value = function (...args: any[]) {\n' + - ' console.log(`Calling ${key} with`, args);\n' + - ' return original.apply(this, args);\n' + - ' };\n' + - ' return descriptor;\n' + - '}\n' + - '\n' + - '// Class with generics\n' + - 'class DataFetcher {\n' + - ' private url: string;\n' + - '\n' + - ' constructor(url: string) {\n' + - ' this.url = url;\n' + - ' }\n' + - '\n' + - ' @logged\n' + - ' async fetchData(): Promise {\n' + - ' const response = await fetch(this.url);\n' + - ' return response.json();\n' + - ' }\n' + - '}\n' + - '\n' + - '// React component\n' + - 'const UserProfile: React.FC = ({ user }) => {\n' + - ' const [loading, setLoading] = useState(true);\n' + - '\n' + - ' useEffect(() => {\n' + - ' // Async function in useEffect\n' + - ' const loadUser = async () => {\n' + - ' try {\n' + - ' const fetcher = new DataFetcher(`/api/users/${user.id}`);\n' + - ' await fetcher.fetchData();\n' + - ' setLoading(false);\n' + - ' } catch (error) {\n' + - ' console.error("Failed to load user:", error);\n' + - ' }\n' + - ' };\n' + - '\n' + - ' loadUser();\n' + - ' }, [user.id]);\n' + - '\n' + - ' if (loading) return
Loading...
;\n' + - '\n' + - ' return (\n' + - '
\n' + - "

{user.name}'s Profile

\n" + - '

User ID: {user.id}

\n' + - '
\n' + - ' );\n' + - '};\n' + - '\n' + - 'export default UserProfile;\n' + - 'import type {\n' + - ' UIColors,\n' + - ' SyntaxColors,\n' + - ' AnsiColors,\n' + - ' VSCodeTheme,\n' + - '} from "@/lib/types/colors"\n' + - '\n' + - 'import Color from "color"\n' + - '\n' + - 'export function generateSemanticThemeJSON(\n' + - ' name: string = "Generated Color Theme",\n' + - ' colors: UIColors,\n' + - ' syntaxColors: SyntaxColors,\n' + - ' ansiColors: AnsiColors\n' + - '): { themeJSON: string; themeObject: VSCodeTheme } {\n' + - ' const theme = {\n' + - ' name: name,\n' + - ' type: Color(colors.BG1).isDark()\n' + - ' ? ("dark" as "dark" | "light")\n' + - ' : ("light" as "dark" | "light"),\n' + - ' semanticClass: "theme.rlabs",\n' + - ' semanticHighlighting: true,\n' + - ' colors: {\n' + - ' // # Integrated Terminal Colors\n' + - ' "terminal.background": colors.BG1,\n' + - ' "terminal.foreground": colors.FG1,\n' + - ' "terminal.border": colors.BORDER,\n' + - ' "terminal.ansiBrightBlack": ansiColors.BrightBlack,\n' + - ' "terminal.ansiBrightRed": ansiColors.BrightRed,\n' + - ' "terminal.ansiBrightGreen": ansiColors.BrightGreen,\n' + - ' "terminal.ansiBrightYellow": ansiColors.BrightYellow,\n' + - ' "terminal.ansiBrightBlue": ansiColors.BrightBlue,\n' + - ' "terminal.ansiBrightMagenta": ansiColors.BrightMagenta,\n' + - ' "terminal.ansiBrightCyan": ansiColors.BrightCyan,\n' + - ' "terminal.ansiBrightWhite": ansiColors.BrightWhite,\n' + - ' "terminal.ansiBlack": ansiColors.Black,\n' + - ' "terminal.ansiRed": ansiColors.Red,\n' + - ' "terminal.ansiGreen": ansiColors.Green,\n' + - ' "terminal.ansiYellow": ansiColors.Yellow,\n' + - ' "terminal.ansiBlue": ansiColors.Blue,\n' + - ' "terminal.ansiMagenta": ansiColors.Magenta,\n' + - ' "terminal.ansiCyan": ansiColors.Cyan,\n' + - ' "terminal.ansiWhite": ansiColors.White,\n' + - ' "terminal.selectionBackground": colors.selection,\n' + - ' }\n' + - ' };\n' + - '\n' + - ' return {\n' + - ' themeJSON: JSON.stringify(theme),\n' + - ' themeObject: theme\n' + - ' };\n' + - '}\n', - - 'javascript.js': - '// Class definition\n' + - 'class Animal {\n' + - ' constructor(name) {\n' + - ' this.name = name;\n' + - ' }\n' + - '\n' + - ' speak() {\n' + - ' console.log(`${this.name} makes a sound.`);\n' + - ' }\n' + - '}\n' + - '\n' + - '// Inheritance\n' + - 'class Dog extends Animal {\n' + - ' constructor(name, breed) {\n' + - ' super(name);\n' + - ' this.breed = breed;\n' + - ' }\n' + - '\n' + - ' speak() {\n' + - ' console.log(`${this.name} barks.`);\n' + - ' }\n' + - '}\n' + - '\n' + - '// Arrow function with default parameter\n' + - 'const createPet = (name, species = "Unknown") => ({\n' + - ' name,\n' + - ' species,\n' + - ' getDescription: () => `${name} is a ${species}.`\n' + - '});\n' + - '\n' + - '// Async function with try-catch\n' + - 'async function fetchData(url) {\n' + - ' try {\n' + - ' const response = await fetch(url);\n' + - ' const data = await response.json();\n' + - ' return data;\n' + - ' } catch (error) {\n' + - ' console.error("Error fetching data:", error);\n' + - ' }\n' + - '}\n' + - '\n' + - '// Regular expression\n' + - 'const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n' + - '\n' + - '// Template literal\n' + - 'const greeting = `Hello, ${name}!`;\n' + - '\n' + - '// Destructuring and spread operator\n' + - 'const { x, y, ...rest } = { x: 1, y: 2, a: 3, b: 4 };\n' + - 'const newArray = [...oldArray, newItem];\n' + - '\n' + - '// Map and filter\n' + - 'const numbers = [1, 2, 3, 4, 5];\n' + - 'const doubled = numbers.map(n => n * 2);\n' + - 'const evens = numbers.filter(n => n % 2 === 0);\n' + - '\n' + - '// Object methods\n' + - 'const math = {\n' + - ' add: (a, b) => a + b,\n' + - ' subtract: (a, b) => a - b,\n' + - ' multiply: (a, b) => a * b,\n' + - ' divide: (a, b) => a / b\n' + - '};\n' + - '\n' + - 'export { Animal, Dog, createPet, fetchData, emailRegex, math };\n', - - 'python.py': - '# Python example\n' + - 'import asyncio\n' + - 'import re\n' + - 'from typing import List, Dict, Optional\n' + - 'from dataclasses import dataclass\n' + - '\n' + - '# Decorator\n' + - 'def log_calls(func):\n' + - ' async def wrapper(*args, **kwargs):\n' + - ' print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")\n' + - ' result = await func(*args, **kwargs)\n' + - ' print(f"{func.__name__} returned {result}")\n' + - ' return result\n' + - ' return wrapper\n' + - '\n' + - '# Class with type hints\n' + - '@dataclass\n' + - 'class User:\n' + - ' id: int\n' + - ' name: str\n' + - ' email: str\n' + - '\n' + - 'class UserManager:\n' + - ' def __init__(self):\n' + - ' self.users: List[User] = []\n' + - '\n' + - ' async def add_user(self, user: User) -> None:\n' + - ' self.users.append(user)\n' + - '\n' + - ' @log_calls\n' + - ' async def get_user(self, user_id: int) -> Optional[User]:\n' + - ' return next((user for user in self.users if user.id == user_id), None)\n' + - '\n' + - ' async def get_all_users(self) -> List[User]:\n' + - ' return self.users\n' + - '\n' + - '# Async function with error handling\n' + - 'async def fetch_user_data(url: str) -> Dict:\n' + - ' try:\n' + - ' # Simulating an API call\n' + - ' await asyncio.sleep(1)\n' + - ' return {"id": 1, "name": "John Doe", "email": "john@example.com"}\n' + - ' except Exception as e:\n' + - ' print(f"Error fetching user data: {e}")\n' + - ' return {}\n' + - '\n' + - '# Regular expression\n' + - "email_pattern = re.compile(r'^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$')\n" + - '\n' + - '# Main function\n' + - 'async def main():\n' + - ' user_manager = UserManager()\n' + - '\n' + - ' # List comprehension\n' + - ' user_ids = [i for i in range(1, 6)]\n' + - '\n' + - ' for user_id in user_ids:\n' + - ' user_data = await fetch_user_data(f"https://api.example.com/users/{user_id}")\n' + - ' if user_data and email_pattern.match(user_data["email"]):\n' + - ' user = User(**user_data)\n' + - ' await user_manager.add_user(user)\n' + - '\n' + - ' # Dictionary comprehension\n' + - ' user_dict = {user.id: user.name for user in await user_manager.get_all_users()}\n' + - '\n' + - ' print("Users:", user_dict)\n' + - '\n' + - ' # Async context manager\n' + - ' async with asyncio.timeout(5):\n' + - ' user = await user_manager.get_user(1)\n' + - ' if user:\n' + - ' print(f"Found user: {user.name}")\n' + - '\n' + - 'if __name__ == "__main__":\n' + - ' asyncio.run(main())\n', - - 'markdown.md': - '# Theme Preview Project\n' + - '\n' + - '## Table of Contents\n' + - '- [Introduction](#introduction)\n' + - '- [Features](#features)\n' + - '- [Installation](#installation)\n' + - '- [Usage](#usage)\n' + - '- [Contributing](#contributing)\n' + - '- [License](#license)\n' + - '\n' + - '## Introduction\n' + - '\n' + - 'This is a **Theme Preview** project that allows users to customize and preview various color schemes for their development environment.\n' + - '\n' + - '## Features\n' + - '\n' + - '- Supports multiple programming languages\n' + - '- Real-time preview\n' + - '- Customizable color palette\n' + - '- Export themes to various formats\n' + - '\n' + - '## Installation\n' + - '\n' + - 'To install the Theme Preview project, follow these steps:\n' + - '\n' + - '1. Clone the repository:\n' + - ' ```\n' + - ' git clone https://github.com/username/theme-preview.git\n' + - ' ```\n' + - '2. Navigate to the project directory:\n' + - ' ```\n' + - ' cd theme-preview\n' + - ' ```\n' + - '3. Install dependencies:\n' + - ' ```\n' + - ' npm install\n' + - ' ```\n' + - '\n' + - '## Usage\n' + - '\n' + - 'To start the Theme Preview application, run:\n' + - '\n' + - '```\n' + - 'npm start\n' + - '```\n' + - '\n' + - 'Then open your browser and navigate to `http://localhost:3000`.\n' + - '\n' + - '## Contributing\n' + - '\n' + - 'We welcome contributions! Please follow these steps to contribute:\n' + - '\n' + - '1. Fork the repository\n' + - '2. Create a new branch: `git checkout -b feature/your-feature-name`\n' + - "3. Make your changes and commit them: `git commit -m 'Add some feature'`\n" + - '4. Push to the branch: `git push origin feature/your-feature-name`\n' + - '5. Submit a pull request\n' + - '\n' + - '## License\n' + - '\n' + - 'This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n', - - 'html.html': - '\n' + - '\n' + - '\n' + - ' \n' + - ' \n' + - ' Theme Preview\n' + - ' \n' + - ' \n' + - '\n' + - '\n' + - '
\n' + - '

Welcome to Theme Preview

\n' + - ' \n' + - '
\n' + - '\n' + - '
\n' + - '
\n' + - '

Home

\n' + - '

This is the home section of our theme preview.

\n' + - '
\n' + - '\n' + - '
\n' + - '

About

\n' + - '

Learn more about our theme preview:

\n' + - '
    \n' + - '
  • Customizable colors
  • \n' + - '
  • Multiple language support
  • \n' + - '
  • Easy to use interface
  • \n' + - '
\n' + - '
\n' + - '\n' + - '
\n' + - '

Contact

\n' + - '
\n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - '
\n' + - '
\n' + - '
\n' + - '\n' + - '
\n' + - '

© 2023 Theme Preview. All rights reserved.

\n' + - '
\n' + - '\n' + - '\n', - - 'css.css': - '/* Variables */\n' + - ':root {\n' + - ' --primary-color: #3498db;\n' + - ' --secondary-color: #2ecc71;\n' + - ' --text-color: #333;\n' + - ' --background-color: #f4f4f4;\n' + - '}\n' + - '\n' + - '/* Global styles */\n' + - 'body {\n' + - ' font-family: Arial, sans-serif;\n' + - ' line-height: 1.6;\n' + - ' color: var(--text-color);\n' + - ' background-color: var(--background-color);\n' + - '}\n' + - '\n' + - '/* Header styles */\n' + - 'header {\n' + - ' background-color: var(--primary-color);\n' + - ' color: white;\n' + - ' padding: 1rem;\n' + - '}\n' + - '\n' + - '/* Navigation styles */\n' + - 'nav ul {\n' + - ' list-style-type: none;\n' + - ' display: flex;\n' + - '}\n' + - '\n' + - 'nav ul li {\n' + - ' margin-right: 1rem;\n' + - '}\n' + - '\n' + - 'nav ul li a {\n' + - ' color: white;\n' + - ' text-decoration: none;\n' + - '}\n' + - '\n' + - '/* Main content styles */\n' + - 'main {\n' + - ' padding: 2rem;\n' + - '}\n' + - '\n' + - 'section {\n' + - ' margin-bottom: 2rem;\n' + - '}\n' + - '\n' + - '/* Form styles */\n' + - 'form {\n' + - ' display: grid;\n' + - ' gap: 1rem;\n' + - '}\n' + - '\n' + - 'input, textarea {\n' + - ' width: 100%;\n' + - ' padding: 0.5rem;\n' + - '}\n' + - '\n' + - 'button {\n' + - ' background-color: var(--secondary-color);\n' + - ' color: white;\n' + - ' border: none;\n' + - ' padding: 0.5rem 1rem;\n' + - ' cursor: pointer;\n' + - '}\n' + - '\n' + - '/* Footer styles */\n' + - 'footer {\n' + - ' background-color: var(--primary-color);\n' + - ' color: white;\n' + - ' text-align: center;\n' + - ' padding: 1rem;\n' + - '}\n' + - '\n' + - '/* Media query for responsive design */\n' + - '@media (max-width: 768px) {\n' + - ' nav ul {\n' + - ' flex-direction: column;\n' + - ' }\n' + - '\n' + - ' nav ul li {\n' + - ' margin-bottom: 0.5rem;\n' + - ' }\n' + - '}\n' + - '\n' + - '/* Animation */\n' + - '@keyframes fadeIn {\n' + - ' from { opacity: 0; }\n' + - ' to { opacity: 1; }\n' + - '}\n' + - '\n' + - '.fade-in {\n' + - ' animation: fadeIn 1s ease-in-out;\n' + - '}\n', -} - -type CodeSnippetKey = keyof typeof codeSnippets +import EditorSmall from './EditorSmall' +import { codeSnippets, type CodeSnippetKey } from '@/lib/utils/codeSnippets' const ThemePreview: React.FC = () => { - const { colors, syntaxColors, ansiColors } = useTheme() - const [isEditorReady, setIsEditorReady] = useState(false) - const [isOnigasmInitialized, setIsOnigasmInitialized] = useState(false) - const editorRef = useRef<{ - editor: editor.IStandaloneCodeEditor - monaco: typeof import('monaco-editor') - } | null>(null) + const { + colors, + syntaxColors, + ansiColors, + isDark, + baseHue, + uiSaturation, + syntaxSaturation, + scheme, + } = useTheme() const [selectedFile, setSelectedFile] = useState('typescript.tsx') - const getTheme = useCallback((): editor.IStandaloneThemeData => { - const { themeJSON, themeObject } = generateSemanticThemeJSON( - 'Generated Color Theme', - colors, - syntaxColors, - ansiColors - ) - - return convertTheme(themeObject) - }, [colors, syntaxColors, ansiColors]) - - const updateTheme = useCallback(() => { - if (editorRef.current) { - const { monaco } = editorRef.current - const theme = getTheme() - editorRef.current.editor.updateOptions({ - bracketPairColorization: { - enabled: false, - independentColorPoolPerBracketType: false, - }, - }) - monaco.editor.defineTheme('custom-theme', theme) - monaco.editor.setTheme('custom-theme') - } - }, [getTheme]) - - const setupTextmate = useCallback(async () => { - if (!editorRef.current) return - - if (!isOnigasmInitialized) { - await loadWASM('onigasm.wasm') - setIsOnigasmInitialized(true) - } - - const registry = new Registry({ - getGrammarDefinition: async ( - scopeName: string - ): Promise => { - const grammarMap: { [key: string]: string } = { - 'source.tsx': 'TypeScriptReact.tmLanguage.json', - 'source.js.jsx': 'JavaScriptReact.tmLanguage.json', - 'source.ts': 'TypeScript.tmLanguage.json', - 'source.js': 'JavaScript.tmLanguage.json', - 'source.css': 'css.tmLanguage.json', - 'text.html.markdown': 'markdown.tmLanguage.json', - 'text.html.basic': 'html.tmLanguage.json', - 'source.python': 'MagicPython.tmLanguage.json', - 'source.yaml': 'yaml.tmLanguage.json', - } - - if (scopeName in grammarMap) { - try { - const response = await fetch(grammarMap[scopeName]) - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - const content = await response.text() - - return { - format: 'json' as const, - content, - } - } catch (error) { - console.error(`Failed to load grammar for ${scopeName}:`, error) - } - } - - // console.warn(`Grammar for scope ${scopeName} not found, using default`) - return { - format: 'json', - content: JSON.stringify({ - name: 'Default', - scopeName: scopeName, - patterns: [], - }), - } - }, - } as RegistryOptions) - - try { - await wireTmGrammars( - editorRef.current.monaco, - registry, - new Map([ - ['typescript', 'source.tsx'], - ['javascript', 'source.jsx'], - ['typescript', 'source.ts'], - ['javascript', 'source.js'], - ['css', 'source.css'], - ['markdown', 'text.html.markdown'], - ['html', 'text.html.basic'], - ['python', 'source.python'], - ['yaml', 'source.yaml'], - ]) - ) - } catch (error) { - console.error('Error setting up TextMate:', error) - } - }, [isOnigasmInitialized]) - - const handleEditorDidMount = useCallback( - ( - editor: editor.IStandaloneCodeEditor, - monaco: typeof import('monaco-editor') - ) => { - editorRef.current = { editor, monaco } - const model = editor.getModel() - if (model) { - model.updateOptions({ - bracketColorizationOptions: { - enabled: false, - independentColorPoolPerBracketType: false, - }, - }) - } - setIsEditorReady(true) - setupTextmate() - updateTheme() - }, - [setupTextmate, updateTheme] - ) - - useEffect(() => { - if (isEditorReady && editorRef.current) { - updateTheme() - } - }, [isEditorReady, updateTheme]) - - const getLanguage = (filename: string) => { - const extension = filename.split('.').pop() - switch (extension) { - case 'js': - case 'jsx': - return 'javascript' - case 'ts': - case 'tsx': - return 'typescript' - case 'py': - return 'python' - case 'html': - return 'html' - case 'css': - return 'css' - case 'md': - return 'markdown' - case 'yaml': - return 'yaml' - default: - return 'plaintext' - } - } - return (
{ {/* Monaco Editor */}
-
diff --git a/src/components/ThemePreviewSmall.tsx b/src/components/ThemePreviewSmall.tsx new file mode 100644 index 0000000..6b4da9f --- /dev/null +++ b/src/components/ThemePreviewSmall.tsx @@ -0,0 +1,118 @@ +import { SavedTheme } from '@/lib/types/colors' +import EditorSmall from './EditorSmall' + +export default function ThemePreviewSmall({ theme }: { theme: SavedTheme }) { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+

+ {theme.name} +

+ +

+ {theme.updatedAt.toDateString()} +

+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+ ) +} diff --git a/src/contexts/ThemeContext.tsx b/src/contexts/ThemeContext.tsx index 5e426f0..60a121d 100644 --- a/src/contexts/ThemeContext.tsx +++ b/src/contexts/ThemeContext.tsx @@ -1,3 +1,5 @@ +'use client' + import React, { createContext, useContext, @@ -78,6 +80,8 @@ interface ThemeContextType { currentThemeId: number | null setCurrentThemeId: (id: number | null) => void updateCurrentTheme: (name: string) => Promise + isOnigasmInitialized: boolean + setIsOnigasmInitialized: (value: boolean) => void } const ThemeContext = createContext(undefined) @@ -104,6 +108,7 @@ export const ThemeProvider: React.FC<{ const generateColorsTimeoutRef = useRef(null) const [savedThemes, setSavedThemes] = useState([]) const [currentThemeId, setCurrentThemeId] = useState(null) + const [isOnigasmInitialized, setIsOnigasmInitialized] = useState(false) const loadSavedThemes = useCallback(async () => { if (userId) { @@ -431,6 +436,8 @@ export const ThemeProvider: React.FC<{ currentThemeId, setCurrentThemeId, updateCurrentTheme, + isOnigasmInitialized, + setIsOnigasmInitialized, } return {children} diff --git a/src/lib/generator/uiColors.ts b/src/lib/generator/uiColors.ts index 623faeb..21d290e 100644 --- a/src/lib/generator/uiColors.ts +++ b/src/lib/generator/uiColors.ts @@ -24,8 +24,8 @@ export function generateThemeColors( const schemeHues = generateSchemeColors(baseHue, scheme) - const bgBase = isDark ? randomInteger(0, 25) : randomInteger(85, 100) - const fgBase = isDark ? randomInteger(75, 100) : randomInteger(0, 10) + const bgBase = isDark ? randomInteger(0, 5) : randomInteger(90, 100) + const fgBase = isDark ? randomInteger(80, 100) : randomInteger(0, 15) const generateColor = ( hue: number, @@ -54,22 +54,22 @@ export function generateThemeColors( initialColors.BG1 || generateColor( schemeHues[randomInteger(0, schemeHues.length - 1)], - uiSaturation * randomNumber(0.45, 0.8), - isDark ? bgBase + randomInteger(0, 5) : bgBase - randomInteger(0, 2) + uiSaturation * randomNumber(0.45, 0.6), + isDark ? bgBase + randomInteger(-5, 5) : bgBase + randomInteger(-10, 10) ), BG2: initialColors.BG2 || generateColor( schemeHues[randomInteger(0, schemeHues.length - 1)], - uiSaturation * randomNumber(0.45, 0.8), - isDark ? bgBase + randomInteger(0, 9) : bgBase - randomInteger(0, 5) + uiSaturation * randomNumber(0.45, 0.6), + isDark ? bgBase + randomInteger(-3, 7) : bgBase + randomInteger(-15, 7) ), BG3: initialColors.BG3 || generateColor( schemeHues[randomInteger(0, schemeHues.length - 1)], - uiSaturation * randomNumber(0.45, 0.8), - isDark ? bgBase + randomInteger(0, 13) : bgBase - randomInteger(0, 7) + uiSaturation * randomNumber(0.45, 0.6), + isDark ? bgBase + randomInteger(-3, 7) : bgBase + randomInteger(-17, 5) ), FG1: initialColors.FG1 || @@ -111,7 +111,7 @@ export function generateThemeColors( generateColor( schemeHues[randomInteger(0, schemeHues.length - 1)], uiSaturation * randomNumber(0.6, 1.25), - isDark ? bgBase + 10 : bgBase - 10 + isDark ? bgBase + randomInteger(-10, 10) : bgBase + randomInteger(-5, 5) ), INFO: initialColors.INFO || @@ -226,9 +226,9 @@ export function updateThemeColorsWithSaturation( const updatedColors: UIColors = { ...currentColors } const saturationMultipliers = { - BG1: 0.55, - BG2: 0.55, - BG3: 0.55, + BG1: 0.5, + BG2: 0.5, + BG3: 0.5, FG1: 1, FG2: 1, FG3: 1, diff --git a/src/lib/utils/codeSnippets.ts b/src/lib/utils/codeSnippets.ts new file mode 100644 index 0000000..68dda14 --- /dev/null +++ b/src/lib/utils/codeSnippets.ts @@ -0,0 +1,488 @@ +export const codeSnippets = { + 'typescript.tsx': + "import React, { useState, useEffect } from 'react';\n" + + '\n' + + '// Interface and Type\n' + + 'interface User {\n' + + ' id: number;\n' + + ' name: string;\n' + + '}\n' + + '\n' + + 'type UserProps = {\n' + + ' user: User;\n' + + '};\n' + + '\n' + + '// Decorator\n' + + 'function logged(target: any, key: string, descriptor: PropertyDescriptor) {\n' + + ' const original = descriptor.value;\n' + + ' descriptor.value = function (...args: any[]) {\n' + + ' console.log(`Calling ${key} with`, args);\n' + + ' return original.apply(this, args);\n' + + ' };\n' + + ' return descriptor;\n' + + '}\n' + + '\n' + + '// Class with generics\n' + + 'class DataFetcher {\n' + + ' private url: string;\n' + + '\n' + + ' constructor(url: string) {\n' + + ' this.url = url;\n' + + ' }\n' + + '\n' + + ' @logged\n' + + ' async fetchData(): Promise {\n' + + ' const response = await fetch(this.url);\n' + + ' return response.json();\n' + + ' }\n' + + '}\n' + + '\n' + + '// React component\n' + + 'const UserProfile: React.FC = ({ user }) => {\n' + + ' const [loading, setLoading] = useState(true);\n' + + '\n' + + ' useEffect(() => {\n' + + ' // Async function in useEffect\n' + + ' const loadUser = async () => {\n' + + ' try {\n' + + ' const fetcher = new DataFetcher(`/api/users/${user.id}`);\n' + + ' await fetcher.fetchData();\n' + + ' setLoading(false);\n' + + ' } catch (error) {\n' + + ' console.error("Failed to load user:", error);\n' + + ' }\n' + + ' };\n' + + '\n' + + ' loadUser();\n' + + ' }, [user.id]);\n' + + '\n' + + ' if (loading) return
Loading...
;\n' + + '\n' + + ' return (\n' + + '
\n' + + "

{user.name}'s Profile

\n" + + '

User ID: {user.id}

\n' + + '
\n' + + ' );\n' + + '};\n' + + '\n' + + 'export default UserProfile;\n' + + 'import type {\n' + + ' UIColors,\n' + + ' SyntaxColors,\n' + + ' AnsiColors,\n' + + ' VSCodeTheme,\n' + + '} from "@/lib/types/colors"\n' + + '\n' + + 'import Color from "color"\n' + + '\n' + + 'export function generateSemanticThemeJSON(\n' + + ' name: string = "Generated Color Theme",\n' + + ' colors: UIColors,\n' + + ' syntaxColors: SyntaxColors,\n' + + ' ansiColors: AnsiColors\n' + + '): { themeJSON: string; themeObject: VSCodeTheme } {\n' + + ' const theme = {\n' + + ' name: name,\n' + + ' type: Color(colors.BG1).isDark()\n' + + ' ? ("dark" as "dark" | "light")\n' + + ' : ("light" as "dark" | "light"),\n' + + ' semanticClass: "theme.rlabs",\n' + + ' semanticHighlighting: true,\n' + + ' colors: {\n' + + ' // # Integrated Terminal Colors\n' + + ' "terminal.background": colors.BG1,\n' + + ' "terminal.foreground": colors.FG1,\n' + + ' "terminal.border": colors.BORDER,\n' + + ' "terminal.ansiBrightBlack": ansiColors.BrightBlack,\n' + + ' "terminal.ansiBrightRed": ansiColors.BrightRed,\n' + + ' "terminal.ansiBrightGreen": ansiColors.BrightGreen,\n' + + ' "terminal.ansiBrightYellow": ansiColors.BrightYellow,\n' + + ' "terminal.ansiBrightBlue": ansiColors.BrightBlue,\n' + + ' "terminal.ansiBrightMagenta": ansiColors.BrightMagenta,\n' + + ' "terminal.ansiBrightCyan": ansiColors.BrightCyan,\n' + + ' "terminal.ansiBrightWhite": ansiColors.BrightWhite,\n' + + ' "terminal.ansiBlack": ansiColors.Black,\n' + + ' "terminal.ansiRed": ansiColors.Red,\n' + + ' "terminal.ansiGreen": ansiColors.Green,\n' + + ' "terminal.ansiYellow": ansiColors.Yellow,\n' + + ' "terminal.ansiBlue": ansiColors.Blue,\n' + + ' "terminal.ansiMagenta": ansiColors.Magenta,\n' + + ' "terminal.ansiCyan": ansiColors.Cyan,\n' + + ' "terminal.ansiWhite": ansiColors.White,\n' + + ' "terminal.selectionBackground": colors.selection,\n' + + ' }\n' + + ' };\n' + + '\n' + + ' return {\n' + + ' themeJSON: JSON.stringify(theme),\n' + + ' themeObject: theme\n' + + ' };\n' + + '}\n', + + 'javascript.js': + '// Class definition\n' + + 'class Animal {\n' + + ' constructor(name) {\n' + + ' this.name = name;\n' + + ' }\n' + + '\n' + + ' speak() {\n' + + ' console.log(`${this.name} makes a sound.`);\n' + + ' }\n' + + '}\n' + + '\n' + + '// Inheritance\n' + + 'class Dog extends Animal {\n' + + ' constructor(name, breed) {\n' + + ' super(name);\n' + + ' this.breed = breed;\n' + + ' }\n' + + '\n' + + ' speak() {\n' + + ' console.log(`${this.name} barks.`);\n' + + ' }\n' + + '}\n' + + '\n' + + '// Arrow function with default parameter\n' + + 'const createPet = (name, species = "Unknown") => ({\n' + + ' name,\n' + + ' species,\n' + + ' getDescription: () => `${name} is a ${species}.`\n' + + '});\n' + + '\n' + + '// Async function with try-catch\n' + + 'async function fetchData(url) {\n' + + ' try {\n' + + ' const response = await fetch(url);\n' + + ' const data = await response.json();\n' + + ' return data;\n' + + ' } catch (error) {\n' + + ' console.error("Error fetching data:", error);\n' + + ' }\n' + + '}\n' + + '\n' + + '// Regular expression\n' + + 'const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n' + + '\n' + + '// Template literal\n' + + 'const greeting = `Hello, ${name}!`;\n' + + '\n' + + '// Destructuring and spread operator\n' + + 'const { x, y, ...rest } = { x: 1, y: 2, a: 3, b: 4 };\n' + + 'const newArray = [...oldArray, newItem];\n' + + '\n' + + '// Map and filter\n' + + 'const numbers = [1, 2, 3, 4, 5];\n' + + 'const doubled = numbers.map(n => n * 2);\n' + + 'const evens = numbers.filter(n => n % 2 === 0);\n' + + '\n' + + '// Object methods\n' + + 'const math = {\n' + + ' add: (a, b) => a + b,\n' + + ' subtract: (a, b) => a - b,\n' + + ' multiply: (a, b) => a * b,\n' + + ' divide: (a, b) => a / b\n' + + '};\n' + + '\n' + + 'export { Animal, Dog, createPet, fetchData, emailRegex, math };\n', + + 'python.py': + '# Python example\n' + + 'import asyncio\n' + + 'import re\n' + + 'from typing import List, Dict, Optional\n' + + 'from dataclasses import dataclass\n' + + '\n' + + '# Decorator\n' + + 'def log_calls(func):\n' + + ' async def wrapper(*args, **kwargs):\n' + + ' print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")\n' + + ' result = await func(*args, **kwargs)\n' + + ' print(f"{func.__name__} returned {result}")\n' + + ' return result\n' + + ' return wrapper\n' + + '\n' + + '# Class with type hints\n' + + '@dataclass\n' + + 'class User:\n' + + ' id: int\n' + + ' name: str\n' + + ' email: str\n' + + '\n' + + 'class UserManager:\n' + + ' def __init__(self):\n' + + ' self.users: List[User] = []\n' + + '\n' + + ' async def add_user(self, user: User) -> None:\n' + + ' self.users.append(user)\n' + + '\n' + + ' @log_calls\n' + + ' async def get_user(self, user_id: int) -> Optional[User]:\n' + + ' return next((user for user in self.users if user.id == user_id), None)\n' + + '\n' + + ' async def get_all_users(self) -> List[User]:\n' + + ' return self.users\n' + + '\n' + + '# Async function with error handling\n' + + 'async def fetch_user_data(url: str) -> Dict:\n' + + ' try:\n' + + ' # Simulating an API call\n' + + ' await asyncio.sleep(1)\n' + + ' return {"id": 1, "name": "John Doe", "email": "john@example.com"}\n' + + ' except Exception as e:\n' + + ' print(f"Error fetching user data: {e}")\n' + + ' return {}\n' + + '\n' + + '# Regular expression\n' + + "email_pattern = re.compile(r'^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$')\n" + + '\n' + + '# Main function\n' + + 'async def main():\n' + + ' user_manager = UserManager()\n' + + '\n' + + ' # List comprehension\n' + + ' user_ids = [i for i in range(1, 6)]\n' + + '\n' + + ' for user_id in user_ids:\n' + + ' user_data = await fetch_user_data(f"https://api.example.com/users/{user_id}")\n' + + ' if user_data and email_pattern.match(user_data["email"]):\n' + + ' user = User(**user_data)\n' + + ' await user_manager.add_user(user)\n' + + '\n' + + ' # Dictionary comprehension\n' + + ' user_dict = {user.id: user.name for user in await user_manager.get_all_users()}\n' + + '\n' + + ' print("Users:", user_dict)\n' + + '\n' + + ' # Async context manager\n' + + ' async with asyncio.timeout(5):\n' + + ' user = await user_manager.get_user(1)\n' + + ' if user:\n' + + ' print(f"Found user: {user.name}")\n' + + '\n' + + 'if __name__ == "__main__":\n' + + ' asyncio.run(main())\n', + + 'markdown.md': + '# Theme Preview Project\n' + + '\n' + + '## Table of Contents\n' + + '- [Introduction](#introduction)\n' + + '- [Features](#features)\n' + + '- [Installation](#installation)\n' + + '- [Usage](#usage)\n' + + '- [Contributing](#contributing)\n' + + '- [License](#license)\n' + + '\n' + + '## Introduction\n' + + '\n' + + 'This is a **Theme Preview** project that allows users to customize and preview various color schemes for their development environment.\n' + + '\n' + + '## Features\n' + + '\n' + + '- Supports multiple programming languages\n' + + '- Real-time preview\n' + + '- Customizable color palette\n' + + '- Export themes to various formats\n' + + '\n' + + '## Installation\n' + + '\n' + + 'To install the Theme Preview project, follow these steps:\n' + + '\n' + + '1. Clone the repository:\n' + + ' ```\n' + + ' git clone https://github.com/username/theme-preview.git\n' + + ' ```\n' + + '2. Navigate to the project directory:\n' + + ' ```\n' + + ' cd theme-preview\n' + + ' ```\n' + + '3. Install dependencies:\n' + + ' ```\n' + + ' npm install\n' + + ' ```\n' + + '\n' + + '## Usage\n' + + '\n' + + 'To start the Theme Preview application, run:\n' + + '\n' + + '```\n' + + 'npm start\n' + + '```\n' + + '\n' + + 'Then open your browser and navigate to `http://localhost:3000`.\n' + + '\n' + + '## Contributing\n' + + '\n' + + 'We welcome contributions! Please follow these steps to contribute:\n' + + '\n' + + '1. Fork the repository\n' + + '2. Create a new branch: `git checkout -b feature/your-feature-name`\n' + + "3. Make your changes and commit them: `git commit -m 'Add some feature'`\n" + + '4. Push to the branch: `git push origin feature/your-feature-name`\n' + + '5. Submit a pull request\n' + + '\n' + + '## License\n' + + '\n' + + 'This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n', + + 'html.html': + '\n' + + '\n' + + '\n' + + ' \n' + + ' \n' + + ' Theme Preview\n' + + ' \n' + + ' \n' + + '\n' + + '\n' + + '
\n' + + '

Welcome to Theme Preview

\n' + + ' \n' + + '
\n' + + '\n' + + '
\n' + + '
\n' + + '

Home

\n' + + '

This is the home section of our theme preview.

\n' + + '
\n' + + '\n' + + '
\n' + + '

About

\n' + + '

Learn more about our theme preview:

\n' + + '
    \n' + + '
  • Customizable colors
  • \n' + + '
  • Multiple language support
  • \n' + + '
  • Easy to use interface
  • \n' + + '
\n' + + '
\n' + + '\n' + + '
\n' + + '

Contact

\n' + + '
\n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + '
\n' + + '
\n' + + '
\n' + + '\n' + + ' \n' + + '\n' + + '\n', + + 'css.css': + '/* Variables */\n' + + ':root {\n' + + ' --primary-color: #3498db;\n' + + ' --secondary-color: #2ecc71;\n' + + ' --text-color: #333;\n' + + ' --background-color: #f4f4f4;\n' + + '}\n' + + '\n' + + '/* Global styles */\n' + + 'body {\n' + + ' font-family: Arial, sans-serif;\n' + + ' line-height: 1.6;\n' + + ' color: var(--text-color);\n' + + ' background-color: var(--background-color);\n' + + '}\n' + + '\n' + + '/* Header styles */\n' + + 'header {\n' + + ' background-color: var(--primary-color);\n' + + ' color: white;\n' + + ' padding: 1rem;\n' + + '}\n' + + '\n' + + '/* Navigation styles */\n' + + 'nav ul {\n' + + ' list-style-type: none;\n' + + ' display: flex;\n' + + '}\n' + + '\n' + + 'nav ul li {\n' + + ' margin-right: 1rem;\n' + + '}\n' + + '\n' + + 'nav ul li a {\n' + + ' color: white;\n' + + ' text-decoration: none;\n' + + '}\n' + + '\n' + + '/* Main content styles */\n' + + 'main {\n' + + ' padding: 2rem;\n' + + '}\n' + + '\n' + + 'section {\n' + + ' margin-bottom: 2rem;\n' + + '}\n' + + '\n' + + '/* Form styles */\n' + + 'form {\n' + + ' display: grid;\n' + + ' gap: 1rem;\n' + + '}\n' + + '\n' + + 'input, textarea {\n' + + ' width: 100%;\n' + + ' padding: 0.5rem;\n' + + '}\n' + + '\n' + + 'button {\n' + + ' background-color: var(--secondary-color);\n' + + ' color: white;\n' + + ' border: none;\n' + + ' padding: 0.5rem 1rem;\n' + + ' cursor: pointer;\n' + + '}\n' + + '\n' + + '/* Footer styles */\n' + + 'footer {\n' + + ' background-color: var(--primary-color);\n' + + ' color: white;\n' + + ' text-align: center;\n' + + ' padding: 1rem;\n' + + '}\n' + + '\n' + + '/* Media query for responsive design */\n' + + '@media (max-width: 768px) {\n' + + ' nav ul {\n' + + ' flex-direction: column;\n' + + ' }\n' + + '\n' + + ' nav ul li {\n' + + ' margin-bottom: 0.5rem;\n' + + ' }\n' + + '}\n' + + '\n' + + '/* Animation */\n' + + '@keyframes fadeIn {\n' + + ' from { opacity: 0; }\n' + + ' to { opacity: 1; }\n' + + '}\n' + + '\n' + + '.fade-in {\n' + + ' animation: fadeIn 1s ease-in-out;\n' + + '}\n', +} + +export type CodeSnippetKey = keyof typeof codeSnippets