├── .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 |
21 |
{width} px
22 |
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 |
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 |
--------------------------------------------------------------------------------