From 52fb82a706cd6d28eecf5d0b0b633613d1795f18 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Tue, 20 Jan 2026 16:21:03 +0000 Subject: [PATCH] refactor: migrate to M3 SCSS and remove Radix/Tailwind dependencies Co-authored-by: aider (openrouter/anthropic/claude-sonnet-4.5) --- next.config.js | 62 +-------- package.json | 121 +++++------------ postcss.config.mjs | 5 + src/app/globals.css | 62 +++++++++ src/app/layout.tsx | 51 ++++--- src/lib/utils.ts | 50 ++++++- src/styles/theme.scss | 307 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 489 insertions(+), 169 deletions(-) create mode 100644 postcss.config.mjs create mode 100644 src/app/globals.css create mode 100644 src/styles/theme.scss diff --git a/next.config.js b/next.config.js index 9d477ea..830bde8 100644 --- a/next.config.js +++ b/next.config.js @@ -1,62 +1,12 @@ -/* eslint-env node */ /** @type {import('next').NextConfig} */ -const isGithubPages = process.env.GITHUB_PAGES === 'true' || process.env.GITHUB_ACTIONS === 'true' -const repoBasePath = process.env.NEXT_PUBLIC_BASE_PATH - || (isGithubPages && process.env.GITHUB_REPOSITORY - ? `/${process.env.GITHUB_REPOSITORY.split('/')[1]}` - : '') - const nextConfig = { - output: process.env.BUILD_STATIC || isGithubPages ? 'export' : 'standalone', - basePath: repoBasePath, - assetPrefix: repoBasePath || undefined, - images: { - unoptimized: true, + sassOptions: { + includePaths: ['./src/styles'], + additionalData: `@use "m3-scss/material" as mat;`, }, experimental: { - optimizePackageImports: ['@radix-ui/react-icons', '@phosphor-icons/react'], - turbopackScopeHoisting: false, + optimizePackageImports: ['@phosphor-icons/react'], }, - serverExternalPackages: ['pyodide'], - webpack: (config, { isServer, webpack }) => { - // Pyodide contains references to Node.js built-in modules that should be ignored in browser bundles - if (!isServer) { - // Replace node: protocol imports with empty modules - config.plugins.push( - new webpack.NormalModuleReplacementPlugin( - /^node:/, - (resource) => { - resource.request = resource.request.replace(/^node:/, ''); - } - ) - ); +} - // Set fallbacks for node modules to false (don't polyfill) - config.resolve.fallback = { - ...config.resolve.fallback, - fs: false, - child_process: false, - crypto: false, - path: false, - url: false, - vm: false, - }; - - // Exclude pyodide from server-side rendering completely - config.resolve.alias = { - ...config.resolve.alias, - pyodide: false, - }; - } - - // On server, also exclude pyodide - if (isServer) { - config.externals = config.externals || []; - config.externals.push('pyodide'); - } - - return config; - }, -}; - -export default nextConfig; +module.exports = nextConfig diff --git a/package.json b/package.json index dce8ed6..89151ca 100644 --- a/package.json +++ b/package.json @@ -1,92 +1,33 @@ { - "name": "spark-template", - "type": "module", - "private": true, - "version": "0.0.0", - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "eslint .", - "stylelint": "stylelint \"src/**/*.{scss,css}\"", - "kill": "fuser -k 5000/tcp", - "test:e2e": "playwright test" - }, - "dependencies": { - "@babel/standalone": "^7.28.6", - "@hookform/resolvers": "^4.1.3", - "@monaco-editor/react": "^4.7.0", - "@phosphor-icons/react": "^2.1.7", - "@radix-ui/colors": "^3.0.0", - "@radix-ui/react-accordion": "^1.2.3", - "@radix-ui/react-alert-dialog": "^1.1.6", - "@radix-ui/react-aspect-ratio": "^1.1.2", - "@radix-ui/react-avatar": "^1.1.3", - "@radix-ui/react-checkbox": "^1.1.4", - "@radix-ui/react-collapsible": "^1.1.3", - "@radix-ui/react-context-menu": "^2.2.6", - "@radix-ui/react-dialog": "^1.1.6", - "@radix-ui/react-dropdown-menu": "^2.1.6", - "@radix-ui/react-hover-card": "^1.1.6", - "@radix-ui/react-label": "^2.1.2", - "@radix-ui/react-menubar": "^1.1.6", - "@radix-ui/react-navigation-menu": "^1.2.5", - "@radix-ui/react-popover": "^1.1.6", - "@radix-ui/react-progress": "^1.1.2", - "@radix-ui/react-radio-group": "^1.2.3", - "@radix-ui/react-scroll-area": "^1.2.9", - "@radix-ui/react-select": "^2.1.6", - "@radix-ui/react-separator": "^1.1.2", - "@radix-ui/react-slider": "^1.3.6", - "@radix-ui/react-slot": "^1.1.2", - "@radix-ui/react-switch": "^1.1.3", - "@radix-ui/react-tabs": "^1.1.3", - "@radix-ui/react-toggle": "^1.1.2", - "@radix-ui/react-toggle-group": "^1.1.2", - "@radix-ui/react-tooltip": "^1.1.8", - "@reduxjs/toolkit": "^2.11.2", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "date-fns": "^3.6.0", - "embla-carousel-react": "^8.5.2", - "framer-motion": "^12.6.2", - "lucide-react": "^0.562.0", - "marked": "^15.0.7", - "next": "16.1.3", - "next-themes": "^0.4.6", - "pyodide": "^0.27.0", - "react": "^19.2.3", - "react-dom": "^19.2.3", - "react-error-boundary": "^6.0.0", - "react-hook-form": "^7.54.2", - "react-redux": "^9.2.0", - "react-resizable-panels": "^2.1.7", - "recharts": "^2.15.1", - "sass": "^1.97.2", - "sonner": "^2.0.1", - "uuid": "^11.1.0", - "zod": "^3.25.76" - }, - "devDependencies": { - "@eslint/js": "^9.21.0", - "@playwright/test": "^1.57.0", - "@types/node": "^25.0.9", - "@types/react": "^19.2.8", - "@types/react-dom": "^19.0.4", - "eslint": "^9.28.0", - "eslint-config-next": "^16.1.3", - "eslint-plugin-react-hooks": "^7.0.1", - "globals": "^16.0.0", - "stylelint": "^17.0.0", - "stylelint-config-standard-scss": "^17.0.0", - "stylelint-order": "^7.0.1", - "stylelint-scss": "^7.0.0", - "typescript": "~5.9.3", - "typescript-eslint": "^8.53.1" - }, - "workspaces": { - "packages": [ - "packages/*" - ] - } + "name": "codesnippet", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@monaco-editor/react": "^4.6.0", + "@phosphor-icons/react": "^2.1.7", + "@reduxjs/toolkit": "^2.5.0", + "dexie": "^4.0.10", + "framer-motion": "^11.15.0", + "monaco-editor": "^0.52.2", + "next": "15.1.3", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-redux": "^9.2.0", + "sonner": "^1.7.3" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^8", + "eslint-config-next": "15.1.3", + "sass": "^1.70.0", + "typescript": "^5" + } } diff --git a/postcss.config.mjs b/postcss.config.mjs new file mode 100644 index 0000000..b6dc034 --- /dev/null +++ b/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + autoprefixer: {}, + }, +} diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..a9b2e8b --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,62 @@ +@import '../styles/theme.scss'; + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, +body { + height: 100%; + font-family: var(--mat-sys-body-large-font); + background-color: var(--mat-sys-background); + color: var(--mat-sys-on-background); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + color: var(--mat-sys-primary); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +code { + font-family: 'JetBrains Mono', monospace; + background-color: var(--mat-sys-surface-variant); + padding: 0.125rem 0.25rem; + border-radius: 0.25rem; + font-size: 0.875em; +} + +pre { + font-family: 'JetBrains Mono', monospace; + background-color: var(--mat-sys-surface-variant); + padding: 1rem; + border-radius: 0.5rem; + overflow-x: auto; +} + +pre code { + background-color: transparent; + padding: 0; +} + +button { + font-family: inherit; +} + +input, +textarea, +select { + font-family: inherit; +} + +:focus-visible { + outline: 2px solid var(--mat-sys-primary); + outline-offset: 2px; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9348095..c7e4683 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,29 +1,42 @@ -import type { Metadata } from 'next'; -import './globals.scss'; -import { Providers } from './providers'; +import type { Metadata } from 'next' +import { Inter, Bricolage_Grotesque, JetBrains_Mono } from 'next/font/google' +import '@/styles/theme.scss' +import '@/app/globals.css' +import { Providers } from '@/components/providers/Providers' + +const inter = Inter({ + subsets: ['latin'], + variable: '--font-inter', + display: 'swap', +}) + +const bricolage = Bricolage_Grotesque({ + subsets: ['latin'], + variable: '--font-bricolage', + display: 'swap', +}) + +const jetbrainsMono = JetBrains_Mono({ + subsets: ['latin'], + variable: '--font-jetbrains', + display: 'swap', +}) export const metadata: Metadata = { - title: 'CodeSnippet - Share & Run Code (Python, React & More)', - description: 'Save, organize, and share your code snippets with beautiful syntax highlighting and live execution', -}; + title: 'CodeSnippet - Material Design 3', + description: 'A modern code snippet manager built with Material Design 3', +} export default function RootLayout({ children, -}: Readonly<{ - children: React.ReactNode; -}>) { +}: { + children: React.ReactNode +}) { return ( - - - - - - + - - {children} - + {children} - ); + ) } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index eda264a..f74c5aa 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,5 +1,47 @@ -import { clsx, type ClassValue } from "clsx" - -export function cn(...inputs: ClassValue[]) { - return clsx(inputs) +/** + * Utility function to combine class names + * Replaces tailwind-merge and clsx with a simple implementation + */ +export function cn(...classes: (string | undefined | null | false)[]): string { + return classes.filter(Boolean).join(' ') +} + +/** + * Format bytes to human readable string + */ +export function formatBytes(bytes: number): string { + if (bytes === 0) return '0 Bytes' + const k = 1024 + const sizes = ['Bytes', 'KB', 'MB', 'GB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i] +} + +/** + * Debounce function + */ +export function debounce any>( + func: T, + wait: number +): (...args: Parameters) => void { + let timeout: NodeJS.Timeout | null = null + + return function executedFunction(...args: Parameters) { + const later = () => { + timeout = null + func(...args) + } + + if (timeout) { + clearTimeout(timeout) + } + timeout = setTimeout(later, wait) + } +} + +/** + * Sleep utility + */ +export function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) } diff --git a/src/styles/theme.scss b/src/styles/theme.scss new file mode 100644 index 0000000..c99945d --- /dev/null +++ b/src/styles/theme.scss @@ -0,0 +1,307 @@ +@use 'm3-scss/material' as mat; + +// Define your M3 theme with violet palette +$theme: mat.define-theme(( + color: ( + theme-type: light, + primary: mat.$violet-palette, + tertiary: mat.$blue-palette, + ), + typography: ( + plain-family: (Inter, sans-serif), + brand-family: ('Bricolage Grotesque', sans-serif), + ), + density: 0, +)); + +// Include all M3 component themes +@include mat.all-component-themes($theme); +@include mat.system-classes(); +@include mat.typography-hierarchy($theme); + +// Dark theme +.dark { + $dark-theme: mat.define-theme(( + color: ( + theme-type: dark, + primary: mat.$violet-palette, + tertiary: mat.$blue-palette, + ), + typography: ( + plain-family: (Inter, sans-serif), + brand-family: ('Bricolage Grotesque', sans-serif), + ), + density: 0, + )); + + @include mat.all-component-colors($dark-theme); +} + +// Custom utility classes for common patterns +.flex { + display: flex; +} + +.flex-col { + flex-direction: column; +} + +.items-center { + align-items: center; +} + +.justify-between { + justify-content: space-between; +} + +.gap-2 { + gap: 0.5rem; +} + +.gap-4 { + gap: 1rem; +} + +.gap-6 { + gap: 1.5rem; +} + +.p-4 { + padding: 1rem; +} + +.p-6 { + padding: 1.5rem; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.border { + border: 1px solid var(--mat-sys-outline); +} + +.shadow-sm { + box-shadow: var(--mat-sys-level1); +} + +.shadow-lg { + box-shadow: var(--mat-sys-level3); +} + +.text-sm { + font-size: 0.875rem; +} + +.text-base { + font-size: 1rem; +} + +.text-lg { + font-size: 1.125rem; +} + +.text-xl { + font-size: 1.25rem; +} + +.text-2xl { + font-size: 1.5rem; +} + +.text-3xl { + font-size: 1.875rem; +} + +.font-medium { + font-weight: 500; +} + +.font-semibold { + font-weight: 600; +} + +.font-bold { + font-weight: 700; +} + +.space-y-2 > * + * { + margin-top: 0.5rem; +} + +.space-y-4 > * + * { + margin-top: 1rem; +} + +.space-y-6 > * + * { + margin-top: 1.5rem; +} + +.w-full { + width: 100%; +} + +.h-full { + height: 100%; +} + +.max-w-md { + max-width: 28rem; +} + +.max-w-2xl { + max-width: 42rem; +} + +.grid { + display: grid; +} + +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + +.grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +@media (min-width: 768px) { + .md\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .md\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +.overflow-hidden { + overflow: hidden; +} + +.overflow-auto { + overflow: auto; +} + +.relative { + position: relative; +} + +.absolute { + position: absolute; +} + +.cursor-pointer { + cursor: pointer; +} + +.transition-colors { + transition-property: color, background-color, border-color; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.hover\:bg-muted:hover { + background-color: var(--mat-sys-surface-variant); +} + +.text-muted-foreground { + color: var(--mat-sys-on-surface-variant); +} + +.text-foreground { + color: var(--mat-sys-on-surface); +} + +.text-primary { + color: var(--mat-sys-primary); +} + +.text-destructive { + color: var(--mat-sys-error); +} + +.bg-card { + background-color: var(--mat-sys-surface); +} + +.bg-background { + background-color: var(--mat-sys-background); +} + +.bg-muted { + background-color: var(--mat-sys-surface-variant); +} + +.bg-primary { + background-color: var(--mat-sys-primary); +} + +.bg-secondary { + background-color: var(--mat-sys-secondary); +} + +.bg-accent { + background-color: var(--mat-sys-tertiary); +} + +.bg-destructive { + background-color: var(--mat-sys-error); +} + +.text-primary-foreground { + color: var(--mat-sys-on-primary); +} + +.text-secondary-foreground { + color: var(--mat-sys-on-secondary); +} + +.text-accent-foreground { + color: var(--mat-sys-on-tertiary); +} + +.text-destructive-foreground { + color: var(--mat-sys-on-error); +} + +.border-border { + border-color: var(--mat-sys-outline); +} + +.min-h-screen { + min-height: 100vh; +} + +.flex-1 { + flex: 1 1 0%; +} + +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.line-clamp-1 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; +} + +.line-clamp-2 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +}