mirror of
https://github.com/johndoe6345789/snippet-pastebin.git
synced 2026-04-24 13:34:55 +00:00
Merge pull request #20 from johndoe6345789/copilot/fix-build-chunk-load-error
[WIP] Fix fatal chunk load error in server bundle generation
This commit is contained in:
@@ -5,8 +5,54 @@ const nextConfig = {
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
typescript: {
|
||||
// TypeScript incorrectly flags CSS imports as errors in Next.js
|
||||
// This is a known issue: https://github.com/vercel/next.js/issues/54282
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
experimental: {
|
||||
optimizePackageImports: ['@radix-ui/react-icons', '@phosphor-icons/react'],
|
||||
turbopackScopeHoisting: false,
|
||||
},
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
4013
package-lock.json
generated
4013
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "spark-template",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
@@ -11,11 +12,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/standalone": "^7.28.6",
|
||||
"@github/spark": ">=0.43.1 <1",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@hookform/resolvers": "^4.1.3",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@octokit/core": "^6.1.4",
|
||||
"@phosphor-icons/react": "^2.1.7",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@radix-ui/react-accordion": "^1.2.3",
|
||||
@@ -45,51 +43,37 @@
|
||||
"@radix-ui/react-toggle-group": "^1.1.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@reduxjs/toolkit": "^2.11.2",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tanstack/react-query": "^5.83.1",
|
||||
"@types/sql.js": "^1.4.9",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"d3": "^7.9.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"embla-carousel-react": "^8.5.2",
|
||||
"framer-motion": "^12.6.2",
|
||||
"input-otp": "^1.4.2",
|
||||
"lucide-react": "^0.484.0",
|
||||
"marked": "^15.0.7",
|
||||
"next": "16.1.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"octokit": "^4.1.2",
|
||||
"pyodide": "^0.29.1",
|
||||
"react": "^19.0.0",
|
||||
"react-day-picker": "^9.6.7",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"react-router-dom": "^7.12.0",
|
||||
"recharts": "^2.15.1",
|
||||
"sass": "^1.97.2",
|
||||
"sonner": "^2.0.1",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"three": "^0.175.0",
|
||||
"tw-animate-css": "^1.2.4",
|
||||
"uuid": "^11.1.0",
|
||||
"vaul": "^1.1.2",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.21.0",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"@types/node": "^25.0.9",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"autoprefixer": "^10.4.23",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint-config-next": "^16.1.3",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"globals": "^16.0.0",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"typescript": "~5.7.2",
|
||||
"typescript-eslint": "^8.38.0"
|
||||
},
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
}
|
||||
@@ -1,14 +1,20 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { motion } from 'framer-motion';
|
||||
import { SplitScreenEditor } from '@/components/features/snippet-editor/SplitScreenEditor';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Sparkle } from '@phosphor-icons/react';
|
||||
import { DEMO_CODE } from '@/components/demo/demo-constants';
|
||||
import { DemoFeatureCards } from '@/components/demo/DemoFeatureCards';
|
||||
import { PageLayout } from '../PageLayout';
|
||||
|
||||
// Dynamically import SplitScreenEditor to avoid SSR issues with Pyodide
|
||||
const SplitScreenEditor = dynamic(
|
||||
() => import('@/components/features/snippet-editor/SplitScreenEditor').then(mod => ({ default: mod.SplitScreenEditor })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function DemoPage() {
|
||||
const [code, setCode] = useState(DEMO_CODE);
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
:root {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 263 70% 50%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 195 100% 70%;
|
||||
--accent-foreground: 222.2 84% 4.9%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 195 100% 70%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
@theme {
|
||||
--color-background: hsl(var(--background));
|
||||
--color-foreground: hsl(var(--foreground));
|
||||
--color-card: hsl(var(--card));
|
||||
--color-card-foreground: hsl(var(--card-foreground));
|
||||
--color-popover: hsl(var(--popover));
|
||||
--color-popover-foreground: hsl(var(--popover-foreground));
|
||||
--color-primary: hsl(var(--primary));
|
||||
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||
--color-secondary: hsl(var(--secondary));
|
||||
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
||||
--color-muted: hsl(var(--muted));
|
||||
--color-muted-foreground: hsl(var(--muted-foreground));
|
||||
--color-accent: hsl(var(--accent));
|
||||
--color-accent-foreground: hsl(var(--accent-foreground));
|
||||
--color-destructive: hsl(var(--destructive));
|
||||
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
||||
--color-border: hsl(var(--border));
|
||||
--color-input: hsl(var(--input));
|
||||
--color-ring: hsl(var(--ring));
|
||||
}
|
||||
|
||||
* {
|
||||
border-color: hsl(var(--border));
|
||||
}
|
||||
|
||||
body {
|
||||
background: hsl(var(--background));
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-inter), 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-inter), 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
code, pre {
|
||||
font-family: var(--font-jetbrains-mono), 'JetBrains Mono', monospace;
|
||||
}
|
||||
49
src/app/globals.scss
Normal file
49
src/app/globals.scss
Normal file
@@ -0,0 +1,49 @@
|
||||
@import '../styles/abstracts';
|
||||
@import '../styles/utilities/utilities';
|
||||
@import './theme.scss';
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
border-color: $border;
|
||||
}
|
||||
|
||||
body {
|
||||
background: $background;
|
||||
color: $foreground;
|
||||
font-family: var(--font-inter), 'Inter', sans-serif;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-inter), 'Inter', sans-serif;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
code, pre {
|
||||
font-family: var(--font-jetbrains-mono), 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 263 70% 50%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 195 100% 70%;
|
||||
--accent-foreground: 222.2 84% 4.9%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 195 100% 70%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Metadata } from 'next';
|
||||
import './globals.css';
|
||||
import './globals.scss';
|
||||
import { Providers } from './providers';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { SnippetManagerRedux } from '@/components/SnippetManagerRedux';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { PageLayout } from './PageLayout';
|
||||
|
||||
// Dynamically import SnippetManagerRedux to avoid SSR issues with Pyodide
|
||||
const SnippetManagerRedux = dynamic(
|
||||
() => import('@/components/SnippetManagerRedux').then(mod => ({ default: mod.SnippetManagerRedux })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<PageLayout>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import '@github/spark/spark';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Toaster } from '@/components/ui/sonner';
|
||||
|
||||
@@ -8,6 +8,7 @@ import { StorageBackendCard } from '@/components/settings/StorageBackendCard';
|
||||
import { DatabaseStatsCard } from '@/components/settings/DatabaseStatsCard';
|
||||
import { StorageInfoCard } from '@/components/settings/StorageInfoCard';
|
||||
import { DatabaseActionsCard } from '@/components/settings/DatabaseActionsCard';
|
||||
import { OpenAISettingsCard } from '@/components/settings/OpenAISettingsCard';
|
||||
import { useSettingsState } from '@/hooks/useSettingsState';
|
||||
import { PageLayout } from '../PageLayout';
|
||||
|
||||
@@ -50,6 +51,8 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 max-w-3xl">
|
||||
<OpenAISettingsCard />
|
||||
|
||||
<PersistenceSettings />
|
||||
|
||||
<SchemaHealthCard
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@import '@radix-ui/colors/sage-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/olive.css' layer(base);
|
||||
@import '@radix-ui/colors/olive-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/sand.css' layer(base);
|
||||
@import '@radix-ui/colors/sand-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/red.css' layer(base);
|
||||
@import '@radix-ui/colors/red-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/ruby.css' layer(base);
|
||||
@import '@radix-ui/colors/ruby-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/crimson.css' layer(base);
|
||||
@import '@radix-ui/colors/crimson-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/pink.css' layer(base);
|
||||
@import '@radix-ui/colors/pink-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/plum.css' layer(base);
|
||||
@import '@radix-ui/colors/plum-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/purple.css' layer(base);
|
||||
@import '@radix-ui/colors/purple-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/violet.css' layer(base);
|
||||
@import '@radix-ui/colors/violet-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/iris.css' layer(base);
|
||||
@import '@radix-ui/colors/iris-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/indigo.css' layer(base);
|
||||
@import '@radix-ui/colors/indigo-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/blue.css' layer(base);
|
||||
@import '@radix-ui/colors/blue-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/cyan.css' layer(base);
|
||||
@import '@radix-ui/colors/cyan-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/teal.css' layer(base);
|
||||
@import '@radix-ui/colors/teal-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/jade.css' layer(base);
|
||||
@import '@radix-ui/colors/jade-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/green.css' layer(base);
|
||||
@import '@radix-ui/colors/green-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/grass.css' layer(base);
|
||||
@import '@radix-ui/colors/grass-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/bronze.css' layer(base);
|
||||
@import '@radix-ui/colors/bronze-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/gold.css' layer(base);
|
||||
@import '@radix-ui/colors/gold-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/brown.css' layer(base);
|
||||
@import '@radix-ui/colors/brown-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/orange.css' layer(base);
|
||||
@import '@radix-ui/colors/orange-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/amber.css' layer(base);
|
||||
@import '@radix-ui/colors/amber-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/yellow.css' layer(base);
|
||||
@import '@radix-ui/colors/yellow-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/lime.css' layer(base);
|
||||
@import '@radix-ui/colors/lime-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/mint.css' layer(base);
|
||||
@import '@radix-ui/colors/mint-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/sky.css' layer(base);
|
||||
@import '@radix-ui/colors/sky-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/tomato.css' layer(base);
|
||||
@import '@radix-ui/colors/tomato-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/gray.css' layer(base);
|
||||
@import '@radix-ui/colors/gray-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/mauve.css' layer(base);
|
||||
@import '@radix-ui/colors/mauve-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/slate.css' layer(base);
|
||||
@import '@radix-ui/colors/slate-dark.css' layer(base);
|
||||
@import '@radix-ui/colors/slate-alpha.css' layer(base);
|
||||
@import '@radix-ui/colors/slate-dark-alpha.css' layer(base);
|
||||
|
||||
@import 'tailwindcss/theme' layer(theme);
|
||||
|
||||
@import 'tailwindcss/preflight' layer(base);
|
||||
|
||||
/*
|
||||
The default border color has changed to `currentColor` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
#spark-app {
|
||||
--tomato-contrast: #fff;
|
||||
--red-contrast: #fff;
|
||||
--ruby-contrast: #fff;
|
||||
--crimson-contrast: #fff;
|
||||
--pink-contrast: #fff;
|
||||
--plum-contrast: #fff;
|
||||
--purple-contrast: #fff;
|
||||
--violet-contrast: #fff;
|
||||
--iris-contrast: #fff;
|
||||
--indigo-contrast: #fff;
|
||||
--blue-contrast: #fff;
|
||||
--cyan-contrast: #fff;
|
||||
--teal-contrast: #fff;
|
||||
--jade-contrast: #fff;
|
||||
--green-contrast: #fff;
|
||||
--grass-contrast: #fff;
|
||||
--bronze-contrast: #fff;
|
||||
--gold-contrast: #fff;
|
||||
--brown-contrast: #fff;
|
||||
--orange-contrast: #fff;
|
||||
--amber-contrast: #000;
|
||||
--yellow-contrast: #000;
|
||||
--lime-contrast: #000;
|
||||
--mint-contrast: #000;
|
||||
--sky-contrast: #000;
|
||||
--gray-contrast: #fff;
|
||||
--mauve-contrast: #fff;
|
||||
--slate-contrast: #fff;
|
||||
--sage-contrast: #fff;
|
||||
--olive-contrast: #fff;
|
||||
--sand-contrast: #fff;
|
||||
|
||||
/**
|
||||
* Spacing scale
|
||||
*
|
||||
* These variables define a spacing scale based on Tailwind's default.
|
||||
* We've introduced a --size-scale variable as a multiplier.
|
||||
* By adjusting this single value, we can proportionally
|
||||
* scale all spacing throughout the entire application.
|
||||
*
|
||||
* https://tailwindcss.com/docs/customizing-spacing#default-spacing-scale
|
||||
*/
|
||||
--size-scale: 1;
|
||||
--size-0: 0px;
|
||||
--size-px: 1px;
|
||||
--size-0-5: calc(0.125rem * var(--size-scale));
|
||||
--size-1: calc(0.25rem * var(--size-scale));
|
||||
--size-1-5: calc(0.375rem * var(--size-scale));
|
||||
--size-2: calc(0.5rem * var(--size-scale));
|
||||
--size-2-5: calc(0.625rem * var(--size-scale));
|
||||
--size-3: calc(0.75rem * var(--size-scale));
|
||||
--size-3-5: calc(0.875rem * var(--size-scale));
|
||||
--size-4: calc(1rem * var(--size-scale));
|
||||
--size-5: calc(1.25rem * var(--size-scale));
|
||||
--size-6: calc(1.5rem * var(--size-scale));
|
||||
--size-7: calc(1.75rem * var(--size-scale));
|
||||
--size-8: calc(2rem * var(--size-scale));
|
||||
--size-9: calc(2.25rem * var(--size-scale));
|
||||
--size-10: calc(2.5rem * var(--size-scale));
|
||||
--size-11: calc(2.75rem * var(--size-scale));
|
||||
--size-12: calc(3rem * var(--size-scale));
|
||||
--size-14: calc(3.5rem * var(--size-scale));
|
||||
--size-16: calc(4rem * var(--size-scale));
|
||||
--size-20: calc(5rem * var(--size-scale));
|
||||
--size-24: calc(6rem * var(--size-scale));
|
||||
--size-28: calc(7rem * var(--size-scale));
|
||||
--size-32: calc(8rem * var(--size-scale));
|
||||
--size-36: calc(9rem * var(--size-scale));
|
||||
--size-40: calc(10rem * var(--size-scale));
|
||||
--size-44: calc(11rem * var(--size-scale));
|
||||
--size-48: calc(12rem * var(--size-scale));
|
||||
--size-52: calc(13rem * var(--size-scale));
|
||||
--size-56: calc(14rem * var(--size-scale));
|
||||
--size-60: calc(15rem * var(--size-scale));
|
||||
--size-64: calc(16rem * var(--size-scale));
|
||||
--size-72: calc(18rem * var(--size-scale));
|
||||
--size-80: calc(20rem * var(--size-scale));
|
||||
--size-96: calc(24rem * var(--size-scale));
|
||||
|
||||
/* Border radii */
|
||||
--radius-factor: 1;
|
||||
--radius-sm: calc(2px * var(--radius-factor) * var(--size-scale));
|
||||
--radius-md: calc(6px * var(--radius-factor) * var(--size-scale));
|
||||
--radius-lg: calc(8px * var(--radius-factor) * var(--size-scale));
|
||||
--radius-xl: calc(12px * var(--radius-factor) * var(--size-scale));
|
||||
--radius-2xl: calc(16px * var(--radius-factor) * var(--size-scale));
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Neutral colors */
|
||||
--color-neutral-1: var(--slate-1);
|
||||
--color-neutral-2: var(--slate-2);
|
||||
--color-neutral-3: var(--slate-3);
|
||||
--color-neutral-4: var(--slate-4);
|
||||
--color-neutral-5: var(--slate-5);
|
||||
--color-neutral-6: var(--slate-6);
|
||||
--color-neutral-7: var(--slate-7);
|
||||
--color-neutral-8: var(--slate-8);
|
||||
--color-neutral-9: var(--slate-9);
|
||||
--color-neutral-10: var(--slate-10);
|
||||
--color-neutral-11: var(--slate-11);
|
||||
--color-neutral-12: var(--slate-12);
|
||||
--color-neutral-a1: var(--slate-a1);
|
||||
--color-neutral-a2: var(--slate-a2);
|
||||
--color-neutral-a3: var(--slate-a3);
|
||||
--color-neutral-a4: var(--slate-a4);
|
||||
--color-neutral-a5: var(--slate-a5);
|
||||
--color-neutral-a6: var(--slate-a6);
|
||||
--color-neutral-a7: var(--slate-a7);
|
||||
--color-neutral-a8: var(--slate-a8);
|
||||
--color-neutral-a9: var(--slate-a9);
|
||||
--color-neutral-a10: var(--slate-a10);
|
||||
--color-neutral-a11: var(--slate-a11);
|
||||
--color-neutral-a12: var(--slate-a12);
|
||||
--color-neutral-contrast: var(--slate-contrast);
|
||||
|
||||
/* Accent colors */
|
||||
--color-accent-1: var(--blue-1);
|
||||
--color-accent-2: var(--blue-2);
|
||||
--color-accent-3: var(--blue-3);
|
||||
--color-accent-4: var(--blue-4);
|
||||
--color-accent-5: var(--blue-5);
|
||||
--color-accent-6: var(--blue-6);
|
||||
--color-accent-7: var(--blue-7);
|
||||
--color-accent-8: var(--blue-8);
|
||||
--color-accent-9: var(--blue-9);
|
||||
--color-accent-10: var(--blue-10);
|
||||
--color-accent-11: var(--blue-11);
|
||||
--color-accent-12: var(--blue-12);
|
||||
--color-accent-contrast: var(--blue-contrast);
|
||||
|
||||
/* Secondary accent colors */
|
||||
--color-accent-secondary-1: var(--violet-1);
|
||||
--color-accent-secondary-2: var(--violet-2);
|
||||
--color-accent-secondary-3: var(--violet-3);
|
||||
--color-accent-secondary-4: var(--violet-4);
|
||||
--color-accent-secondary-5: var(--violet-5);
|
||||
--color-accent-secondary-6: var(--violet-6);
|
||||
--color-accent-secondary-7: var(--violet-7);
|
||||
--color-accent-secondary-8: var(--violet-8);
|
||||
--color-accent-secondary-9: var(--violet-9);
|
||||
--color-accent-secondary-10: var(--violet-10);
|
||||
--color-accent-secondary-11: var(--violet-11);
|
||||
--color-accent-secondary-12: var(--violet-12);
|
||||
--color-accent-secondary-contrast: var(--violet-contrast);
|
||||
|
||||
/* Foreground colors */
|
||||
--color-fg: var(--color-neutral-12);
|
||||
--color-fg-secondary: var(--color-neutral-a11);
|
||||
|
||||
/* Background colors */
|
||||
--color-bg: #ffffff;
|
||||
--color-bg-inset: var(--color-neutral-2);
|
||||
--color-bg-overlay: #ffffff;
|
||||
|
||||
/* Focus ring */
|
||||
--color-focus-ring: var(--color-accent-9);
|
||||
|
||||
/* Fonts */
|
||||
--font-sans-serif: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
|
||||
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-monospace: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
"Liberation Mono", "Courier New", monospace;
|
||||
--font-family: var(--font-sans-serif);
|
||||
}
|
||||
|
||||
#spark-app.dark-theme {
|
||||
--color-bg: var(--color-neutral-1);
|
||||
--color-bg-inset: #000000;
|
||||
--color-bg-overlay: var(--color-neutral-3);
|
||||
}
|
||||
}
|
||||
75
src/app/theme.scss
Normal file
75
src/app/theme.scss
Normal file
@@ -0,0 +1,75 @@
|
||||
// Radix UI Colors imports (keeping the color system)
|
||||
@import '@radix-ui/colors/sage-dark.css';
|
||||
@import '@radix-ui/colors/slate.css';
|
||||
@import '@radix-ui/colors/slate-dark.css';
|
||||
@import '@radix-ui/colors/blue.css';
|
||||
@import '@radix-ui/colors/blue-dark.css';
|
||||
@import '@radix-ui/colors/violet.css';
|
||||
@import '@radix-ui/colors/violet-dark.css';
|
||||
|
||||
// Spacing scale variables
|
||||
$size-scale: 1;
|
||||
$size-0: 0px;
|
||||
$size-px: 1px;
|
||||
$size-0-5: calc(0.125rem * $size-scale);
|
||||
$size-1: calc(0.25rem * $size-scale);
|
||||
$size-2: calc(0.5rem * $size-scale);
|
||||
$size-3: calc(0.75rem * $size-scale);
|
||||
$size-4: calc(1rem * $size-scale);
|
||||
$size-5: calc(1.25rem * $size-scale);
|
||||
$size-6: calc(1.5rem * $size-scale);
|
||||
$size-8: calc(2rem * $size-scale);
|
||||
$size-10: calc(2.5rem * $size-scale);
|
||||
$size-12: calc(3rem * $size-scale);
|
||||
$size-16: calc(4rem * $size-scale);
|
||||
$size-20: calc(5rem * $size-scale);
|
||||
$size-24: calc(6rem * $size-scale);
|
||||
|
||||
// Border radii
|
||||
$radius-factor: 1;
|
||||
$radius-sm: calc(2px * $radius-factor * $size-scale);
|
||||
$radius-md: calc(6px * $radius-factor * $size-scale);
|
||||
$radius-lg: calc(8px * $radius-factor * $size-scale);
|
||||
$radius-xl: calc(12px * $radius-factor * $size-scale);
|
||||
$radius-full: 9999px;
|
||||
|
||||
// App-level CSS variables
|
||||
#spark-app {
|
||||
--size-scale: #{$size-scale};
|
||||
--radius-factor: #{$radius-factor};
|
||||
|
||||
// Neutral colors (using slate from Radix)
|
||||
--color-neutral-1: var(--slate-1);
|
||||
--color-neutral-2: var(--slate-2);
|
||||
--color-neutral-3: var(--slate-3);
|
||||
--color-neutral-11: var(--slate-11);
|
||||
--color-neutral-12: var(--slate-12);
|
||||
|
||||
// Accent colors (using blue from Radix)
|
||||
--color-accent-9: var(--blue-9);
|
||||
--color-accent-11: var(--blue-11);
|
||||
|
||||
// Foreground colors
|
||||
--color-fg: var(--color-neutral-12);
|
||||
--color-fg-secondary: var(--color-neutral-11);
|
||||
|
||||
// Background colors
|
||||
--color-bg: #ffffff;
|
||||
--color-bg-inset: var(--color-neutral-2);
|
||||
--color-bg-overlay: #ffffff;
|
||||
|
||||
// Focus ring
|
||||
--color-focus-ring: var(--color-accent-9);
|
||||
|
||||
// Fonts
|
||||
--font-sans-serif: ui-sans-serif, system-ui, sans-serif;
|
||||
--font-serif: ui-serif, Georgia, serif;
|
||||
--font-monospace: ui-monospace, 'SF Mono', Monaco, Consolas, monospace;
|
||||
--font-family: var(--font-sans-serif);
|
||||
}
|
||||
|
||||
#spark-app.dark-theme {
|
||||
--color-bg: var(--color-neutral-1);
|
||||
--color-bg-inset: #000000;
|
||||
--color-bg-overlay: var(--color-neutral-3);
|
||||
}
|
||||
@@ -3,10 +3,37 @@ export async function analyzeErrorWithAI(
|
||||
errorStack?: string,
|
||||
context?: string
|
||||
): Promise<string> {
|
||||
const contextInfo = context ? `\n\nContext: ${context}` : ''
|
||||
const stackInfo = errorStack ? `\n\nStack trace: ${errorStack}` : ''
|
||||
// Check if OpenAI API key is configured
|
||||
const apiKey = localStorage.getItem('openai_api_key');
|
||||
|
||||
const prompt = (window.spark.llmPrompt as any)`You are a helpful debugging assistant for a code snippet manager app. Analyze this error and provide:
|
||||
if (!apiKey) {
|
||||
// Fallback to simple error analysis if no API key
|
||||
const lines = ['## Error Analysis\n'];
|
||||
|
||||
lines.push('**Error Message:**');
|
||||
lines.push(`\`${errorMessage}\`\n`);
|
||||
|
||||
if (context) {
|
||||
lines.push('**Context:**');
|
||||
lines.push(`${context}\n`);
|
||||
}
|
||||
|
||||
lines.push('**Note:** Configure your OpenAI API key in Settings to enable AI-powered error analysis.\n');
|
||||
|
||||
lines.push('**Basic Troubleshooting:**');
|
||||
lines.push('1. Check the browser console for more details');
|
||||
lines.push('2. Try refreshing the page');
|
||||
lines.push('3. Clear your browser cache and local storage');
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// Use OpenAI API for advanced error analysis
|
||||
try {
|
||||
const contextInfo = context ? `\n\nContext: ${context}` : '';
|
||||
const stackInfo = errorStack ? `\n\nStack trace: ${errorStack}` : '';
|
||||
|
||||
const prompt = `You are a helpful debugging assistant for a code snippet manager app. Analyze this error and provide:
|
||||
|
||||
1. A clear explanation of what went wrong (in plain language)
|
||||
2. Why this error likely occurred
|
||||
@@ -14,8 +41,51 @@ export async function analyzeErrorWithAI(
|
||||
|
||||
Error message: ${errorMessage}${contextInfo}${stackInfo}
|
||||
|
||||
Keep your response concise, friendly, and focused on practical solutions. Format your response with clear sections using markdown.`
|
||||
Keep your response concise, friendly, and focused on practical solutions. Format your response with clear sections using markdown.`;
|
||||
|
||||
const result = await window.spark.llm(prompt, 'gpt-4o-mini')
|
||||
return result
|
||||
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'gpt-4o-mini',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'You are a helpful debugging assistant for a code snippet manager app.',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: prompt,
|
||||
},
|
||||
],
|
||||
temperature: 0.7,
|
||||
max_tokens: 500,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to analyze error with AI. Please check your API key.');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.choices[0]?.message?.content || 'Unable to analyze error.';
|
||||
} catch (err) {
|
||||
console.error('Error calling OpenAI API:', err);
|
||||
|
||||
// Fallback to simple analysis if API call fails
|
||||
return `## Error Analysis
|
||||
|
||||
**Error Message:**
|
||||
\`${errorMessage}\`
|
||||
|
||||
**Note:** Failed to get AI analysis. ${err instanceof Error ? err.message : 'Unknown error'}
|
||||
|
||||
**Basic Troubleshooting:**
|
||||
1. Check the browser console for more details
|
||||
2. Try refreshing the page
|
||||
3. Verify your OpenAI API key in Settings`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,15 @@ import {
|
||||
} from '@/components/ui/dialog'
|
||||
import { Snippet } from '@/lib/types'
|
||||
import { useState } from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { appConfig } from '@/lib/config'
|
||||
import { SnippetViewerHeader } from './SnippetViewerHeader'
|
||||
import { SnippetViewerContent } from './SnippetViewerContent'
|
||||
|
||||
// Dynamically import SnippetViewerContent to avoid SSR issues with Pyodide
|
||||
const SnippetViewerContent = dynamic(
|
||||
() => import('./SnippetViewerContent').then(mod => ({ default: mod.SnippetViewerContent })),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
interface SnippetViewerProps {
|
||||
snippet: Snippet | null
|
||||
|
||||
107
src/components/settings/OpenAISettingsCard.tsx
Normal file
107
src/components/settings/OpenAISettingsCard.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Eye, EyeClosed, Key } from '@phosphor-icons/react';
|
||||
|
||||
export function OpenAISettingsCard() {
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
const [showKey, setShowKey] = useState(false);
|
||||
const [saved, setSaved] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Load the API key from localStorage on mount
|
||||
const storedKey = localStorage.getItem('openai_api_key');
|
||||
if (storedKey) {
|
||||
setApiKey(storedKey);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleSave = () => {
|
||||
if (apiKey.trim()) {
|
||||
localStorage.setItem('openai_api_key', apiKey.trim());
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 2000);
|
||||
} else {
|
||||
localStorage.removeItem('openai_api_key');
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setApiKey('');
|
||||
localStorage.removeItem('openai_api_key');
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Key className="h-5 w-5 text-primary" weight="duotone" />
|
||||
<CardTitle>OpenAI API Settings</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Configure your OpenAI API key for AI-powered error analysis. Your key is stored locally in your browser.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="openai-key">OpenAI API Key</Label>
|
||||
<div className="flex gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Input
|
||||
id="openai-key"
|
||||
type={showKey ? 'text' : 'password'}
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
placeholder="sk-..."
|
||||
className="pr-10"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowKey(!showKey)}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{showKey ? <EyeClosed size={18} /> : <Eye size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Get your API key from{' '}
|
||||
<a
|
||||
href="https://platform.openai.com/api-keys"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
OpenAI Platform
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={handleSave} disabled={!apiKey.trim()}>
|
||||
{saved ? 'Saved!' : 'Save API Key'}
|
||||
</Button>
|
||||
{apiKey && (
|
||||
<Button onClick={handleClear} variant="outline">
|
||||
Clear
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{apiKey && (
|
||||
<div className="text-sm text-muted-foreground bg-muted/50 p-3 rounded-md">
|
||||
✓ API key is configured. Error analysis will use OpenAI GPT-4o-mini.
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
return clsx(inputs)
|
||||
}
|
||||
|
||||
24
src/styles/abstracts/_functions.scss
Normal file
24
src/styles/abstracts/_functions.scss
Normal file
@@ -0,0 +1,24 @@
|
||||
// Spacing function
|
||||
@function spacing($key) {
|
||||
@return map-get($spacing, $key);
|
||||
}
|
||||
|
||||
// Border radius function
|
||||
@function radius($key) {
|
||||
@return map-get($radius, $key);
|
||||
}
|
||||
|
||||
// Font size function
|
||||
@function font-size($key) {
|
||||
@return map-get($font-sizes, $key);
|
||||
}
|
||||
|
||||
// Font weight function
|
||||
@function font-weight($key) {
|
||||
@return map-get($font-weights, $key);
|
||||
}
|
||||
|
||||
// Breakpoint function
|
||||
@function breakpoint($key) {
|
||||
@return map-get($breakpoints, $key);
|
||||
}
|
||||
3
src/styles/abstracts/_index.scss
Normal file
3
src/styles/abstracts/_index.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
@import './variables';
|
||||
@import './functions';
|
||||
@import './mixins';
|
||||
212
src/styles/abstracts/_mixins.scss
Normal file
212
src/styles/abstracts/_mixins.scss
Normal file
@@ -0,0 +1,212 @@
|
||||
// Flexbox mixins
|
||||
@mixin flex($direction: row, $justify: flex-start, $align: stretch, $wrap: nowrap) {
|
||||
display: flex;
|
||||
flex-direction: $direction;
|
||||
justify-content: $justify;
|
||||
align-items: $align;
|
||||
flex-wrap: $wrap;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
@include flex(row, center, center);
|
||||
}
|
||||
|
||||
@mixin flex-between {
|
||||
@include flex(row, space-between, center);
|
||||
}
|
||||
|
||||
@mixin flex-col {
|
||||
@include flex(column, flex-start, stretch);
|
||||
}
|
||||
|
||||
// Grid mixin
|
||||
@mixin grid($columns: 1, $gap: spacing(4)) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat($columns, 1fr);
|
||||
gap: $gap;
|
||||
}
|
||||
|
||||
// Spacing mixins
|
||||
@mixin p($value) {
|
||||
padding: spacing($value);
|
||||
}
|
||||
|
||||
@mixin px($value) {
|
||||
padding-left: spacing($value);
|
||||
padding-right: spacing($value);
|
||||
}
|
||||
|
||||
@mixin py($value) {
|
||||
padding-top: spacing($value);
|
||||
padding-bottom: spacing($value);
|
||||
}
|
||||
|
||||
@mixin pt($value) {
|
||||
padding-top: spacing($value);
|
||||
}
|
||||
|
||||
@mixin pr($value) {
|
||||
padding-right: spacing($value);
|
||||
}
|
||||
|
||||
@mixin pb($value) {
|
||||
padding-bottom: spacing($value);
|
||||
}
|
||||
|
||||
@mixin pl($value) {
|
||||
padding-left: spacing($value);
|
||||
}
|
||||
|
||||
@mixin m($value) {
|
||||
margin: spacing($value);
|
||||
}
|
||||
|
||||
@mixin mx($value) {
|
||||
margin-left: spacing($value);
|
||||
margin-right: spacing($value);
|
||||
}
|
||||
|
||||
@mixin my($value) {
|
||||
margin-top: spacing($value);
|
||||
margin-bottom: spacing($value);
|
||||
}
|
||||
|
||||
@mixin mt($value) {
|
||||
margin-top: spacing($value);
|
||||
}
|
||||
|
||||
@mixin mr($value) {
|
||||
margin-right: spacing($value);
|
||||
}
|
||||
|
||||
@mixin mb($value) {
|
||||
margin-bottom: spacing($value);
|
||||
}
|
||||
|
||||
@mixin ml($value) {
|
||||
margin-left: spacing($value);
|
||||
}
|
||||
|
||||
// Gap mixin
|
||||
@mixin gap($value) {
|
||||
gap: spacing($value);
|
||||
}
|
||||
|
||||
// Border radius mixin
|
||||
@mixin rounded($value: base) {
|
||||
border-radius: radius($value);
|
||||
}
|
||||
|
||||
// Typography mixins
|
||||
@mixin text($size) {
|
||||
font-size: font-size($size);
|
||||
}
|
||||
|
||||
@mixin font($weight) {
|
||||
font-weight: font-weight($weight);
|
||||
}
|
||||
|
||||
// Responsive breakpoint mixin
|
||||
@mixin respond-to($breakpoint) {
|
||||
$size: breakpoint($breakpoint);
|
||||
@media (min-width: $size) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Button base styles mixin
|
||||
@mixin button-base {
|
||||
@include px(4);
|
||||
@include py(2);
|
||||
@include rounded(md);
|
||||
@include font(medium);
|
||||
@include text(sm);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: spacing(2);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
// Card mixin
|
||||
@mixin card {
|
||||
background: $card;
|
||||
color: $card-foreground;
|
||||
border: 1px solid $border;
|
||||
@include rounded(lg);
|
||||
@include p(6);
|
||||
}
|
||||
|
||||
// Shadow mixins
|
||||
@mixin shadow-sm {
|
||||
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
}
|
||||
|
||||
@mixin shadow {
|
||||
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
@mixin shadow-md {
|
||||
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
@mixin shadow-lg {
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
// Transition mixin
|
||||
@mixin transition($properties: all, $duration: 0.2s, $timing: ease) {
|
||||
transition: $properties $duration $timing;
|
||||
}
|
||||
|
||||
// Truncate text
|
||||
@mixin truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// Visually hidden (for accessibility)
|
||||
@mixin sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
// Absolute positioning shortcuts
|
||||
@mixin absolute-center {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@mixin absolute-fill {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
// Focus ring
|
||||
@mixin focus-ring {
|
||||
&:focus-visible {
|
||||
outline: 2px solid $ring;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
119
src/styles/abstracts/_variables.scss
Normal file
119
src/styles/abstracts/_variables.scss
Normal file
@@ -0,0 +1,119 @@
|
||||
// Color Variables
|
||||
$background: hsl(222.2, 84%, 4.9%);
|
||||
$foreground: hsl(210, 40%, 98%);
|
||||
$card: hsl(222.2, 84%, 4.9%);
|
||||
$card-foreground: hsl(210, 40%, 98%);
|
||||
$popover: hsl(222.2, 84%, 4.9%);
|
||||
$popover-foreground: hsl(210, 40%, 98%);
|
||||
$primary: hsl(263, 70%, 50%);
|
||||
$primary-foreground: hsl(210, 40%, 98%);
|
||||
$secondary: hsl(217.2, 32.6%, 17.5%);
|
||||
$secondary-foreground: hsl(210, 40%, 98%);
|
||||
$muted: hsl(217.2, 32.6%, 17.5%);
|
||||
$muted-foreground: hsl(215, 20.2%, 65.1%);
|
||||
$accent: hsl(195, 100%, 70%);
|
||||
$accent-foreground: hsl(222.2, 84%, 4.9%);
|
||||
$destructive: hsl(0, 62.8%, 30.6%);
|
||||
$destructive-foreground: hsl(210, 40%, 98%);
|
||||
$border: hsl(217.2, 32.6%, 17.5%);
|
||||
$input: hsl(217.2, 32.6%, 17.5%);
|
||||
$ring: hsl(195, 100%, 70%);
|
||||
|
||||
// Spacing Scale
|
||||
$spacing: (
|
||||
0: 0,
|
||||
px: 1px,
|
||||
0-5: 0.125rem,
|
||||
1: 0.25rem,
|
||||
1-5: 0.375rem,
|
||||
2: 0.5rem,
|
||||
2-5: 0.625rem,
|
||||
3: 0.75rem,
|
||||
3-5: 0.875rem,
|
||||
4: 1rem,
|
||||
5: 1.25rem,
|
||||
6: 1.5rem,
|
||||
7: 1.75rem,
|
||||
8: 2rem,
|
||||
9: 2.25rem,
|
||||
10: 2.5rem,
|
||||
11: 2.75rem,
|
||||
12: 3rem,
|
||||
14: 3.5rem,
|
||||
16: 4rem,
|
||||
20: 5rem,
|
||||
24: 6rem,
|
||||
28: 7rem,
|
||||
32: 8rem,
|
||||
36: 9rem,
|
||||
40: 10rem,
|
||||
48: 12rem,
|
||||
56: 14rem,
|
||||
64: 16rem,
|
||||
72: 18rem,
|
||||
80: 20rem,
|
||||
96: 24rem,
|
||||
);
|
||||
|
||||
// Border Radius
|
||||
$radius: (
|
||||
none: 0,
|
||||
sm: 0.125rem,
|
||||
base: 0.25rem,
|
||||
md: 0.375rem,
|
||||
lg: 0.5rem,
|
||||
xl: 0.75rem,
|
||||
2xl: 1rem,
|
||||
3xl: 1.5rem,
|
||||
full: 9999px,
|
||||
);
|
||||
|
||||
// Font Sizes
|
||||
$font-sizes: (
|
||||
xs: 0.75rem,
|
||||
sm: 0.875rem,
|
||||
base: 1rem,
|
||||
lg: 1.125rem,
|
||||
xl: 1.25rem,
|
||||
2xl: 1.5rem,
|
||||
3xl: 1.875rem,
|
||||
4xl: 2.25rem,
|
||||
5xl: 3rem,
|
||||
6xl: 3.75rem,
|
||||
7xl: 4.5rem,
|
||||
8xl: 6rem,
|
||||
9xl: 8rem,
|
||||
);
|
||||
|
||||
// Font Weights
|
||||
$font-weights: (
|
||||
thin: 100,
|
||||
extralight: 200,
|
||||
light: 300,
|
||||
normal: 400,
|
||||
medium: 500,
|
||||
semibold: 600,
|
||||
bold: 700,
|
||||
extrabold: 800,
|
||||
black: 900,
|
||||
);
|
||||
|
||||
// Breakpoints
|
||||
$breakpoints: (
|
||||
sm: 640px,
|
||||
md: 768px,
|
||||
lg: 1024px,
|
||||
xl: 1280px,
|
||||
2xl: 1536px,
|
||||
);
|
||||
|
||||
// Z-index scale
|
||||
$z-index: (
|
||||
0: 0,
|
||||
10: 10,
|
||||
20: 20,
|
||||
30: 30,
|
||||
40: 40,
|
||||
50: 50,
|
||||
auto: auto,
|
||||
);
|
||||
142
src/styles/utilities/_utilities.scss
Normal file
142
src/styles/utilities/_utilities.scss
Normal file
@@ -0,0 +1,142 @@
|
||||
@import '../abstracts';
|
||||
|
||||
// Utility classes generator
|
||||
@each $name, $value in $spacing {
|
||||
.p-#{$name} { padding: $value !important; }
|
||||
.px-#{$name} { padding-left: $value !important; padding-right: $value !important; }
|
||||
.py-#{$name} { padding-top: $value !important; padding-bottom: $value !important; }
|
||||
.pt-#{$name} { padding-top: $value !important; }
|
||||
.pr-#{$name} { padding-right: $value !important; }
|
||||
.pb-#{$name} { padding-bottom: $value !important; }
|
||||
.pl-#{$name} { padding-left: $value !important; }
|
||||
|
||||
.m-#{$name} { margin: $value !important; }
|
||||
.mx-#{$name} { margin-left: $value !important; margin-right: $value !important; }
|
||||
.my-#{$name} { margin-top: $value !important; margin-bottom: $value !important; }
|
||||
.mt-#{$name} { margin-top: $value !important; }
|
||||
.mr-#{$name} { margin-right: $value !important; }
|
||||
.mb-#{$name} { margin-bottom: $value !important; }
|
||||
.ml-#{$name} { margin-left: $value !important; }
|
||||
|
||||
.gap-#{$name} { gap: $value !important; }
|
||||
}
|
||||
|
||||
// Flexbox utilities
|
||||
.flex { display: flex !important; }
|
||||
.inline-flex { display: inline-flex !important; }
|
||||
.flex-row { flex-direction: row !important; }
|
||||
.flex-col { flex-direction: column !important; }
|
||||
.flex-wrap { flex-wrap: wrap !important; }
|
||||
.flex-nowrap { flex-wrap: nowrap !important; }
|
||||
.flex-1 { flex: 1 1 0% !important; }
|
||||
|
||||
// Justify content
|
||||
.justify-start { justify-content: flex-start !important; }
|
||||
.justify-end { justify-content: flex-end !important; }
|
||||
.justify-center { justify-content: center !important; }
|
||||
.justify-between { justify-content: space-between !important; }
|
||||
.justify-around { justify-content: space-around !important; }
|
||||
|
||||
// Align items
|
||||
.items-start { align-items: flex-start !important; }
|
||||
.items-end { align-items: flex-end !important; }
|
||||
.items-center { align-items: center !important; }
|
||||
.items-stretch { align-items: stretch !important; }
|
||||
|
||||
// Grid
|
||||
.grid { display: grid !important; }
|
||||
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)) !important; }
|
||||
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
|
||||
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)) !important; }
|
||||
.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)) !important; }
|
||||
|
||||
// Border radius
|
||||
@each $name, $value in $radius {
|
||||
.rounded-#{$name} { border-radius: $value !important; }
|
||||
}
|
||||
|
||||
// Text sizes
|
||||
@each $name, $value in $font-sizes {
|
||||
.text-#{$name} { font-size: $value !important; }
|
||||
}
|
||||
|
||||
// Font weights
|
||||
@each $name, $value in $font-weights {
|
||||
.font-#{$name} { font-weight: $value !important; }
|
||||
}
|
||||
|
||||
// Text align
|
||||
.text-left { text-align: left !important; }
|
||||
.text-center { text-align: center !important; }
|
||||
.text-right { text-align: right !important; }
|
||||
|
||||
// Colors
|
||||
.text-foreground { color: $foreground !important; }
|
||||
.text-muted-foreground { color: $muted-foreground !important; }
|
||||
.text-primary { color: $primary !important; }
|
||||
.text-destructive { color: $destructive !important; }
|
||||
.text-accent { color: $accent !important; }
|
||||
|
||||
.bg-background { background-color: $background !important; }
|
||||
.bg-card { background-color: $card !important; }
|
||||
.bg-primary { background-color: $primary !important; }
|
||||
.bg-secondary { background-color: $secondary !important; }
|
||||
.bg-muted { background-color: $muted !important; }
|
||||
.bg-accent { background-color: $accent !important; }
|
||||
.bg-destructive { background-color: $destructive !important; }
|
||||
|
||||
// Border
|
||||
.border { border: 1px solid $border !important; }
|
||||
.border-b { border-bottom: 1px solid $border !important; }
|
||||
.border-t { border-top: 1px solid $border !important; }
|
||||
.border-l { border-left: 1px solid $border !important; }
|
||||
.border-r { border-right: 1px solid $border !important; }
|
||||
|
||||
// Width & Height
|
||||
.w-full { width: 100% !important; }
|
||||
.h-full { height: 100% !important; }
|
||||
.min-h-screen { min-height: 100vh !important; }
|
||||
.max-w-3xl { max-width: 48rem !important; }
|
||||
.max-w-7xl { max-width: 80rem !important; }
|
||||
|
||||
// Position
|
||||
.relative { position: relative !important; }
|
||||
.absolute { position: absolute !important; }
|
||||
.fixed { position: fixed !important; }
|
||||
.sticky { position: sticky !important; }
|
||||
|
||||
// Display
|
||||
.hidden { display: none !important; }
|
||||
.block { display: block !important; }
|
||||
.inline-block { display: inline-block !important; }
|
||||
|
||||
// Overflow
|
||||
.overflow-hidden { overflow: hidden !important; }
|
||||
.overflow-auto { overflow: auto !important; }
|
||||
.overflow-scroll { overflow: scroll !important; }
|
||||
|
||||
// Cursor
|
||||
.cursor-pointer { cursor: pointer !important; }
|
||||
.cursor-not-allowed { cursor: not-allowed !important; }
|
||||
|
||||
// Opacity
|
||||
.opacity-50 { opacity: 0.5 !important; }
|
||||
.opacity-75 { opacity: 0.75 !important; }
|
||||
.opacity-100 { opacity: 1 !important; }
|
||||
|
||||
// Transitions
|
||||
.transition { transition: all 0.2s ease !important; }
|
||||
.transition-colors { transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease !important; }
|
||||
|
||||
// Shadow
|
||||
.shadow-sm { @include shadow-sm; }
|
||||
.shadow { @include shadow; }
|
||||
.shadow-md { @include shadow-md; }
|
||||
.shadow-lg { @include shadow-lg; }
|
||||
|
||||
// Space between children - DRY loop
|
||||
$space-values: (2, 3, 4, 6, 8);
|
||||
@each $value in $space-values {
|
||||
.space-y-#{$value} > * + * { margin-top: spacing($value) !important; }
|
||||
.space-x-#{$value} > * + * { margin-left: spacing($value) !important; }
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
'./src/pages/**/*.{ts,tsx}',
|
||||
'./src/components/**/*.{ts,tsx}',
|
||||
'./src/app/**/*.{ts,tsx}',
|
||||
'./src/**/*.{ts,tsx}',
|
||||
],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
Reference in New Issue
Block a user