├── .eslintignore ├── app ├── favicon.ico ├── layout.tsx ├── globals.css └── page.tsx ├── postcss.config.mjs ├── lib └── utils.ts ├── public ├── window.svg ├── file.svg └── globe.svg ├── prettier.config.js ├── components.json ├── next.config.ts ├── components ├── controls │ ├── DarkModeSwitch.tsx │ ├── PaddingSlider.tsx │ ├── BackgroundSwitch.tsx │ ├── FontSizeInput.tsx │ ├── FontSelect.tsx │ ├── ThemeSelect.tsx │ ├── LanguageSelect.tsx │ └── ExportOptions.tsx ├── WidthMeasurement.tsx ├── ui │ ├── input.tsx │ ├── switch.tsx │ ├── slider.tsx │ ├── button.tsx │ ├── card.tsx │ ├── select.tsx │ └── dropdown-menu.tsx └── CodeEditor.tsx ├── .gitignore ├── tsconfig.json ├── eslint.config.mjs ├── README.md ├── package.json ├── store └── use-preferences-store.ts └── options.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | *.json 2 | *.md 3 | *.mdx 4 | *.png 5 | *.svg 6 | *.webp 7 | *.webp 8 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AayushBharti/Snippix/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | } 4 | 5 | export default config 6 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: "avoid", 3 | bracketSameLine: false, 4 | bracketSpacing: true, 5 | htmlWhitespaceSensitivity: "css", 6 | insertPragma: false, 7 | jsxSingleQuote: false, 8 | printWidth: 80, 9 | proseWrap: "always", 10 | quoteProps: "as-needed", 11 | requirePragma: false, 12 | semi: false, 13 | singleQuote: false, 14 | tabWidth: 2, 15 | trailingComma: "all", 16 | useTabs: false, 17 | plugins: [ 18 | "prettier-plugin-tailwindcss", 19 | ], 20 | } 21 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | reactStrictMode: true, 6 | eslint: { 7 | ignoreDuringBuilds: true, 8 | }, 9 | experimental: { 10 | optimizeCss: true, 11 | }, 12 | async redirects() { 13 | return [ 14 | { 15 | source: "/owner", 16 | destination: "https://github.com/aayushbharti", 17 | permanent: true, 18 | }, 19 | ]; 20 | }, 21 | }; 22 | 23 | export default nextConfig; 24 | -------------------------------------------------------------------------------- /components/controls/DarkModeSwitch.tsx: -------------------------------------------------------------------------------- 1 | import { usePreferencesStore } from "@/store/use-preferences-store"; 2 | import { Switch } from "../ui/switch"; 3 | 4 | export default function DarkModeSwitch() { 5 | const darkMode = usePreferencesStore((state) => state.darkMode); 6 | 7 | return ( 8 |
9 | 12 | 15 | usePreferencesStore.setState({ darkMode: checked }) 16 | } 17 | className="my-1.5" 18 | /> 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /components/controls/PaddingSlider.tsx: -------------------------------------------------------------------------------- 1 | import { usePreferencesStore } from "@/store/use-preferences-store"; 2 | import { Slider } from "../ui/slider"; 3 | 4 | export default function PaddingSlider() { 5 | const padding = usePreferencesStore((state) => state.padding); 6 | 7 | return ( 8 |
9 | 12 | usePreferencesStore.setState({ padding })} 16 | max={128} 17 | step={8} 18 | /> 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env 35 | .env.local 36 | 37 | # vercel 38 | .vercel 39 | 40 | # typescript 41 | *.tsbuildinfo 42 | next-env.d.ts 43 | -------------------------------------------------------------------------------- /components/controls/BackgroundSwitch.tsx: -------------------------------------------------------------------------------- 1 | import { usePreferencesStore } from "@/store/use-preferences-store"; 2 | import { Switch } from "../ui/switch"; 3 | 4 | export default function BackgroundSwitch() { 5 | const showBg = usePreferencesStore((state) => state.showBackground); 6 | 7 | return ( 8 |
9 | 12 | 15 | usePreferencesStore.setState({ showBackground: checked }) 16 | } 17 | className="my-1.5" 18 | /> 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /components/controls/FontSizeInput.tsx: -------------------------------------------------------------------------------- 1 | import { usePreferencesStore } from "@/store/use-preferences-store"; 2 | import { Input } from "../ui/input"; 3 | 4 | export default function FontSizeInput() { 5 | const fontSize = usePreferencesStore((state) => state.fontSize); 6 | 7 | return ( 8 |
9 | 12 | 18 | usePreferencesStore.setState({ fontSize: Number(e.target.value) }) 19 | } 20 | /> 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "downlevelIteration": true, 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /components/WidthMeasurement.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | export default function WidthMeasurement({ 4 | showWidth, 5 | width, 6 | }: { 7 | showWidth: boolean; 8 | width: number; 9 | }) { 10 | return ( 11 |
17 |
18 |
19 |
20 |
21 | {width} px 22 |
23 |
24 |
25 |
26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Geist, Geist_Mono } from "next/font/google"; 3 | import "./globals.css"; 4 | import { Toaster } from "react-hot-toast"; 5 | 6 | const geistSans = Geist({ 7 | variable: "--font-geist-sans", 8 | subsets: ["latin"], 9 | }); 10 | 11 | const geistMono = Geist_Mono({ 12 | variable: "--font-geist-mono", 13 | subsets: ["latin"], 14 | }); 15 | 16 | export const metadata: Metadata = { 17 | title: "Snippix", 18 | description: 19 | "Snippix is a tool that allows you to create beautiful code snippets.", 20 | }; 21 | 22 | export default function RootLayout({ 23 | children, 24 | }: Readonly<{ 25 | children: React.ReactNode; 26 | }>) { 27 | return ( 28 | 29 | 32 | 33 | {children} 34 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) { 6 | return ( 7 | 18 | ) 19 | } 20 | 21 | export { Input } 22 | -------------------------------------------------------------------------------- /components/controls/FontSelect.tsx: -------------------------------------------------------------------------------- 1 | import { fonts } from "@/options"; 2 | import { 3 | Select, 4 | SelectContent, 5 | SelectItem, 6 | SelectTrigger, 7 | SelectValue, 8 | } from "../ui/select"; 9 | import { usePreferencesStore } from "@/store/use-preferences-store"; 10 | 11 | export default function FontSelect() { 12 | const fontStyle = usePreferencesStore((state) => state.fontStyle); 13 | 14 | return ( 15 |
16 | 19 | 36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SwitchPrimitive from "@radix-ui/react-switch" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Switch({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | 27 | 28 | ) 29 | } 30 | 31 | export { Switch } 32 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path" 2 | import { fileURLToPath } from "url" 3 | import { FlatCompat } from "@eslint/eslintrc" 4 | 5 | const __filename = fileURLToPath(import.meta.url) 6 | const __dirname = dirname(__filename) 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }) 11 | 12 | const eslintConfig = [ 13 | ...compat.extends( 14 | "next/core-web-vitals", 15 | "next/typescript", 16 | "plugin:unicorn/recommended", 17 | "plugin:import/recommended", 18 | "plugin:tailwindcss/recommended", 19 | ), 20 | { 21 | rules: { 22 | "simple-import-sort/exports": "error", 23 | "simple-import-sort/imports": "error", 24 | "tailwindcss/classnames-order": "error", 25 | "tailwindcss/no-custom-classname": "off", 26 | "unicorn/no-array-callback-reference": "off", 27 | "unicorn/no-array-for-each": "off", 28 | "unicorn/no-array-reduce": "off", 29 | "unicorn/no-null": "off", 30 | "unicorn/filename-case": "off", 31 | "unicorn/prevent-abbreviations": [ 32 | "error", 33 | { 34 | allowList: { 35 | e2e: true, 36 | }, 37 | replacements: { 38 | props: false, 39 | ref: false, 40 | params: false, 41 | }, 42 | }, 43 | ], 44 | }, 45 | }, 46 | { 47 | plugins: ["simple-import-sort", "tailwindcss"], 48 | }, 49 | ] 50 | 51 | export default eslintConfig 52 | -------------------------------------------------------------------------------- /components/controls/ThemeSelect.tsx: -------------------------------------------------------------------------------- 1 | import { themes } from "@/options"; 2 | import { 3 | Select, 4 | SelectContent, 5 | SelectItem, 6 | SelectTrigger, 7 | SelectValue, 8 | } from "../ui/select"; 9 | import { cn } from "@/lib/utils"; 10 | import { usePreferencesStore } from "@/store/use-preferences-store"; 11 | 12 | export default function ThemeSelect() { 13 | const theme = usePreferencesStore((state) => state.theme); 14 | 15 | return ( 16 |
17 | 20 | 39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /components/controls/LanguageSelect.tsx: -------------------------------------------------------------------------------- 1 | import { languages } from "@/options"; 2 | import { 3 | Select, 4 | SelectContent, 5 | SelectItem, 6 | SelectTrigger, 7 | SelectValue, 8 | } from "../ui/select"; 9 | import { usePreferencesStore } from "@/store/use-preferences-store"; 10 | import { MagicWandIcon } from "@radix-ui/react-icons"; 11 | 12 | export default function LanguageSelect() { 13 | const language = usePreferencesStore((state) => state.language); 14 | const autoDetectLanguage = usePreferencesStore( 15 | (state) => state.autoDetectLanguage 16 | ); 17 | 18 | const handleChange = (language: string) => { 19 | if (language === "auto-detect") { 20 | usePreferencesStore.setState({ 21 | autoDetectLanguage: true, 22 | language: "plaintext", 23 | }); 24 | } else { 25 | usePreferencesStore.setState({ autoDetectLanguage: false, language }); 26 | } 27 | }; 28 | return ( 29 |
30 | 33 | 47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sinppix - Code Screenshot 2 | 3 | A powerful tool for sharing code snippets with additional features. Share beautiful screenshots of your code on your social media platforms. 4 | 5 | Project Live at: https://snippix.vercel.app/ 6 | 7 | --- 8 | 9 | ## 🎨 Features 10 | 11 | - 10+ elegant themes (light + dark theme included). 12 | - 12+ font styles (popular monospace fonts). 13 | - Auto-detection & syntax highlighting for all major programming languages. 14 | - Export as PNG/SVG, copy to clipboard, or share via link. 15 | - Change font size, background, padding, line numbers, and more. 16 | 17 | --- 18 | 19 | ## 🧪 Technologies Used 20 | 21 | - [**React**](https://react.dev) – Front-end JavaScript library 22 | - [**Next.js**](https://nextjs.org) – Full-stack React framework with SSR 23 | - [**TypeScript**](https://www.typescriptlang.org) – Static typing for scalable 24 | apps 25 | - [**Tailwind CSS**](https://tailwindcss.com) – Utility-first CSS framework 26 | - [**ShadCN UI**](https://ui.shadcn.dev) – Radix-powered component library 27 | styled with Tailwind 28 | - [**Zustand**](https://github.com/pmndrs/zustand) – Simple and fast state 29 | management 30 | - [**Highlight.js**](https://highlightjs.org) – Language detection and syntax 31 | highlighting 32 | - [**React Simple Code Editor**](https://github.com/satya164/react-simple-code-editor) 33 | – Lightweight code editor in the browser 34 | - [**HTML-to-Image**](https://github.com/bubkoo/html-to-image) – Converts DOM to 35 | image using canvas/SVG 36 | - [**React Hot Toast**](https://react-hot-toast.com) – Sleek toast notifications 37 | - [**React Hotkeys Hook**](https://github.com/JohannesKlauss/react-hotkeys-hook) 38 | – Keyboard shortcuts made easy 39 | - [**Resizable**](https://github.com/bokuweb/react-resizable-box) – Resizable 40 | container component 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snippix", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@radix-ui/react-dropdown-menu": "^2.1.11", 13 | "@radix-ui/react-icons": "^1.3.2", 14 | "@radix-ui/react-select": "^2.2.2", 15 | "@radix-ui/react-slider": "^1.3.2", 16 | "@radix-ui/react-slot": "^1.2.0", 17 | "@radix-ui/react-switch": "^1.2.2", 18 | "class-variance-authority": "^0.7.1", 19 | "clsx": "^2.1.1", 20 | "critters": "^0.0.25", 21 | "flourite": "^1.3.0", 22 | "highlight.js": "^11.11.1", 23 | "html-to-image": "^1.11.13", 24 | "lucide-react": "^0.501.0", 25 | "next": "15.3.1", 26 | "re-resizable": "^6.11.2", 27 | "react": "^19.1.0", 28 | "react-dom": "^19.1.0", 29 | "react-hot-toast": "^2.5.2", 30 | "react-hotkeys-hook": "^5.0.1", 31 | "react-resizable": "^3.0.5", 32 | "react-simple-code-editor": "^0.14.1", 33 | "tailwind-merge": "^3.2.0", 34 | "zustand": "^5.0.3" 35 | }, 36 | "devDependencies": { 37 | "@eslint/eslintrc": "^3.3.1", 38 | "@tailwindcss/postcss": "^4.1.4", 39 | "@types/node": "^22.14.1", 40 | "@types/react": "^19.1.2", 41 | "@types/react-dom": "^19.1.2", 42 | "eslint": "^9.25.0", 43 | "eslint-config-next": "15.3.1", 44 | "eslint-config-prettier": "^10.1.2", 45 | "eslint-plugin-import": "^2.31.0", 46 | "eslint-plugin-prettier": "^5.2.6", 47 | "eslint-plugin-react": "^7.37.5", 48 | "eslint-plugin-simple-import-sort": "^12.1.1", 49 | "eslint-plugin-tailwindcss": "3.18.0", 50 | "eslint-plugin-unicorn": "^58.0.0", 51 | "prettier": "^3.5.3", 52 | "prettier-plugin-tailwindcss": "^0.6.11", 53 | "tailwindcss": "^4.1.4", 54 | "tw-animate-css": "^1.2.5", 55 | "typescript": "^5.8.3" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /components/ui/slider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SliderPrimitive from "@radix-ui/react-slider" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Slider({ 9 | className, 10 | defaultValue, 11 | value, 12 | min = 0, 13 | max = 100, 14 | ...props 15 | }: React.ComponentProps) { 16 | const _values = React.useMemo( 17 | () => 18 | Array.isArray(value) 19 | ? value 20 | : Array.isArray(defaultValue) 21 | ? defaultValue 22 | : [min, max], 23 | [value, defaultValue, min, max] 24 | ) 25 | 26 | return ( 27 | 39 | 45 | 51 | 52 | {Array.from({ length: _values.length }, (_, index) => ( 53 | 58 | ))} 59 | 60 | ) 61 | } 62 | 63 | export { Slider } 64 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 16 | outline: 17 | "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", 20 | ghost: 21 | "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", 22 | link: "text-primary underline-offset-4 hover:underline", 23 | }, 24 | size: { 25 | default: "h-9 px-4 py-2 has-[>svg]:px-3", 26 | sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", 27 | lg: "h-10 rounded-md px-6 has-[>svg]:px-4", 28 | icon: "size-9", 29 | }, 30 | }, 31 | defaultVariants: { 32 | variant: "default", 33 | size: "default", 34 | }, 35 | } 36 | ) 37 | 38 | function Button({ 39 | className, 40 | variant, 41 | size, 42 | asChild = false, 43 | ...props 44 | }: React.ComponentProps<"button"> & 45 | VariantProps & { 46 | asChild?: boolean 47 | }) { 48 | const Comp = asChild ? Slot : "button" 49 | 50 | return ( 51 | 56 | ) 57 | } 58 | 59 | export { Button, buttonVariants } 60 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | function Card({ className, ...props }: React.ComponentProps<"div">) { 6 | return ( 7 |
15 | ); 16 | } 17 | 18 | function CardHeader({ className, ...props }: React.ComponentProps<"div">) { 19 | return ( 20 |
28 | ); 29 | } 30 | 31 | function CardTitle({ className, ...props }: React.ComponentProps<"div">) { 32 | return ( 33 |
38 | ); 39 | } 40 | 41 | function CardDescription({ className, ...props }: React.ComponentProps<"div">) { 42 | return ( 43 |
48 | ); 49 | } 50 | 51 | function CardAction({ className, ...props }: React.ComponentProps<"div">) { 52 | return ( 53 |
61 | ); 62 | } 63 | 64 | function CardContent({ className, ...props }: React.ComponentProps<"div">) { 65 | return ( 66 |
71 | ); 72 | } 73 | 74 | function CardFooter({ className, ...props }: React.ComponentProps<"div">) { 75 | return ( 76 |
81 | ); 82 | } 83 | 84 | export { 85 | Card, 86 | CardHeader, 87 | CardFooter, 88 | CardTitle, 89 | CardAction, 90 | CardDescription, 91 | CardContent, 92 | }; 93 | -------------------------------------------------------------------------------- /store/use-preferences-store.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | import { persist } from "zustand/middleware"; 3 | 4 | // export const usePreferencesStore = create( 5 | // persist( 6 | // () => ({ 7 | // code: "", 8 | // title: "Untitled", 9 | // theme: "hyper", 10 | // darkMode: true, 11 | // showBackground: true, 12 | // language: "plaintext", 13 | // autoDetectLanguage: false, 14 | // fontSize: 16, 15 | // fontStyle: "jetBrainsMono", 16 | // padding: 64, 17 | // }), 18 | // { 19 | // name: "user-preferences", 20 | // } 21 | // ) 22 | // ); 23 | 24 | // Persistent: Saves data to localStorage under the key user-preferences, so the state is retained after refreshing/restarting the app. 25 | 26 | interface PreferencesState { 27 | code: string; 28 | title: string; 29 | theme: string; 30 | darkMode: boolean; 31 | showBackground: boolean; 32 | language: string; 33 | autoDetectLanguage: boolean; 34 | fontSize: number; 35 | fontStyle: string; 36 | padding: number; 37 | 38 | // Setters 39 | setCode: (code: string) => void; 40 | setTitle: (title: string) => void; 41 | setTheme: (theme: string) => void; 42 | toggleDarkMode: () => void; 43 | toggleBackground: () => void; 44 | setLanguage: (language: string) => void; 45 | setAutoDetectLanguage: (enabled: boolean) => void; 46 | setFontSize: (size: number) => void; 47 | setFontStyle: (style: string) => void; 48 | setPadding: (padding: number) => void; 49 | } 50 | 51 | // Create a persistent Zustand store with type safety and update methods 52 | export const usePreferencesStore = create()( 53 | persist( 54 | (set) => ({ 55 | code: "", 56 | title: "Untitled", 57 | theme: "hyper", 58 | darkMode: true, 59 | showBackground: true, 60 | language: "plaintext", 61 | autoDetectLanguage: false, 62 | fontSize: 16, 63 | fontStyle: "jetBrainsMono", 64 | padding: 64, 65 | 66 | // Setters 67 | setCode: (code) => set({ code }), 68 | setTitle: (title) => set({ title }), 69 | setTheme: (theme) => set({ theme }), 70 | toggleDarkMode: () => set((state) => ({ darkMode: !state.darkMode })), 71 | toggleBackground: () => 72 | set((state) => ({ showBackground: !state.showBackground })), 73 | setLanguage: (language) => set({ language }), 74 | setAutoDetectLanguage: (enabled) => set({ autoDetectLanguage: enabled }), 75 | setFontSize: (size) => set({ fontSize: size }), 76 | setFontStyle: (style) => set({ fontStyle: style }), 77 | setPadding: (padding) => set({ padding }), 78 | }), 79 | { 80 | name: "user-preferences", // Key used in localStorage 81 | } 82 | ) 83 | ); 84 | -------------------------------------------------------------------------------- /components/CodeEditor.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import flourite from "flourite"; 3 | import { codeSnippets, fonts } from "@/options"; 4 | import hljs from "highlight.js"; 5 | import { useEffect } from "react"; 6 | import Editor from "react-simple-code-editor"; 7 | import { usePreferencesStore } from "@/store/use-preferences-store"; 8 | 9 | export default function CodeEditor() { 10 | const store = usePreferencesStore(); 11 | 12 | // Add random code snippets on mount 13 | useEffect(() => { 14 | const randomSnippet = 15 | codeSnippets[Math.floor(Math.random() * codeSnippets.length)]; 16 | usePreferencesStore.setState(randomSnippet); 17 | }, []); 18 | 19 | // Auto Detect Language 20 | useEffect(() => { 21 | if (store.autoDetectLanguage) { 22 | // use flourite to detect language and provide highlighting 23 | const { language } = flourite(store.code, { noUnknown: true }); 24 | usePreferencesStore.setState({ 25 | language: language.toLowerCase() || "plaintext", 26 | }); 27 | } 28 | }, [store.autoDetectLanguage, store.code]); 29 | 30 | return ( 31 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 50 | usePreferencesStore.setState({ title: e.target.value }) 51 | } 52 | spellCheck={false} 53 | onClick={(e) => { 54 | if (e.target instanceof HTMLInputElement) { 55 | e.target.select(); 56 | } 57 | }} 58 | className="bg-transparent text-center text-gray-400 text-sm font-medium focus:outline-none" 59 | /> 60 |
61 |
62 |
70 | usePreferencesStore.setState({ code })} 73 | highlight={(code) => 74 | hljs.highlight(code, { language: store.language || "plaintext" }) 75 | .value 76 | } 77 | style={{ 78 | fontFamily: fonts[store.fontStyle as keyof typeof fonts].name, 79 | fontSize: store.fontSize, 80 | }} 81 | textareaClassName="focus:outline-none" 82 | onClick={(e) => { 83 | if (e.target instanceof HTMLTextAreaElement) { 84 | e.target.select(); 85 | } 86 | }} 87 | /> 88 |
89 |
90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | @import "tw-animate-css"; 3 | 4 | @custom-variant dark (&:is(.dark *)); 5 | 6 | @theme inline { 7 | --color-background: var(--background); 8 | --color-foreground: var(--foreground); 9 | --font-sans: var(--font-geist-sans); 10 | --font-mono: var(--font-geist-mono); 11 | --color-sidebar-ring: var(--sidebar-ring); 12 | --color-sidebar-border: var(--sidebar-border); 13 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 14 | --color-sidebar-accent: var(--sidebar-accent); 15 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 16 | --color-sidebar-primary: var(--sidebar-primary); 17 | --color-sidebar-foreground: var(--sidebar-foreground); 18 | --color-sidebar: var(--sidebar); 19 | --color-chart-5: var(--chart-5); 20 | --color-chart-4: var(--chart-4); 21 | --color-chart-3: var(--chart-3); 22 | --color-chart-2: var(--chart-2); 23 | --color-chart-1: var(--chart-1); 24 | --color-ring: var(--ring); 25 | --color-input: var(--input); 26 | --color-border: var(--border); 27 | --color-destructive: var(--destructive); 28 | --color-accent-foreground: var(--accent-foreground); 29 | --color-accent: var(--accent); 30 | --color-muted-foreground: var(--muted-foreground); 31 | --color-muted: var(--muted); 32 | --color-secondary-foreground: var(--secondary-foreground); 33 | --color-secondary: var(--secondary); 34 | --color-primary-foreground: var(--primary-foreground); 35 | --color-primary: var(--primary); 36 | --color-popover-foreground: var(--popover-foreground); 37 | --color-popover: var(--popover); 38 | --color-card-foreground: var(--card-foreground); 39 | --color-card: var(--card); 40 | --radius-sm: calc(var(--radius) - 4px); 41 | --radius-md: calc(var(--radius) - 2px); 42 | --radius-lg: var(--radius); 43 | --radius-xl: calc(var(--radius) + 4px); 44 | } 45 | 46 | :root { 47 | --radius: 0.625rem; 48 | --background: oklch(1 0 0); 49 | --foreground: oklch(0.145 0 0); 50 | --card: oklch(1 0 0); 51 | --card-foreground: oklch(0.145 0 0); 52 | --popover: oklch(1 0 0); 53 | --popover-foreground: oklch(0.145 0 0); 54 | --primary: oklch(0.205 0 0); 55 | --primary-foreground: oklch(0.985 0 0); 56 | --secondary: oklch(0.97 0 0); 57 | --secondary-foreground: oklch(0.205 0 0); 58 | --muted: oklch(0.97 0 0); 59 | --muted-foreground: oklch(0.556 0 0); 60 | --accent: oklch(0.97 0 0); 61 | --accent-foreground: oklch(0.205 0 0); 62 | --destructive: oklch(0.577 0.245 27.325); 63 | --border: oklch(0.922 0 0); 64 | --input: oklch(0.922 0 0); 65 | --ring: oklch(0.708 0 0); 66 | --chart-1: oklch(0.646 0.222 41.116); 67 | --chart-2: oklch(0.6 0.118 184.704); 68 | --chart-3: oklch(0.398 0.07 227.392); 69 | --chart-4: oklch(0.828 0.189 84.429); 70 | --chart-5: oklch(0.769 0.188 70.08); 71 | --sidebar: oklch(0.985 0 0); 72 | --sidebar-foreground: oklch(0.145 0 0); 73 | --sidebar-primary: oklch(0.205 0 0); 74 | --sidebar-primary-foreground: oklch(0.985 0 0); 75 | --sidebar-accent: oklch(0.97 0 0); 76 | --sidebar-accent-foreground: oklch(0.205 0 0); 77 | --sidebar-border: oklch(0.922 0 0); 78 | --sidebar-ring: oklch(0.708 0 0); 79 | } 80 | 81 | .dark { 82 | --background: oklch(0.145 0 0); 83 | --foreground: oklch(0.985 0 0); 84 | --card: oklch(0.205 0 0); 85 | --card-foreground: oklch(0.985 0 0); 86 | --popover: oklch(0.205 0 0); 87 | --popover-foreground: oklch(0.985 0 0); 88 | --primary: oklch(0.922 0 0); 89 | --primary-foreground: oklch(0.205 0 0); 90 | --secondary: oklch(0.269 0 0); 91 | --secondary-foreground: oklch(0.985 0 0); 92 | --muted: oklch(0.269 0 0); 93 | --muted-foreground: oklch(0.708 0 0); 94 | --accent: oklch(0.269 0 0); 95 | --accent-foreground: oklch(0.985 0 0); 96 | --destructive: oklch(0.704 0.191 22.216); 97 | --border: oklch(1 0 0 / 10%); 98 | --input: oklch(1 0 0 / 15%); 99 | --ring: oklch(0.556 0 0); 100 | --chart-1: oklch(0.488 0.243 264.376); 101 | --chart-2: oklch(0.696 0.17 162.48); 102 | --chart-3: oklch(0.769 0.188 70.08); 103 | --chart-4: oklch(0.627 0.265 303.9); 104 | --chart-5: oklch(0.645 0.246 16.439); 105 | --sidebar: oklch(0.205 0 0); 106 | --sidebar-foreground: oklch(0.985 0 0); 107 | --sidebar-primary: oklch(0.488 0.243 264.376); 108 | --sidebar-primary-foreground: oklch(0.985 0 0); 109 | --sidebar-accent: oklch(0.269 0 0); 110 | --sidebar-accent-foreground: oklch(0.985 0 0); 111 | --sidebar-border: oklch(1 0 0 / 10%); 112 | --sidebar-ring: oklch(0.556 0 0); 113 | } 114 | 115 | @layer base { 116 | * { 117 | @apply border-border outline-ring/50; 118 | } 119 | 120 | body { 121 | @apply bg-background text-foreground; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | import { useRef } from "react"; 5 | import { useState } from "react"; 6 | import { usePreferencesStore } from "@/store/use-preferences-store"; 7 | import { fonts } from "@/options"; 8 | import { themes } from "@/options"; 9 | import { cn } from "@/lib/utils"; 10 | import CodeEditor from "@/components/CodeEditor"; 11 | import WidthMeasurement from "@/components/WidthMeasurement"; 12 | import { Button } from "@/components/ui/button"; 13 | import { Card, CardContent } from "@/components/ui/card"; 14 | import { Resizable } from "re-resizable"; 15 | import ThemeSelect from "@/components/controls/ThemeSelect"; 16 | import LanguageSelect from "@/components/controls/LanguageSelect"; 17 | import { ResetIcon } from "@radix-ui/react-icons"; 18 | import FontSelect from "@/components/controls/FontSelect"; 19 | import FontSizeInput from "@/components/controls/FontSizeInput"; 20 | import PaddingSlider from "@/components/controls/PaddingSlider"; 21 | import BackgroundSwitch from "@/components/controls/BackgroundSwitch"; 22 | import DarkModeSwitch from "@/components/controls/DarkModeSwitch"; 23 | import ExportOptions from "@/components/controls/ExportOptions"; 24 | 25 | function App() { 26 | const [width, setWidth] = useState("auto"); 27 | const [showWidth, setShowWidth] = useState(false); 28 | 29 | const theme = usePreferencesStore((state) => state.theme); 30 | const padding = usePreferencesStore((state) => state.padding); 31 | const fontStyle = usePreferencesStore((state) => state.fontStyle); 32 | const showBackground = usePreferencesStore((state) => state.showBackground); 33 | 34 | const editorRef = useRef(null); 35 | 36 | useEffect(() => { 37 | const queryParams = new URLSearchParams(location.search); 38 | if (queryParams.size === 0) return; 39 | const state = Object.fromEntries(queryParams); 40 | 41 | usePreferencesStore.setState({ 42 | ...state, 43 | code: state.code ? atob(state.code) : "", 44 | autoDetectLanguage: state.autoDetectLanguage === "true", 45 | darkMode: state.darkMode === "true", 46 | fontSize: Number(state.fontSize || 18), 47 | padding: Number(state.padding || 64), 48 | }); 49 | }, []); 50 | 51 | return ( 52 |
53 | 58 | 63 | 64 |
65 | setWidth(ref.offsetWidth.toString())} 71 | onResizeStart={() => setShowWidth(true)} 72 | onResizeStop={() => setShowWidth(false)} 73 | > 74 |
84 | 85 |
86 | 87 |
95 | 99 |
100 |
101 |
102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |
113 |
114 | 117 | } 118 | /> 119 |
120 | 121 | 122 |
123 | ); 124 | } 125 | 126 | export default App; 127 | -------------------------------------------------------------------------------- /components/controls/ExportOptions.tsx: -------------------------------------------------------------------------------- 1 | import { DownloadIcon, ImageIcon, Link2Icon, Share2Icon } from "lucide-react"; 2 | import { Button } from "../ui/button"; 3 | import { 4 | DropdownMenu, 5 | DropdownMenuContent, 6 | DropdownMenuItem, 7 | DropdownMenuSeparator, 8 | DropdownMenuShortcut, 9 | DropdownMenuTrigger, 10 | } from "../ui/dropdown-menu"; 11 | import { toast } from "react-hot-toast"; 12 | import { toBlob, toPng, toSvg } from "html-to-image"; 13 | import { usePreferencesStore } from "@/store/use-preferences-store"; 14 | import { useHotkeys } from "react-hotkeys-hook"; 15 | 16 | export default function ExportOptions({ 17 | targetRef, 18 | }: { 19 | targetRef: React.RefObject; 20 | }) { 21 | const title = usePreferencesStore((state) => state.title); 22 | 23 | const copyImage = async () => { 24 | const loading = toast.loading("Copying..."); 25 | 26 | try { 27 | // generate blob from DOM node using html-to-image library 28 | const imgBlob = await toBlob(targetRef.current, { 29 | pixelRatio: 2, 30 | }); 31 | 32 | // Create a new ClipboardItem from the image blob 33 | const img = new ClipboardItem({ "image/png": imgBlob as Blob }); 34 | navigator.clipboard.write([img]); 35 | 36 | toast.remove(loading); 37 | toast.success("Image copied to clipboard!"); 38 | } catch (error) { 39 | console.error(error); 40 | toast.remove(loading); 41 | toast.error("Something went wrong!"); 42 | } 43 | }; 44 | 45 | const copyLink = () => { 46 | try { 47 | // Get the current state using the 'usePreferencesStore ' hook 48 | const state = usePreferencesStore.getState(); 49 | 50 | // Encode the 'code' property of the state object to base-64 encoding 51 | const encodedCode = btoa(state.code); 52 | 53 | // Create a new URLSearchParams object with state parameters, including the encoded 'code' 54 | const queryParams = new URLSearchParams({ 55 | ...state, 56 | code: encodedCode, 57 | } as unknown as string).toString(); 58 | 59 | // Construct the URL with query parameters and copy it to the clipboard 60 | navigator.clipboard.writeText(`${location.href}?${queryParams}`); 61 | toast.success("Link copied to clipboard!"); 62 | } catch (error) { 63 | console.error(error); 64 | toast.error("Something went wrong!"); 65 | } 66 | }; 67 | 68 | // Save images in different formats 69 | const saveImage = async (name: string, format: string) => { 70 | const loading = toast.loading(`Exporting ${format} image...`); 71 | 72 | try { 73 | let imgUrl, filename; 74 | switch (format) { 75 | case "PNG": 76 | imgUrl = await toPng(targetRef.current, { pixelRatio: 2 }); 77 | filename = `${name}.png`; 78 | break; 79 | case "SVG": 80 | imgUrl = await toSvg(targetRef.current, { pixelRatio: 2 }); 81 | filename = `${name}.svg`; 82 | break; 83 | 84 | default: 85 | return; 86 | } 87 | // using anchor tag prompt dowload window 88 | const a = document.createElement("a"); 89 | a.href = imgUrl; 90 | a.download = filename; 91 | a.click(); 92 | 93 | toast.remove(loading); 94 | toast.success("Exported successfully!"); 95 | } catch (error) { 96 | console.error(error); 97 | toast.remove(loading); 98 | toast.error("Something went wrong!"); 99 | } 100 | }; 101 | 102 | useHotkeys("ctrl+c", copyImage); 103 | useHotkeys("shift+ctrl+c", copyLink); 104 | useHotkeys("ctrl+s", () => saveImage(title, "PNG")); 105 | useHotkeys("shift+ctrl+s", () => saveImage(title, "SVG")); 106 | 107 | return ( 108 | 109 | 110 | 114 | 115 | 116 | 117 | 118 | 119 | Copy Image 120 | ⌘C 121 | 122 | 123 | 124 | 125 | Copy Link 126 | ⇧⌘C 127 | 128 | 129 | 130 | 131 | saveImage(title, "PNG")} 134 | > 135 | 136 | Save as PNG 137 | ⌘S 138 | 139 | 140 | saveImage(title, "SVG")} 143 | > 144 | 145 | Save as SVG 146 | ⇧⌘S 147 | 148 | 149 | 150 | ); 151 | } 152 | -------------------------------------------------------------------------------- /components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SelectPrimitive from "@radix-ui/react-select" 5 | import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | function Select({ 10 | ...props 11 | }: React.ComponentProps) { 12 | return 13 | } 14 | 15 | function SelectGroup({ 16 | ...props 17 | }: React.ComponentProps) { 18 | return 19 | } 20 | 21 | function SelectValue({ 22 | ...props 23 | }: React.ComponentProps) { 24 | return 25 | } 26 | 27 | function SelectTrigger({ 28 | className, 29 | size = "default", 30 | children, 31 | ...props 32 | }: React.ComponentProps & { 33 | size?: "sm" | "default" 34 | }) { 35 | return ( 36 | 45 | {children} 46 | 47 | 48 | 49 | 50 | ) 51 | } 52 | 53 | function SelectContent({ 54 | className, 55 | children, 56 | position = "popper", 57 | ...props 58 | }: React.ComponentProps) { 59 | return ( 60 | 61 | 72 | 73 | 80 | {children} 81 | 82 | 83 | 84 | 85 | ) 86 | } 87 | 88 | function SelectLabel({ 89 | className, 90 | ...props 91 | }: React.ComponentProps) { 92 | return ( 93 | 98 | ) 99 | } 100 | 101 | function SelectItem({ 102 | className, 103 | children, 104 | ...props 105 | }: React.ComponentProps) { 106 | return ( 107 | 115 | 116 | 117 | 118 | 119 | 120 | {children} 121 | 122 | ) 123 | } 124 | 125 | function SelectSeparator({ 126 | className, 127 | ...props 128 | }: React.ComponentProps) { 129 | return ( 130 | 135 | ) 136 | } 137 | 138 | function SelectScrollUpButton({ 139 | className, 140 | ...props 141 | }: React.ComponentProps) { 142 | return ( 143 | 151 | 152 | 153 | ) 154 | } 155 | 156 | function SelectScrollDownButton({ 157 | className, 158 | ...props 159 | }: React.ComponentProps) { 160 | return ( 161 | 169 | 170 | 171 | ) 172 | } 173 | 174 | export { 175 | Select, 176 | SelectContent, 177 | SelectGroup, 178 | SelectItem, 179 | SelectLabel, 180 | SelectScrollDownButton, 181 | SelectScrollUpButton, 182 | SelectSeparator, 183 | SelectTrigger, 184 | SelectValue, 185 | } 186 | -------------------------------------------------------------------------------- /options.ts: -------------------------------------------------------------------------------- 1 | export const languages: Record = { 2 | bash: "Bash", 3 | c: "C", 4 | "c++": "C++", 5 | csharp: "C#", 6 | clojure: "Clojure", 7 | crystal: "Crystal", 8 | css: "CSS", 9 | diff: "Diff", 10 | dockerfile: "Docker", 11 | elm: "Elm", 12 | elixir: "Elixir", 13 | erlang: "Erlang", 14 | graphql: "GraphQL", 15 | go: "Go", 16 | haskell: "Haskell", 17 | html: "HTML", 18 | java: "Java", 19 | javascript: "JavaScript/JSX", 20 | json: "JSON", 21 | kotlin: "Kotlin", 22 | lisp: "Lisp", 23 | lua: "Lua", 24 | markdown: "Markdown", 25 | matlab: "MATLAB/Octave", 26 | pascal: "Pascal", 27 | plaintext: "Plaintext", 28 | powershell: "Powershell", 29 | objectivec: "Objective C", 30 | php: "PHP", 31 | python: "Python", 32 | ruby: "Ruby", 33 | rust: "Rust", 34 | scala: "Scala", 35 | scss: "SCSS", 36 | sql: "SQL", 37 | swift: "Swift", 38 | toml: "TOML", 39 | typescript: "TypeScript/TSX", 40 | xml: "XML", 41 | yaml: "YAML", 42 | }; 43 | 44 | export const themes: Record = { 45 | hyper: { 46 | background: "bg-gradient-to-br from-fuchsia-500 via-red-600 to-orange-400", 47 | theme: 48 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/atom-one-dark.min.css", 49 | }, 50 | oceanic: { 51 | background: "bg-gradient-to-br from-green-300 via-blue-500 to-purple-600", 52 | theme: 53 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/base16/material-darker.min.css", 54 | }, 55 | candy: { 56 | background: "bg-gradient-to-br from-pink-300 via-purple-300 to-indigo-400", 57 | theme: 58 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/base16/chalk.min.css", 59 | }, 60 | sublime: { 61 | background: "bg-gradient-to-br from-rose-400 via-fuchsia-500 to-indigo-500", 62 | theme: 63 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github-dark.min.css", 64 | }, 65 | horizon: { 66 | background: "bg-gradient-to-br from-orange-500 to-yellow-300", 67 | theme: 68 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/monokai-sublime.min.css", 69 | }, 70 | coral: { 71 | background: "bg-gradient-to-br from-blue-400 to-emerald-400", 72 | theme: 73 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/tokyo-night-dark.min.css", 74 | }, 75 | peach: { 76 | background: "bg-gradient-to-br from-rose-400 to-orange-300", 77 | theme: 78 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/base16/zenburn.min.css", 79 | }, 80 | flamingo: { 81 | background: "bg-gradient-to-br from-pink-400 to-pink-600", 82 | theme: 83 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/panda-syntax-dark.min.css", 84 | }, 85 | gotham: { 86 | background: "bg-gradient-to-br from-gray-700 via-gray-900 to-black", 87 | 88 | theme: 89 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/base16/black-metal-dark-funeral.min.css", 90 | }, 91 | ice: { 92 | background: "bg-gradient-to-br from-rose-100 to-teal-100", 93 | theme: 94 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/base16/ashes.min.css", 95 | }, 96 | }; 97 | 98 | export const fonts: Record = { 99 | jetBrainsMono: { 100 | name: "JetBrains Mono", 101 | src: "https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap", 102 | }, 103 | inconsolata: { 104 | name: "Inconsolata", 105 | src: "https://fonts.googleapis.com/css2?family=Inconsolata&display=swap", 106 | }, 107 | firaCode: { 108 | name: "Fira Code", 109 | src: "https://fonts.googleapis.com/css2?family=Fira+Code&display=swap", 110 | }, 111 | cascadiaCode: { 112 | name: "Cascadia Code", 113 | src: "https://cdn.jsdelivr.net/npm/@fontsource/cascadia-code@4.2.1/index.min.css", 114 | }, 115 | victorMono: { 116 | name: "Victor Mono", 117 | src: "https://fonts.googleapis.com/css2?family=Victor+Mono&display=swap", 118 | }, 119 | sourceCodePro: { 120 | name: "Source Code Pro", 121 | src: "https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap", 122 | }, 123 | ibmPlexMono: { 124 | name: "IBM Plex Mono", 125 | src: "https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap", 126 | }, 127 | robotoMono: { 128 | name: "Roboto Mono", 129 | src: "https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap", 130 | }, 131 | ubuntuMono: { 132 | name: "Ubuntu Mono", 133 | src: "https://fonts.googleapis.com/css2?family=Ubuntu+Mono&display=swap", 134 | }, 135 | spaceMono: { 136 | name: "Space Mono", 137 | src: "https://fonts.googleapis.com/css2?family=Space+Mono&display=swap", 138 | }, 139 | courierPrime: { 140 | name: "Courier Prime", 141 | src: "https://fonts.googleapis.com/css2?family=Courier+Prime&display=swap", 142 | }, 143 | anonymousPro: { 144 | name: "Anonymous Pro", 145 | src: "https://fonts.googleapis.com/css2?family=Anonymous+Pro&display=swap", 146 | }, 147 | oxygenMono: { 148 | name: "Oxygen Mono", 149 | src: "https://fonts.googleapis.com/css2?family=Oxygen+Mono&display=swap", 150 | }, 151 | redHatMono: { 152 | name: "Red Hat Mono", 153 | src: "https://fonts.googleapis.com/css2?family=Red+Hat+Mono&display=swap", 154 | }, 155 | }; 156 | 157 | export const codeSnippets = [ 158 | { 159 | language: "python", 160 | code: "def is_prime(n):\n if n <= 1:\n return False\n for i in range(2, int(n ** 0.5) + 1):\n if n % i == 0:\n return False\n return True", 161 | }, 162 | { 163 | language: "javascript", 164 | code: "const fibonacci = (n) => {\n if (n <= 1) return n;\n return fibonacci(n - 1) + fibonacci(n - 2);\n};\nconsole.log(fibonacci(10));", 165 | }, 166 | { 167 | language: "java", 168 | code: "import java.util.stream.IntStream;\n\nclass StreamExample {\n public static void main(String[] args) {\n IntStream.rangeClosed(1, 5).forEach(System.out::println);\n }\n}", 169 | }, 170 | { 171 | language: "c", 172 | code: '#include \n\nint main() {\n for (int i = 1; i <= 10; i++) {\n if (i % 2 == 0) {\n printf("%d\\n", i);\n }\n }\n return 0;\n}', 173 | }, 174 | { 175 | language: "ruby", 176 | code: 'class Animal\n attr_reader :name\n\n def initialize(name)\n @name = name\n end\n\n def speak\n raise NotImplementedError, "Subclasses must implement this method"\n end\nend', 177 | }, 178 | { 179 | language: "swift", 180 | code: "enum Compass {\n case north, south, east, west\n}\nlet currentDirection = Compass.east\nprint(currentDirection)", 181 | }, 182 | { 183 | language: "csharp", 184 | code: "using System;\nusing System.Linq;\n\nclass LINQExample {\n static void Main() {\n int[] numbers = { 3, 9, 2, 8, 6 };\n var evenNumbers = numbers.Where(n => n % 2 == 0);\n foreach (var num in evenNumbers) {\n Console.WriteLine(num);\n }\n }\n}", 185 | }, 186 | { 187 | language: "php", 188 | code: "", 189 | }, 190 | { 191 | language: "go", 192 | code: 'package main\n\nimport (\n "fmt"\n "math"\n)\n\nfunc main() {\n x := 4.0\n y := math.Sqrt(x)\n fmt.Printf("Square root of %.2f is %.2f\\n", x, y)\n}', 193 | }, 194 | { 195 | language: "rust", 196 | code: 'fn main() {\n let mut count = 0;\n loop {\n println!("Count: {}", count);\n count += 1;\n if count > 5 {\n break;\n }\n }\n}', 197 | }, 198 | ]; 199 | -------------------------------------------------------------------------------- /components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 5 | import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | function DropdownMenu({ 10 | ...props 11 | }: React.ComponentProps) { 12 | return 13 | } 14 | 15 | function DropdownMenuPortal({ 16 | ...props 17 | }: React.ComponentProps) { 18 | return ( 19 | 20 | ) 21 | } 22 | 23 | function DropdownMenuTrigger({ 24 | ...props 25 | }: React.ComponentProps) { 26 | return ( 27 | 31 | ) 32 | } 33 | 34 | function DropdownMenuContent({ 35 | className, 36 | sideOffset = 4, 37 | ...props 38 | }: React.ComponentProps) { 39 | return ( 40 | 41 | 50 | 51 | ) 52 | } 53 | 54 | function DropdownMenuGroup({ 55 | ...props 56 | }: React.ComponentProps) { 57 | return ( 58 | 59 | ) 60 | } 61 | 62 | function DropdownMenuItem({ 63 | className, 64 | inset, 65 | variant = "default", 66 | ...props 67 | }: React.ComponentProps & { 68 | inset?: boolean 69 | variant?: "default" | "destructive" 70 | }) { 71 | return ( 72 | 82 | ) 83 | } 84 | 85 | function DropdownMenuCheckboxItem({ 86 | className, 87 | children, 88 | checked, 89 | ...props 90 | }: React.ComponentProps) { 91 | return ( 92 | 101 | 102 | 103 | 104 | 105 | 106 | {children} 107 | 108 | ) 109 | } 110 | 111 | function DropdownMenuRadioGroup({ 112 | ...props 113 | }: React.ComponentProps) { 114 | return ( 115 | 119 | ) 120 | } 121 | 122 | function DropdownMenuRadioItem({ 123 | className, 124 | children, 125 | ...props 126 | }: React.ComponentProps) { 127 | return ( 128 | 136 | 137 | 138 | 139 | 140 | 141 | {children} 142 | 143 | ) 144 | } 145 | 146 | function DropdownMenuLabel({ 147 | className, 148 | inset, 149 | ...props 150 | }: React.ComponentProps & { 151 | inset?: boolean 152 | }) { 153 | return ( 154 | 163 | ) 164 | } 165 | 166 | function DropdownMenuSeparator({ 167 | className, 168 | ...props 169 | }: React.ComponentProps) { 170 | return ( 171 | 176 | ) 177 | } 178 | 179 | function DropdownMenuShortcut({ 180 | className, 181 | ...props 182 | }: React.ComponentProps<"span">) { 183 | return ( 184 | 192 | ) 193 | } 194 | 195 | function DropdownMenuSub({ 196 | ...props 197 | }: React.ComponentProps) { 198 | return 199 | } 200 | 201 | function DropdownMenuSubTrigger({ 202 | className, 203 | inset, 204 | children, 205 | ...props 206 | }: React.ComponentProps & { 207 | inset?: boolean 208 | }) { 209 | return ( 210 | 219 | {children} 220 | 221 | 222 | ) 223 | } 224 | 225 | function DropdownMenuSubContent({ 226 | className, 227 | ...props 228 | }: React.ComponentProps) { 229 | return ( 230 | 238 | ) 239 | } 240 | 241 | export { 242 | DropdownMenu, 243 | DropdownMenuPortal, 244 | DropdownMenuTrigger, 245 | DropdownMenuContent, 246 | DropdownMenuGroup, 247 | DropdownMenuLabel, 248 | DropdownMenuItem, 249 | DropdownMenuCheckboxItem, 250 | DropdownMenuRadioGroup, 251 | DropdownMenuRadioItem, 252 | DropdownMenuSeparator, 253 | DropdownMenuShortcut, 254 | DropdownMenuSub, 255 | DropdownMenuSubTrigger, 256 | DropdownMenuSubContent, 257 | } 258 | --------------------------------------------------------------------------------