Generated by Spark: Any non standard material UI CSS uses sass

This commit is contained in:
2026-01-16 01:48:07 +00:00
committed by GitHub
parent 14ba22e595
commit 3905226082
13 changed files with 2984 additions and 2 deletions

7
PRD.md
View File

@@ -96,6 +96,13 @@ This is a full-featured low-code IDE with multiple integrated tools (code editor
- **Progression**: User finalizes design → Clicks export → System bundles files → Downloads zip or shows code → User extracts and runs locally
- **Success criteria**: Generated project structure is valid; includes package.json; code runs without errors
### Custom Sass Styling System
- **Functionality**: Comprehensive Sass-based styling system for non-standard Material UI components with pre-built components, utilities, mixins, and animations
- **Purpose**: Extend Material UI with custom styled components and provide consistent design patterns through Sass
- **Trigger**: Using Sass classes in components or importing Sass modules for custom styling
- **Progression**: Import main.scss → Apply custom component classes → Use mixins for custom styles → Configure variables for theming
- **Success criteria**: All Sass styles compile correctly; custom components render with proper styling; mixins work as expected; animations are smooth and respect reduced motion preferences
## Edge Case Handling
- **Empty Projects**: Show welcome screen with quick-start templates when no project exists; AI can generate entire projects from scratch
- **Invalid Prisma Schemas**: Validate models and show inline errors before generating code

View File

@@ -15,6 +15,7 @@ A comprehensive visual low-code platform for generating production-ready Next.js
- **Prisma Schema Designer** - Visual database model builder with relations and field configuration
- **Component Tree Builder** - Hierarchical React component designer with Material UI integration
- **Theme Designer** - Advanced theming with multiple variants (light/dark/custom) and unlimited custom colors
- **Sass Styling System** - Custom Material UI components with Sass, including utilities, mixins, and animations
- **Flask Backend Designer** - Python REST API designer with blueprints, endpoints, and CORS configuration
- **Project Settings** - Configure Next.js options, npm packages, scripts, and build settings
@@ -58,6 +59,7 @@ A comprehensive visual low-code platform for generating production-ready Next.js
- Next.js 14 with App Router
- React 18 with TypeScript
- Material UI 5
- Sass/SCSS for custom styling
- Monaco Editor
- Tailwind CSS
- Framer Motion
@@ -81,6 +83,7 @@ The application includes comprehensive built-in documentation:
- **README** - Complete feature overview and getting started guide
- **Roadmap** - Completed features and planned enhancements
- **Agents Files** - AI service architecture and integration points
- **Sass Styles Guide** - Custom Material UI components, utilities, mixins, and animations
Access documentation by clicking the **Documentation** tab in the application.
@@ -95,6 +98,7 @@ Access documentation by clicking the **Documentation** tab in the application.
- Auto error detection and repair
- Flask backend designer
- Project settings and npm management
- Custom Sass styling system with utilities and mixins
### 🔮 Planned
- Real-time preview with hot reload

362
package-lock.json generated
View File

@@ -63,6 +63,7 @@
"react-hook-form": "^7.54.2",
"react-resizable-panels": "^2.1.7",
"recharts": "^2.15.1",
"sass": "^1.97.2",
"sonner": "^2.0.1",
"tailwind-merge": "^3.0.2",
"three": "^0.175.0",
@@ -1479,6 +1480,302 @@
"node": ">= 18"
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.4.tgz",
"integrity": "sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"detect-libc": "^2.0.3",
"is-glob": "^4.0.3",
"node-addon-api": "^7.0.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"@parcel/watcher-android-arm64": "2.5.4",
"@parcel/watcher-darwin-arm64": "2.5.4",
"@parcel/watcher-darwin-x64": "2.5.4",
"@parcel/watcher-freebsd-x64": "2.5.4",
"@parcel/watcher-linux-arm-glibc": "2.5.4",
"@parcel/watcher-linux-arm-musl": "2.5.4",
"@parcel/watcher-linux-arm64-glibc": "2.5.4",
"@parcel/watcher-linux-arm64-musl": "2.5.4",
"@parcel/watcher-linux-x64-glibc": "2.5.4",
"@parcel/watcher-linux-x64-musl": "2.5.4",
"@parcel/watcher-win32-arm64": "2.5.4",
"@parcel/watcher-win32-ia32": "2.5.4",
"@parcel/watcher-win32-x64": "2.5.4"
}
},
"node_modules/@parcel/watcher-android-arm64": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.4.tgz",
"integrity": "sha512-hoh0vx4v+b3BNI7Cjoy2/B0ARqcwVNrzN/n7DLq9ZB4I3lrsvhrkCViJyfTj/Qi5xM9YFiH4AmHGK6pgH1ss7g==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.4.tgz",
"integrity": "sha512-kphKy377pZiWpAOyTgQYPE5/XEKVMaj6VUjKT5VkNyUJlr2qZAn8gIc7CPzx+kbhvqHDT9d7EqdOqRXT6vk0zw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-x64": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.4.tgz",
"integrity": "sha512-UKaQFhCtNJW1A9YyVz3Ju7ydf6QgrpNQfRZ35wNKUhTQ3dxJ/3MULXN5JN/0Z80V/KUBDGa3RZaKq1EQT2a2gg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.4.tgz",
"integrity": "sha512-Dib0Wv3Ow/m2/ttvLdeI2DBXloO7t3Z0oCp4bAb2aqyqOjKPPGrg10pMJJAQ7tt8P4V2rwYwywkDhUia/FgS+Q==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.4.tgz",
"integrity": "sha512-I5Vb769pdf7Q7Sf4KNy8Pogl/URRCKu9ImMmnVKYayhynuyGYMzuI4UOWnegQNa2sGpsPSbzDsqbHNMyeyPCgw==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.4.tgz",
"integrity": "sha512-kGO8RPvVrcAotV4QcWh8kZuHr9bXi9a3bSZw7kFarYR0+fGliU7hd/zevhjw8fnvIKG3J9EO5G6sXNGCSNMYPQ==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.4.tgz",
"integrity": "sha512-KU75aooXhqGFY2W5/p8DYYHt4hrjHZod8AhcGAmhzPn/etTa+lYCDB2b1sJy3sWJ8ahFVTdy+EbqSBvMx3iFlw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.4.tgz",
"integrity": "sha512-Qx8uNiIekVutnzbVdrgSanM+cbpDD3boB1f8vMtnuG5Zau4/bdDbXyKwIn0ToqFhIuob73bcxV9NwRm04/hzHQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.4.tgz",
"integrity": "sha512-UYBQvhYmgAv61LNUn24qGQdjtycFBKSK3EXr72DbJqX9aaLbtCOO8+1SkKhD/GNiJ97ExgcHBrukcYhVjrnogA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.4.tgz",
"integrity": "sha512-YoRWCVgxv8akZrMhdyVi6/TyoeeMkQ0PGGOf2E4omODrvd1wxniXP+DBynKoHryStks7l+fDAMUBRzqNHrVOpg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-arm64": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.4.tgz",
"integrity": "sha512-iby+D/YNXWkiQNYcIhg8P5hSjzXEHaQrk2SLrWOUD7VeC4Ohu0WQvmV+HDJokZVJ2UjJ4AGXW3bx7Lls9Ln4TQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-ia32": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.4.tgz",
"integrity": "sha512-vQN+KIReG0a2ZDpVv8cgddlf67J8hk1WfZMMP7sMeZmJRSmEax5xNDNWKdgqSe2brOKTQQAs3aCCUal2qBHAyg==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.4.tgz",
"integrity": "sha512-3A6efb6BOKwyw7yk9ro2vus2YTt2nvcd56AuzxdMiVOxL9umDyN5PKkKfZ/gZ9row41SjVmTVQNWQhaRRGpOKw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@phosphor-icons/react": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz",
@@ -5247,6 +5544,21 @@
"node": ">= 16"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/class-variance-authority": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
@@ -7238,6 +7550,12 @@
"node": ">= 4"
}
},
"node_modules/immutable": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
"license": "MIT"
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -7329,7 +7647,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -7339,7 +7657,7 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@@ -8084,6 +8402,13 @@
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"license": "MIT",
"optional": true
},
"node_modules/nwsapi": {
"version": "2.2.23",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz",
@@ -8733,6 +9058,19 @@
"react-dom": ">=16.6.0"
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/recharts": {
"version": "2.15.4",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz",
@@ -8987,6 +9325,26 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/sass": {
"version": "1.97.2",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.97.2.tgz",
"integrity": "sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==",
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=14.0.0"
},
"optionalDependencies": {
"@parcel/watcher": "^2.4.1"
}
},
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",

View File

@@ -67,6 +67,7 @@
"react-hook-form": "^7.54.2",
"react-resizable-panels": "^2.1.7",
"recharts": "^2.15.1",
"sass": "^1.97.2",
"sonner": "^2.0.1",
"tailwind-merge": "^3.0.2",
"three": "^0.175.0",

View File

@@ -20,6 +20,7 @@ import { FlaskDesigner } from '@/components/FlaskDesigner'
import { ProjectSettingsDesigner } from '@/components/ProjectSettingsDesigner'
import { ErrorPanel } from '@/components/ErrorPanel'
import { DocumentationView } from '@/components/DocumentationView'
import { SassStylesShowcase } from '@/components/SassStylesShowcase'
import { generateNextJSProject, generatePrismaSchema, generateMUITheme, generatePlaywrightTests, generateStorybookStories, generateUnitTests, generateFlaskApp } from '@/lib/generators'
import { AIService } from '@/lib/ai-service'
import { toast } from 'sonner'
@@ -350,6 +351,10 @@ function App() {
<FileText size={18} />
Documentation
</TabsTrigger>
<TabsTrigger value="sass" className="gap-2">
<PaintBrush size={18} />
Sass Styles
</TabsTrigger>
</TabsList>
</div>
@@ -428,6 +433,10 @@ function App() {
<TabsContent value="docs" className="h-full m-0">
<DocumentationView />
</TabsContent>
<TabsContent value="sass" className="h-full m-0">
<SassStylesShowcase />
</TabsContent>
</div>
</Tabs>

View File

@@ -46,6 +46,10 @@ export function DocumentationView() {
<FileCode size={18} />
Agents Files
</TabsTrigger>
<TabsTrigger value="sass" className="gap-2">
<PaintBrush size={18} />
Sass Styles Guide
</TabsTrigger>
</TabsList>
</div>
@@ -728,6 +732,387 @@ export function DocumentationView() {
</div>
</div>
</TabsContent>
<TabsContent value="sass" className="m-0 space-y-6">
<div className="space-y-4">
<div className="flex items-center gap-4">
<div className="w-16 h-16 rounded-xl bg-gradient-to-br from-primary to-accent flex items-center justify-center">
<PaintBrush size={32} weight="duotone" className="text-white" />
</div>
<div>
<h1 className="text-4xl font-bold">Sass Styles Guide</h1>
<p className="text-lg text-muted-foreground">
Custom Material UI components with Sass
</p>
</div>
</div>
<Separator />
<div className="space-y-4">
<h2 className="text-2xl font-semibold">Overview</h2>
<p className="text-foreground/90 leading-relaxed">
CodeForge includes a comprehensive Sass-based styling system for non-standard Material UI components.
This system provides pre-built components, utilities, mixins, and animations that extend beyond the
standard Material UI component library.
</p>
</div>
<Card>
<CardHeader>
<CardTitle>File Structure</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="space-y-2">
<code className="text-sm font-mono text-accent">src/styles/_variables.scss</code>
<p className="text-sm text-muted-foreground ml-4">
Color palettes, spacing scales, typography, transitions, and other design tokens
</p>
</div>
<div className="space-y-2">
<code className="text-sm font-mono text-accent">src/styles/_utilities.scss</code>
<p className="text-sm text-muted-foreground ml-4">
Mixins and functions for responsive design, colors, typography, and layout helpers
</p>
</div>
<div className="space-y-2">
<code className="text-sm font-mono text-accent">src/styles/_animations.scss</code>
<p className="text-sm text-muted-foreground ml-4">
Keyframe animations and animation utility classes for transitions and effects
</p>
</div>
<div className="space-y-2">
<code className="text-sm font-mono text-accent">src/styles/material-ui-custom.scss</code>
<p className="text-sm text-muted-foreground ml-4">
Custom Material UI component styles with variants and states
</p>
</div>
<div className="space-y-2">
<code className="text-sm font-mono text-accent">src/styles/main.scss</code>
<p className="text-sm text-muted-foreground ml-4">
Main entry point that imports all Sass modules and provides layout components
</p>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Available Components</CardTitle>
<CardDescription>Custom Material UI components built with Sass</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4 md:grid-cols-2">
<SassComponentItem
name="Buttons"
classes={['mui-custom-button--primary', 'mui-custom-button--secondary', 'mui-custom-button--accent', 'mui-custom-button--outline', 'mui-custom-button--ghost']}
description="Custom styled buttons with hover effects and variants"
/>
<SassComponentItem
name="Cards"
classes={['mui-custom-card', 'mui-custom-card--gradient', 'mui-custom-card--glass']}
description="Elevated cards with gradient and glassmorphism variants"
/>
<SassComponentItem
name="Inputs"
classes={['mui-custom-input', 'mui-custom-input--error', 'mui-custom-input--success']}
description="Form inputs with focus states and validation styling"
/>
<SassComponentItem
name="Chips"
classes={['mui-custom-chip--primary', 'mui-custom-chip--success', 'mui-custom-chip--error', 'mui-custom-chip--warning']}
description="Status chips and tags with color variants"
/>
<SassComponentItem
name="Panels"
classes={['mui-custom-panel', 'mui-custom-panel--with-header']}
description="Content panels with headers and footers"
/>
<SassComponentItem
name="Dialogs"
classes={['mui-custom-dialog']}
description="Modal dialogs with backdrop blur effects"
/>
<SassComponentItem
name="Badges"
classes={['custom-mui-badge', 'custom-mui-badge--dot']}
description="Notification badges and indicators"
/>
<SassComponentItem
name="Progress"
classes={['mui-custom-progress', 'mui-custom-progress--indeterminate']}
description="Loading progress bars with animations"
/>
<SassComponentItem
name="Skeletons"
classes={['mui-custom-skeleton--text', 'mui-custom-skeleton--circle', 'mui-custom-skeleton--rect']}
description="Loading skeleton placeholders with shimmer effect"
/>
<SassComponentItem
name="Accordions"
classes={['mui-custom-accordion']}
description="Collapsible content sections with animations"
/>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Layout Components</CardTitle>
<CardDescription>Sass-powered layout utilities</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<FeatureItem
icon={<Code size={18} />}
title="custom-mui-container"
description="Max-width container with responsive padding"
/>
<FeatureItem
icon={<Code size={18} />}
title="custom-mui-grid"
description="CSS Grid layouts with responsive columns (--cols-1 to --cols-12, --responsive)"
/>
<FeatureItem
icon={<Code size={18} />}
title="custom-mui-flex"
description="Flexbox utilities (--row, --col, --wrap, --center, --between, --around)"
/>
<FeatureItem
icon={<Code size={18} />}
title="custom-mui-stack"
description="Vertical/horizontal stacks with configurable gaps"
/>
<FeatureItem
icon={<Code size={18} />}
title="custom-mui-surface"
description="Interactive surfaces with elevation and hover effects"
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Sass Utilities & Mixins</CardTitle>
<CardDescription>Reusable functions for custom styling</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="space-y-2">
<h3 className="font-semibold flex items-center gap-2">
<Lightbulb size={18} weight="duotone" className="text-accent" />
Responsive Design
</h3>
<div className="ml-6 space-y-2 text-sm">
<p className="font-mono text-accent">@include respond-to($breakpoint)</p>
<p className="text-muted-foreground">Generate media queries for xs, sm, md, lg, xl, 2xl breakpoints</p>
<pre className="custom-mui-code-block text-xs mt-2">
{`@include respond-to('lg') {
padding: 2rem;
}`}
</pre>
</div>
</div>
<Separator />
<div className="space-y-2">
<h3 className="font-semibold flex items-center gap-2">
<Lightbulb size={18} weight="duotone" className="text-accent" />
Elevation & Shadows
</h3>
<div className="ml-6 space-y-2 text-sm">
<p className="font-mono text-accent">@include elevation($level)</p>
<p className="text-muted-foreground">Apply box shadows with levels 1-4</p>
<pre className="custom-mui-code-block text-xs mt-2">
{`@include elevation(2);`}
</pre>
</div>
</div>
<Separator />
<div className="space-y-2">
<h3 className="font-semibold flex items-center gap-2">
<Lightbulb size={18} weight="duotone" className="text-accent" />
Glassmorphism
</h3>
<div className="ml-6 space-y-2 text-sm">
<p className="font-mono text-accent">@include glassmorphism($blur, $opacity)</p>
<p className="text-muted-foreground">Create frosted glass effects with backdrop blur</p>
<pre className="custom-mui-code-block text-xs mt-2">
{`@include glassmorphism(16px, 0.1);`}
</pre>
</div>
</div>
<Separator />
<div className="space-y-2">
<h3 className="font-semibold flex items-center gap-2">
<Lightbulb size={18} weight="duotone" className="text-accent" />
Color Functions
</h3>
<div className="ml-6 space-y-2 text-sm">
<p className="font-mono text-accent">get-color($palette, $shade)</p>
<p className="text-muted-foreground">Access colors from predefined palettes (primary, secondary, accent, success, error, warning)</p>
<pre className="custom-mui-code-block text-xs mt-2">
{`color: get-color('primary', 500);`}
</pre>
</div>
</div>
<Separator />
<div className="space-y-2">
<h3 className="font-semibold flex items-center gap-2">
<Lightbulb size={18} weight="duotone" className="text-accent" />
Text Truncation
</h3>
<div className="ml-6 space-y-2 text-sm">
<p className="font-mono text-accent">@include truncate($lines)</p>
<p className="text-muted-foreground">Truncate text with ellipsis after specified lines</p>
<pre className="custom-mui-code-block text-xs mt-2">
{`@include truncate(2);`}
</pre>
</div>
</div>
<Separator />
<div className="space-y-2">
<h3 className="font-semibold flex items-center gap-2">
<Lightbulb size={18} weight="duotone" className="text-accent" />
Custom Scrollbars
</h3>
<div className="ml-6 space-y-2 text-sm">
<p className="font-mono text-accent">@include show-scrollbar($track, $thumb)</p>
<p className="text-muted-foreground">Style webkit scrollbars with custom colors</p>
<pre className="custom-mui-code-block text-xs mt-2">
{`@include show-scrollbar(rgba(0,0,0,0.1), rgba(0,0,0,0.3));`}
</pre>
</div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Animation Classes</CardTitle>
<CardDescription>Pre-built animation utilities</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
<AnimationItem name="animate-fade-in" description="Fade in from opacity 0" />
<AnimationItem name="animate-slide-in-up" description="Slide in from bottom" />
<AnimationItem name="animate-slide-in-down" description="Slide in from top" />
<AnimationItem name="animate-scale-in" description="Scale in from 95%" />
<AnimationItem name="animate-pulse" description="Pulsing opacity effect" />
<AnimationItem name="animate-bounce" description="Bouncing effect" />
<AnimationItem name="animate-spin" description="Continuous rotation" />
<AnimationItem name="animate-shimmer" description="Shimmer effect for loading" />
<AnimationItem name="animate-float" description="Floating up and down" />
<AnimationItem name="animate-glow" description="Glowing shadow effect" />
</div>
</CardContent>
</Card>
<Card className="bg-accent/5 border-accent/20">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Rocket size={20} weight="duotone" />
Quick Start Example
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<h3 className="font-semibold">Using Custom Components</h3>
<pre className="custom-mui-code-block">
{`import './styles/main.scss'
function MyComponent() {
return (
<div className="custom-mui-container">
<div className="custom-mui-grid custom-mui-grid--cols-3">
<div className="mui-custom-card">
<h3>Card Title</h3>
<p>Card content</p>
<button className="mui-custom-button mui-custom-button--primary">
Click Me
</button>
</div>
</div>
</div>
)
}`}
</pre>
</div>
<Separator />
<div className="space-y-2">
<h3 className="font-semibold">Creating Custom Styles with Mixins</h3>
<pre className="custom-mui-code-block">
{`@use './styles/utilities' as *;
@use './styles/variables' as *;
.my-custom-component {
@include elevation(2);
@include responsive-padding(spacing('6'));
background: get-color('primary', 500);
@include respond-to('md') {
@include elevation(3);
}
&:hover {
@include glassmorphism(12px, 0.15);
}
}`}
</pre>
</div>
</CardContent>
</Card>
<Card className="bg-muted/50">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Target size={20} weight="duotone" />
Best Practices
</CardTitle>
</CardHeader>
<CardContent>
<ul className="space-y-2 text-sm">
<li className="flex items-start gap-2">
<CheckCircle size={16} className="text-accent mt-1 flex-shrink-0" weight="fill" />
<span>Import main.scss in your index.css to access all Sass components and utilities</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle size={16} className="text-accent mt-1 flex-shrink-0" weight="fill" />
<span>Use @use instead of @import for better module encapsulation</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle size={16} className="text-accent mt-1 flex-shrink-0" weight="fill" />
<span>Leverage mixins for consistent spacing, elevation, and responsive design</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle size={16} className="text-accent mt-1 flex-shrink-0" weight="fill" />
<span>Extend existing component classes rather than creating from scratch</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle size={16} className="text-accent mt-1 flex-shrink-0" weight="fill" />
<span>Use animation classes sparingly and respect prefers-reduced-motion</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle size={16} className="text-accent mt-1 flex-shrink-0" weight="fill" />
<span>Customize variables in _variables.scss to match your design system</span>
</li>
</ul>
</CardContent>
</Card>
</div>
</TabsContent>
</div>
</ScrollArea>
</Tabs>
@@ -735,6 +1120,29 @@ export function DocumentationView() {
)
}
function SassComponentItem({ name, classes, description }: { name: string; classes: string[]; description: string }) {
return (
<div className="space-y-2 p-4 border rounded-lg bg-card">
<h4 className="font-semibold">{name}</h4>
<p className="text-sm text-muted-foreground">{description}</p>
<div className="space-y-1">
{classes.map((cls, idx) => (
<code key={idx} className="text-xs font-mono text-accent block">{cls}</code>
))}
</div>
</div>
)
}
function AnimationItem({ name, description }: { name: string; description: string }) {
return (
<div className="space-y-1 p-3 border rounded-lg bg-card">
<code className="text-xs font-mono text-accent">{name}</code>
<p className="text-xs text-muted-foreground">{description}</p>
</div>
)
}
function FeatureItem({ icon, title, description }: { icon: React.ReactNode; title: string; description: string }) {
return (
<div className="flex gap-3">

View File

@@ -0,0 +1,320 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Code, Palette, Sparkle, CheckCircle } from '@phosphor-icons/react'
export function SassStylesShowcase() {
return (
<div className="h-full p-6 space-y-6">
<div>
<h1 className="text-3xl font-bold mb-2">Custom Material UI Sass Styles</h1>
<p className="text-muted-foreground">
Non-standard Material UI CSS components built with Sass
</p>
</div>
<Tabs defaultValue="buttons" className="flex-1">
<TabsList>
<TabsTrigger value="buttons">Buttons</TabsTrigger>
<TabsTrigger value="inputs">Inputs</TabsTrigger>
<TabsTrigger value="cards">Cards</TabsTrigger>
<TabsTrigger value="chips">Chips</TabsTrigger>
<TabsTrigger value="layout">Layout</TabsTrigger>
<TabsTrigger value="animations">Animations</TabsTrigger>
</TabsList>
<TabsContent value="buttons" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Custom Button Styles</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-3">
<div className="flex gap-3 flex-wrap">
<button className="mui-custom-button mui-custom-button--primary">
Primary Button
</button>
<button className="mui-custom-button mui-custom-button--secondary">
Secondary Button
</button>
<button className="mui-custom-button mui-custom-button--accent">
Accent Button
</button>
</div>
<div className="flex gap-3 flex-wrap">
<button className="mui-custom-button mui-custom-button--outline">
Outline Button
</button>
<button className="mui-custom-button mui-custom-button--ghost">
Ghost Button
</button>
</div>
</div>
<div className="mt-4">
<pre className="custom-mui-code-block">
{`<button className="mui-custom-button mui-custom-button--primary">
Primary Button
</button>
<button className="mui-custom-button mui-custom-button--accent">
Accent Button
</button>`}
</pre>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="inputs" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Custom Input Styles</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-3">
<input
type="text"
placeholder="Default input"
className="mui-custom-input w-full"
/>
<input
type="text"
placeholder="Error state"
className="mui-custom-input mui-custom-input--error w-full"
/>
<input
type="text"
placeholder="Success state"
className="mui-custom-input mui-custom-input--success w-full"
/>
</div>
<div className="mt-4">
<pre className="custom-mui-code-block">
{`<input
className="mui-custom-input"
placeholder="Your input"
/>
<input
className="mui-custom-input mui-custom-input--error"
placeholder="Error state"
/>`}
</pre>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="cards" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Custom Card Styles</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div className="mui-custom-card p-6">
<h3 className="font-bold mb-2">Standard Card</h3>
<p className="text-sm text-muted-foreground">
Basic card with elevation and hover effect
</p>
</div>
<div className="mui-custom-card mui-custom-card--gradient p-6">
<h3 className="font-bold mb-2">Gradient Card</h3>
<p className="text-sm opacity-90">
Card with gradient background
</p>
</div>
<div className="mui-custom-card mui-custom-card--glass p-6">
<h3 className="font-bold mb-2">Glass Card</h3>
<p className="text-sm text-muted-foreground">
Glassmorphism effect card
</p>
</div>
</div>
<div className="mt-4">
<pre className="custom-mui-code-block">
{`<div className="mui-custom-card">
Standard Card
</div>
<div className="mui-custom-card mui-custom-card--gradient">
Gradient Card
</div>
<div className="mui-custom-card mui-custom-card--glass">
Glass Card
</div>`}
</pre>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="chips" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Custom Chip/Badge Styles</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex gap-2 flex-wrap">
<span className="mui-custom-chip mui-custom-chip--primary">
Primary
</span>
<span className="mui-custom-chip mui-custom-chip--secondary">
Secondary
</span>
<span className="mui-custom-chip mui-custom-chip--accent">
Accent
</span>
<span className="mui-custom-chip mui-custom-chip--success">
<CheckCircle size={14} weight="fill" />
Success
</span>
<span className="mui-custom-chip mui-custom-chip--error">
Error
</span>
<span className="mui-custom-chip mui-custom-chip--warning">
Warning
</span>
</div>
<div className="mt-4">
<pre className="custom-mui-code-block">
{`<span className="mui-custom-chip mui-custom-chip--primary">
Primary
</span>
<span className="mui-custom-chip mui-custom-chip--success">
<CheckCircle size={14} />
Success
</span>`}
</pre>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Custom Tags</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex gap-2 flex-wrap">
<span className="custom-mui-tag">Default</span>
<span className="custom-mui-tag custom-mui-tag--sm">Small</span>
<span className="custom-mui-tag custom-mui-tag--lg">Large</span>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="layout" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Custom Layout Components</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
<div>
<h3 className="font-semibold mb-3">Custom Stack</h3>
<div className="custom-mui-stack custom-mui-stack--gap-3">
<div className="p-4 bg-card border rounded">Item 1</div>
<div className="p-4 bg-card border rounded">Item 2</div>
<div className="p-4 bg-card border rounded">Item 3</div>
</div>
</div>
<div>
<h3 className="font-semibold mb-3">Custom Grid (Responsive)</h3>
<div className="custom-mui-grid custom-mui-grid--responsive">
<div className="p-4 bg-card border rounded">Grid 1</div>
<div className="p-4 bg-card border rounded">Grid 2</div>
<div className="p-4 bg-card border rounded">Grid 3</div>
<div className="p-4 bg-card border rounded">Grid 4</div>
</div>
</div>
<div>
<h3 className="font-semibold mb-3">Custom Surface</h3>
<div className="custom-mui-surface custom-mui-surface--interactive">
<p>Interactive surface with hover effects</p>
</div>
</div>
<div className="mt-4">
<pre className="custom-mui-code-block">
{`<div className="custom-mui-stack custom-mui-stack--gap-3">
<div>Item 1</div>
<div>Item 2</div>
</div>
<div className="custom-mui-grid custom-mui-grid--responsive">
<div>Grid Item</div>
</div>`}
</pre>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="animations" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Animation Classes</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
<div className="p-4 bg-card border rounded animate-fade-in">
Fade In
</div>
<div className="p-4 bg-card border rounded animate-slide-in-up">
Slide Up
</div>
<div className="p-4 bg-card border rounded animate-scale-in">
Scale In
</div>
<div className="p-4 bg-card border rounded animate-pulse">
Pulse
</div>
<div className="p-4 bg-card border rounded animate-bounce">
Bounce
</div>
<div className="p-4 bg-card border rounded animate-float">
Float
</div>
</div>
<div className="mt-4">
<pre className="custom-mui-code-block">
{`<div className="animate-fade-in">Fade In</div>
<div className="animate-slide-in-up">Slide Up</div>
<div className="animate-pulse">Pulse</div>
<div className="animate-bounce">Bounce</div>`}
</pre>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Skeleton Loading</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-3">
<div className="mui-custom-skeleton mui-custom-skeleton--text" />
<div className="mui-custom-skeleton mui-custom-skeleton--text" />
<div className="mui-custom-skeleton mui-custom-skeleton--rect" />
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
)
}

View File

@@ -1,5 +1,6 @@
@import 'tailwindcss';
@import "tw-animate-css";
@import './styles/main.scss';
@layer base {
* {

364
src/styles/_animations.scss Normal file
View File

@@ -0,0 +1,364 @@
@use './variables' as *;
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes slide-in-up {
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes slide-in-down {
from {
transform: translateY(-100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes slide-in-left {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slide-in-right {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes scale-in {
from {
transform: scale(0.95);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
@keyframes scale-out {
from {
transform: scale(1);
opacity: 1;
}
to {
transform: scale(0.95);
opacity: 0;
}
}
@keyframes bounce {
0%, 100% {
transform: translateY(0);
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
}
50% {
transform: translateY(-25%);
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
@keyframes ping {
0% {
transform: scale(1);
opacity: 1;
}
75%, 100% {
transform: scale(2);
opacity: 0;
}
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes shimmer {
0% {
background-position: -1000px 0;
}
100% {
background-position: 1000px 0;
}
}
@keyframes shake {
0%, 100% {
transform: translateX(0);
}
10%, 30%, 50%, 70%, 90% {
transform: translateX(-10px);
}
20%, 40%, 60%, 80% {
transform: translateX(10px);
}
}
@keyframes swing {
0%, 100% {
transform: rotate(0deg);
}
20% {
transform: rotate(15deg);
}
40% {
transform: rotate(-10deg);
}
60% {
transform: rotate(5deg);
}
80% {
transform: rotate(-5deg);
}
}
@keyframes rotate-in {
from {
transform: rotate(-180deg) scale(0.8);
opacity: 0;
}
to {
transform: rotate(0deg) scale(1);
opacity: 1;
}
}
@keyframes flip {
from {
transform: perspective(400px) rotateY(90deg);
opacity: 0;
}
to {
transform: perspective(400px) rotateY(0deg);
opacity: 1;
}
}
@keyframes glow {
0%, 100% {
box-shadow: 0 0 5px rgba($accent-color, 0.2);
}
50% {
box-shadow: 0 0 20px rgba($accent-color, 0.6);
}
}
@keyframes gradient-shift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
@keyframes float {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-20px);
}
}
@keyframes wave {
0% {
transform: rotate(0deg);
}
10% {
transform: rotate(14deg);
}
20% {
transform: rotate(-8deg);
}
30% {
transform: rotate(14deg);
}
40% {
transform: rotate(-4deg);
}
50% {
transform: rotate(10deg);
}
60% {
transform: rotate(0deg);
}
100% {
transform: rotate(0deg);
}
}
@keyframes typing {
from {
width: 0;
}
to {
width: 100%;
}
}
@keyframes blink {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
.animate-fade-in {
animation: fade-in $transition-base forwards;
}
.animate-fade-out {
animation: fade-out $transition-base forwards;
}
.animate-slide-in-up {
animation: slide-in-up $transition-base $easing-ease-out;
}
.animate-slide-in-down {
animation: slide-in-down $transition-base $easing-ease-out;
}
.animate-slide-in-left {
animation: slide-in-left $transition-base $easing-ease-out;
}
.animate-slide-in-right {
animation: slide-in-right $transition-base $easing-ease-out;
}
.animate-scale-in {
animation: scale-in $transition-base $easing-ease-out;
}
.animate-scale-out {
animation: scale-out $transition-base $easing-ease-out;
}
.animate-bounce {
animation: bounce 1s infinite;
}
.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
.animate-ping {
animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
}
.animate-spin {
animation: spin 1s linear infinite;
}
.animate-shimmer {
animation: shimmer 2s infinite;
}
.animate-shake {
animation: shake 0.5s;
}
.animate-swing {
animation: swing 1s ease-in-out;
}
.animate-rotate-in {
animation: rotate-in $transition-base $easing-ease-out;
}
.animate-flip {
animation: flip $transition-base $easing-ease-out;
}
.animate-glow {
animation: glow 2s ease-in-out infinite;
}
.animate-gradient-shift {
animation: gradient-shift 3s ease infinite;
background-size: 200% 200%;
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
.animate-wave {
animation: wave 2s ease-in-out;
}
.animate-typing {
animation: typing 3.5s steps(40, end);
overflow: hidden;
white-space: nowrap;
border-right: 3px solid;
animation: typing 3.5s steps(40, end), blink 0.75s step-end infinite;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

333
src/styles/_utilities.scss Normal file
View File

@@ -0,0 +1,333 @@
@use 'sass:color';
@use 'sass:map';
$breakpoints: (
'xs': 480px,
'sm': 640px,
'md': 768px,
'lg': 1024px,
'xl': 1280px,
'2xl': 1536px,
);
@function breakpoint($size) {
@return map.get($breakpoints, $size);
}
@mixin respond-to($breakpoint) {
@if map.has-key($breakpoints, $breakpoint) {
@media (min-width: map.get($breakpoints, $breakpoint)) {
@content;
}
} @else {
@warn "Unknown breakpoint: #{$breakpoint}";
}
}
@mixin respond-below($breakpoint) {
@if map.has-key($breakpoints, $breakpoint) {
@media (max-width: map.get($breakpoints, $breakpoint) - 1px) {
@content;
}
} @else {
@warn "Unknown breakpoint: #{$breakpoint}";
}
}
@mixin respond-between($min, $max) {
@if map.has-key($breakpoints, $min) and map.has-key($breakpoints, $max) {
@media (min-width: map.get($breakpoints, $min)) and (max-width: map.get($breakpoints, $max) - 1px) {
@content;
}
}
}
$spacing-scale: (
'0': 0,
'1': 0.25rem,
'2': 0.5rem,
'3': 0.75rem,
'4': 1rem,
'5': 1.25rem,
'6': 1.5rem,
'8': 2rem,
'10': 2.5rem,
'12': 3rem,
'16': 4rem,
'20': 5rem,
'24': 6rem,
);
@function spacing($size) {
@return map.get($spacing-scale, $size);
}
$color-palette: (
'primary': (
50: #eef2ff,
100: #e0e7ff,
200: #c7d2fe,
300: #a5b4fc,
400: #818cf8,
500: #6366f1,
600: #4f46e5,
700: #4338ca,
800: #3730a3,
900: #312e81,
),
'secondary': (
50: #faf5ff,
100: #f3e8ff,
200: #e9d5ff,
300: #d8b4fe,
400: #c084fc,
500: #a855f7,
600: #9333ea,
700: #7e22ce,
800: #6b21a8,
900: #581c87,
),
'accent': (
50: #ecfeff,
100: #cffafe,
200: #a5f3fc,
300: #67e8f9,
400: #22d3ee,
500: #06b6d4,
600: #0891b2,
700: #0e7490,
800: #155e75,
900: #164e63,
),
'success': (
50: #f0fdf4,
100: #dcfce7,
200: #bbf7d0,
300: #86efac,
400: #4ade80,
500: #22c55e,
600: #16a34a,
700: #15803d,
800: #166534,
900: #14532d,
),
'error': (
50: #fef2f2,
100: #fee2e2,
200: #fecaca,
300: #fca5a5,
400: #f87171,
500: #ef4444,
600: #dc2626,
700: #b91c1c,
800: #991b1b,
900: #7f1d1d,
),
'warning': (
50: #fffbeb,
100: #fef3c7,
200: #fde68a,
300: #fcd34d,
400: #fbbf24,
500: #f59e0b,
600: #d97706,
700: #b45309,
800: #92400e,
900: #78350f,
),
'gray': (
50: #f9fafb,
100: #f3f4f6,
200: #e5e7eb,
300: #d1d5db,
400: #9ca3af,
500: #6b7280,
600: #4b5563,
700: #374151,
800: #1f2937,
900: #111827,
),
);
@function get-color($palette, $shade: 500) {
@if map.has-key($color-palette, $palette) {
$palette-map: map.get($color-palette, $palette);
@return map.get($palette-map, $shade);
}
@warn "Unknown color palette: #{$palette}";
@return null;
}
@mixin theme-colors($theme-name) {
&[data-theme='#{$theme-name}'] {
@content;
}
}
@mixin transition($properties: all, $duration: 0.3s, $timing: ease) {
transition: $properties $duration $timing;
}
@mixin truncate($lines: 1) {
@if $lines == 1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} @else {
display: -webkit-box;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
@mixin center($direction: 'both') {
display: flex;
@if $direction == 'both' {
justify-content: center;
align-items: center;
} @else if $direction == 'horizontal' {
justify-content: center;
} @else if $direction == 'vertical' {
align-items: center;
}
}
@mixin aspect-ratio($width, $height) {
aspect-ratio: #{$width} / #{$height};
@supports not (aspect-ratio: 1 / 1) {
&::before {
content: '';
padding-top: percentage($height / $width);
float: left;
}
&::after {
content: '';
display: block;
clear: both;
}
}
}
@mixin focus-ring($color: #06b6d4, $width: 3px, $offset: 2px) {
&:focus {
outline: none;
box-shadow: 0 0 0 $width rgba($color, 0.3);
}
&:focus-visible {
outline: $width solid rgba($color, 0.5);
outline-offset: $offset;
}
}
@mixin hide-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
@mixin show-scrollbar($track-color: rgba(255, 255, 255, 0.1), $thumb-color: rgba(255, 255, 255, 0.3)) {
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
&::-webkit-scrollbar-track {
background: $track-color;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: $thumb-color;
border-radius: 4px;
&:hover {
background: color.adjust($thumb-color, $lightness: 10%);
}
}
}
@mixin visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
@mixin reset-button {
background: none;
border: none;
padding: 0;
margin: 0;
font: inherit;
color: inherit;
cursor: pointer;
&:focus {
outline: none;
}
}
@mixin reset-list {
list-style: none;
padding: 0;
margin: 0;
}
$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,
);
@function font-size($size) {
@return map.get($font-sizes, $size);
}
$font-weights: (
'thin': 100,
'light': 300,
'normal': 400,
'medium': 500,
'semibold': 600,
'bold': 700,
'extrabold': 800,
'black': 900,
);
@function font-weight($weight) {
@return map.get($font-weights, $weight);
}
$z-index-layers: (
'base': 0,
'dropdown': 1000,
'sticky': 1020,
'fixed': 1030,
'modal-backdrop': 1040,
'modal': 1050,
'popover': 1060,
'tooltip': 1070,
);
@function z-index($layer) {
@return map.get($z-index-layers, $layer);
}

View File

@@ -0,0 +1,72 @@
$primary-color: #6366f1;
$primary-light: #818cf8;
$primary-dark: #4338ca;
$secondary-color: #8b5cf6;
$secondary-light: #a78bfa;
$secondary-dark: #7c3aed;
$accent-color: #06b6d4;
$accent-light: #22d3ee;
$accent-dark: #0891b2;
$success-color: #10b981;
$error-color: #ef4444;
$warning-color: #f59e0b;
$info-color: #3b82f6;
$background-dark: #1a1d29;
$background-card: #23273a;
$background-elevated: #2d3348;
$background-hover: rgba(255, 255, 255, 0.05);
$text-primary: #e8eaed;
$text-secondary: #a8abb5;
$text-disabled: #6b6e7b;
$text-inverse: #1a1d29;
$border-color: rgba(255, 255, 255, 0.1);
$border-color-strong: rgba(255, 255, 255, 0.2);
$border-color-weak: rgba(255, 255, 255, 0.05);
$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
$shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
$shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
$radius-sm: 0.25rem;
$radius-md: 0.5rem;
$radius-lg: 0.75rem;
$radius-xl: 1rem;
$radius-2xl: 1.5rem;
$radius-full: 9999px;
$spacing-xs: 0.25rem;
$spacing-sm: 0.5rem;
$spacing-md: 1rem;
$spacing-lg: 1.5rem;
$spacing-xl: 2rem;
$spacing-2xl: 3rem;
$font-family-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
$font-family-display: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
$font-family-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
$transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
$transition-base: 300ms cubic-bezier(0.4, 0, 0.2, 1);
$transition-slow: 500ms cubic-bezier(0.4, 0, 0.2, 1);
$easing-linear: linear;
$easing-ease: ease;
$easing-ease-in: ease-in;
$easing-ease-out: ease-out;
$easing-ease-in-out: ease-in-out;
$easing-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
$easing-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
$grid-columns: 12;
$grid-gutter: 1rem;
$container-max-width: 1280px;
$container-padding: 1rem;

573
src/styles/main.scss Normal file
View File

@@ -0,0 +1,573 @@
@use './variables' as *;
@use './utilities' as *;
@use './animations';
@use './material-ui-custom';
.mui-enhanced {
font-family: $font-family-sans;
color: $text-primary;
* {
box-sizing: border-box;
}
code, pre {
font-family: $font-family-mono;
}
h1, h2, h3, h4, h5, h6 {
font-family: $font-family-display;
}
}
.custom-mui-container {
max-width: $container-max-width;
margin-left: auto;
margin-right: auto;
padding-left: $container-padding;
padding-right: $container-padding;
@include respond-to('lg') {
padding-left: calc($container-padding * 2);
padding-right: calc($container-padding * 2);
}
}
.custom-mui-grid {
display: grid;
gap: $grid-gutter;
&--cols-1 {
grid-template-columns: repeat(1, 1fr);
}
&--cols-2 {
grid-template-columns: repeat(2, 1fr);
}
&--cols-3 {
grid-template-columns: repeat(3, 1fr);
}
&--cols-4 {
grid-template-columns: repeat(4, 1fr);
}
&--cols-6 {
grid-template-columns: repeat(6, 1fr);
}
&--cols-12 {
grid-template-columns: repeat(12, 1fr);
}
&--responsive {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
}
.custom-mui-flex {
display: flex;
&--row {
flex-direction: row;
}
&--col {
flex-direction: column;
}
&--wrap {
flex-wrap: wrap;
}
&--center {
justify-content: center;
align-items: center;
}
&--between {
justify-content: space-between;
}
&--around {
justify-content: space-around;
}
&--evenly {
justify-content: space-evenly;
}
&--start {
justify-content: flex-start;
align-items: flex-start;
}
&--end {
justify-content: flex-end;
align-items: flex-end;
}
}
.custom-mui-stack {
display: flex;
flex-direction: column;
gap: spacing('4');
&--horizontal {
flex-direction: row;
}
&--gap-1 {
gap: spacing('1');
}
&--gap-2 {
gap: spacing('2');
}
&--gap-3 {
gap: spacing('3');
}
&--gap-4 {
gap: spacing('4');
}
&--gap-6 {
gap: spacing('6');
}
&--gap-8 {
gap: spacing('8');
}
}
.custom-mui-surface {
background: $background-card;
border: 1px solid $border-color;
border-radius: $radius-lg;
padding: spacing('6');
&--elevated {
box-shadow: $shadow-lg;
background: $background-elevated;
}
&--interactive {
cursor: pointer;
transition: all $transition-base;
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-xl;
border-color: $border-color-strong;
}
&:active {
transform: translateY(0);
box-shadow: $shadow-md;
}
}
}
.custom-mui-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(4px);
z-index: z-index('modal-backdrop');
display: flex;
align-items: center;
justify-content: center;
animation: fade-in $transition-fast;
}
.custom-mui-drawer {
position: fixed;
background: $background-card;
z-index: z-index('modal');
box-shadow: $shadow-2xl;
overflow-y: auto;
&--left {
top: 0;
left: 0;
bottom: 0;
width: 320px;
animation: slide-in-left $transition-base;
}
&--right {
top: 0;
right: 0;
bottom: 0;
width: 320px;
animation: slide-in-right $transition-base;
}
&--top {
top: 0;
left: 0;
right: 0;
height: auto;
animation: slide-in-down $transition-base;
}
&--bottom {
bottom: 0;
left: 0;
right: 0;
height: auto;
max-height: 90vh;
animation: slide-in-up $transition-base;
}
}
.custom-mui-navbar {
position: sticky;
top: 0;
z-index: z-index('sticky');
background: rgba($background-card, 0.9);
backdrop-filter: blur(12px);
border-bottom: 1px solid $border-color;
padding: spacing('4') spacing('6');
@include respond-below('md') {
padding: spacing('3') spacing('4');
}
}
.custom-mui-sidebar {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 280px;
background: $background-card;
border-right: 1px solid $border-color;
overflow-y: auto;
z-index: z-index('fixed');
@include respond-below('lg') {
transform: translateX(-100%);
transition: transform $transition-base;
&--open {
transform: translateX(0);
}
}
}
.custom-mui-footer {
background: $background-card;
border-top: 1px solid $border-color;
padding: spacing('8') spacing('6');
margin-top: auto;
@include respond-below('md') {
padding: spacing('6') spacing('4');
}
}
.custom-mui-breadcrumbs {
display: flex;
align-items: center;
gap: spacing('2');
flex-wrap: wrap;
&-item {
display: flex;
align-items: center;
color: $text-secondary;
font-size: font-size('sm');
&--active {
color: $text-primary;
font-weight: font-weight('medium');
}
&:not(:last-child)::after {
content: '/';
margin-left: spacing('2');
color: $text-disabled;
}
}
a {
color: inherit;
text-decoration: none;
transition: color $transition-fast;
&:hover {
color: $accent-color;
}
}
}
.custom-mui-stepper {
display: flex;
align-items: center;
&-step {
display: flex;
flex-direction: column;
align-items: center;
gap: spacing('2');
flex: 1;
position: relative;
&:not(:last-child)::after {
content: '';
position: absolute;
top: 16px;
left: 50%;
width: 100%;
height: 2px;
background: $border-color;
}
&--completed::after {
background: $success-color;
}
&--active::after {
background: $primary-color;
}
}
&-icon {
@include center;
width: 32px;
height: 32px;
border-radius: $radius-full;
background: $background-elevated;
border: 2px solid $border-color;
color: $text-secondary;
font-size: font-size('sm');
font-weight: font-weight('semibold');
z-index: 1;
.custom-mui-stepper-step--completed & {
background: $success-color;
border-color: $success-color;
color: white;
}
.custom-mui-stepper-step--active & {
background: $primary-color;
border-color: $primary-color;
color: white;
}
}
&-label {
font-size: font-size('sm');
color: $text-secondary;
text-align: center;
.custom-mui-stepper-step--active & {
color: $text-primary;
font-weight: font-weight('medium');
}
}
}
.custom-mui-timeline {
position: relative;
padding-left: spacing('6');
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 2px;
background: $border-color;
}
&-item {
position: relative;
padding-bottom: spacing('6');
&::before {
content: '';
position: absolute;
left: -29px;
top: 4px;
width: 12px;
height: 12px;
border-radius: $radius-full;
background: $primary-color;
border: 2px solid $background-dark;
}
&--success::before {
background: $success-color;
}
&--error::before {
background: $error-color;
}
&--warning::before {
background: $warning-color;
}
}
}
.custom-mui-table {
width: 100%;
border-collapse: collapse;
thead {
background: $background-elevated;
border-bottom: 2px solid $border-color-strong;
}
th {
padding: spacing('3') spacing('4');
text-align: left;
font-weight: font-weight('semibold');
font-size: font-size('sm');
color: $text-secondary;
text-transform: uppercase;
letter-spacing: 0.05em;
}
td {
padding: spacing('3') spacing('4');
border-bottom: 1px solid $border-color;
}
tbody tr {
transition: background $transition-fast;
&:hover {
background: $background-hover;
}
}
&--striped tbody tr:nth-child(even) {
background: rgba(255, 255, 255, 0.02);
}
&--bordered {
border: 1px solid $border-color;
td, th {
border: 1px solid $border-color;
}
}
}
.custom-mui-list {
@include reset-list;
&-item {
padding: spacing('3') spacing('4');
transition: background $transition-fast;
border-radius: $radius-md;
cursor: pointer;
&:hover {
background: $background-hover;
}
&--active {
background: rgba($primary-color, 0.1);
color: $primary-color;
font-weight: font-weight('medium');
}
&--disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
}
&--bordered &-item {
border: 1px solid $border-color;
margin-bottom: spacing('2');
}
&--divided &-item {
border-bottom: 1px solid $border-color;
border-radius: 0;
&:last-child {
border-bottom: none;
}
}
}
.custom-mui-code-block {
background: $background-elevated;
border: 1px solid $border-color;
border-radius: $radius-md;
padding: spacing('4');
overflow-x: auto;
font-family: $font-family-mono;
font-size: font-size('sm');
@include show-scrollbar;
pre {
margin: 0;
padding: 0;
}
code {
color: $text-primary;
font-family: inherit;
}
}
.custom-mui-tag {
display: inline-flex;
align-items: center;
gap: spacing('1');
padding: spacing('1') spacing('2');
border-radius: $radius-md;
font-size: font-size('xs');
font-weight: font-weight('medium');
background: rgba($primary-color, 0.15);
color: $primary-color;
&--sm {
padding: 2px spacing('1');
font-size: 10px;
}
&--lg {
padding: spacing('2') spacing('3');
font-size: font-size('sm');
}
}
.utility-text {
&-truncate {
@include truncate(1);
}
&-truncate-2 {
@include truncate(2);
}
&-truncate-3 {
@include truncate(3);
}
}
.utility-visually-hidden {
@include visually-hidden;
}
.utility-focus-ring {
@include focus-ring;
}

View File

@@ -0,0 +1,532 @@
@use 'sass:color';
@use 'sass:map';
$primary-color: #6366f1;
$secondary-color: #8b5cf6;
$accent-color: #06b6d4;
$background-dark: #1a1d29;
$background-card: #23273a;
$text-light: #e8eaed;
@mixin elevation($level) {
@if $level == 1 {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
} @else if $level == 2 {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
} @else if $level == 3 {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
} @else if $level == 4 {
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.25);
}
}
@mixin responsive-padding($base-padding) {
padding: $base-padding;
@media (max-width: 768px) {
padding: $base-padding * 0.75;
}
@media (max-width: 480px) {
padding: $base-padding * 0.5;
}
}
@mixin glassmorphism($blur: 10px, $opacity: 0.1) {
background: rgba(255, 255, 255, $opacity);
backdrop-filter: blur($blur);
-webkit-backdrop-filter: blur($blur);
}
@mixin gradient-background($start-color, $end-color, $angle: 135deg) {
background: linear-gradient($angle, $start-color, $end-color);
}
.mui-custom {
&-card {
@include elevation(2);
border-radius: 12px;
background-color: $background-card;
transition: all 0.3s ease;
&:hover {
@include elevation(3);
transform: translateY(-2px);
}
&--gradient {
@include gradient-background($primary-color, $secondary-color);
color: white;
}
&--glass {
@include glassmorphism(16px, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
}
}
&-button {
@include responsive-padding(12px);
border-radius: 8px;
font-weight: 600;
text-transform: none;
transition: all 0.2s ease;
&--primary {
background: $primary-color;
color: white;
&:hover {
background: color.adjust($primary-color, $lightness: 10%);
box-shadow: 0 4px 12px rgba($primary-color, 0.4);
}
&:active {
transform: scale(0.98);
}
}
&--secondary {
background: $secondary-color;
color: white;
&:hover {
background: color.adjust($secondary-color, $lightness: 10%);
box-shadow: 0 4px 12px rgba($secondary-color, 0.4);
}
}
&--accent {
background: $accent-color;
color: $background-dark;
&:hover {
background: color.adjust($accent-color, $lightness: 10%);
box-shadow: 0 4px 12px rgba($accent-color, 0.4);
}
}
&--outline {
background: transparent;
border: 2px solid $primary-color;
color: $primary-color;
&:hover {
background: rgba($primary-color, 0.1);
}
}
&--ghost {
background: transparent;
color: $text-light;
&:hover {
background: rgba($text-light, 0.1);
}
}
}
&-input {
@include responsive-padding(12px);
border-radius: 8px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
color: $text-light;
transition: all 0.2s ease;
&:focus {
outline: none;
border-color: $accent-color;
box-shadow: 0 0 0 3px rgba($accent-color, 0.2);
}
&::placeholder {
color: rgba($text-light, 0.5);
}
&--error {
border-color: #ef4444;
&:focus {
box-shadow: 0 0 0 3px rgba(#ef4444, 0.2);
}
}
&--success {
border-color: #10b981;
&:focus {
box-shadow: 0 0 0 3px rgba(#10b981, 0.2);
}
}
}
&-chip {
display: inline-flex;
align-items: center;
padding: 4px 12px;
border-radius: 16px;
font-size: 12px;
font-weight: 500;
&--primary {
background: rgba($primary-color, 0.2);
color: $primary-color;
border: 1px solid rgba($primary-color, 0.3);
}
&--secondary {
background: rgba($secondary-color, 0.2);
color: $secondary-color;
border: 1px solid rgba($secondary-color, 0.3);
}
&--accent {
background: rgba($accent-color, 0.2);
color: $accent-color;
border: 1px solid rgba($accent-color, 0.3);
}
&--success {
background: rgba(#10b981, 0.2);
color: #10b981;
border: 1px solid rgba(#10b981, 0.3);
}
&--error {
background: rgba(#ef4444, 0.2);
color: #ef4444;
border: 1px solid rgba(#ef4444, 0.3);
}
&--warning {
background: rgba(#f59e0b, 0.2);
color: #f59e0b;
border: 1px solid rgba(#f59e0b, 0.3);
}
}
&-panel {
@include responsive-padding(24px);
@include elevation(1);
border-radius: 12px;
background: $background-card;
&--with-header {
padding: 0;
.panel-header {
@include responsive-padding(20px);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
font-weight: 600;
font-size: 18px;
}
.panel-body {
@include responsive-padding(24px);
}
.panel-footer {
@include responsive-padding(20px);
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
}
}
&-dialog {
.dialog-overlay {
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(4px);
}
.dialog-content {
@include elevation(4);
border-radius: 16px;
background: $background-card;
max-width: 600px;
animation: dialog-appear 0.3s ease;
}
@keyframes dialog-appear {
from {
opacity: 0;
transform: scale(0.95) translateY(-20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
}
&-tooltip {
@include elevation(2);
padding: 8px 12px;
border-radius: 6px;
background: $background-card;
color: $text-light;
font-size: 12px;
max-width: 200px;
animation: tooltip-appear 0.2s ease;
@keyframes tooltip-appear {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
}
&-avatar {
border-radius: 50%;
overflow: hidden;
&--small {
width: 32px;
height: 32px;
}
&--medium {
width: 48px;
height: 48px;
}
&--large {
width: 64px;
height: 64px;
}
&--with-ring {
box-shadow: 0 0 0 3px $accent-color;
}
}
&-badge {
position: relative;
display: inline-flex;
.badge-indicator {
position: absolute;
top: 0;
right: 0;
transform: translate(50%, -50%);
min-width: 20px;
height: 20px;
padding: 0 6px;
border-radius: 10px;
background: #ef4444;
color: white;
font-size: 11px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid $background-dark;
}
&--dot {
.badge-indicator {
min-width: 10px;
width: 10px;
height: 10px;
padding: 0;
border-radius: 50%;
}
}
}
&-skeleton {
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.05) 25%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0.05) 75%
);
background-size: 200% 100%;
animation: skeleton-shimmer 1.5s infinite;
border-radius: 4px;
@keyframes skeleton-shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
&--text {
height: 16px;
margin-bottom: 8px;
}
&--circle {
border-radius: 50%;
width: 48px;
height: 48px;
}
&--rect {
height: 120px;
}
}
&-progress {
width: 100%;
height: 4px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
overflow: hidden;
.progress-bar {
height: 100%;
background: linear-gradient(90deg, $primary-color, $accent-color);
transition: width 0.3s ease;
}
&--indeterminate {
.progress-bar {
width: 40%;
animation: progress-indeterminate 1.5s infinite;
}
}
@keyframes progress-indeterminate {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(350%);
}
}
}
&-divider {
height: 1px;
background: rgba(255, 255, 255, 0.1);
margin: 16px 0;
&--vertical {
width: 1px;
height: auto;
margin: 0 16px;
}
&--with-text {
display: flex;
align-items: center;
gap: 16px;
&::before,
&::after {
content: '';
flex: 1;
height: 1px;
background: rgba(255, 255, 255, 0.1);
}
}
}
&-accordion {
border-radius: 8px;
overflow: hidden;
&-item {
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
&:last-child {
border-bottom: none;
}
}
&-trigger {
@include responsive-padding(16px);
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
background: transparent;
border: none;
color: $text-light;
font-weight: 500;
cursor: pointer;
transition: background 0.2s ease;
&:hover {
background: rgba(255, 255, 255, 0.05);
}
.accordion-icon {
transition: transform 0.3s ease;
}
&[data-state='open'] .accordion-icon {
transform: rotate(180deg);
}
}
&-content {
@include responsive-padding(16px);
animation: accordion-slide-down 0.3s ease;
&[data-state='closed'] {
animation: accordion-slide-up 0.3s ease;
}
}
@keyframes accordion-slide-down {
from {
height: 0;
opacity: 0;
}
to {
height: var(--radix-accordion-content-height);
opacity: 1;
}
}
@keyframes accordion-slide-up {
from {
height: var(--radix-accordion-content-height);
opacity: 1;
}
to {
height: 0;
opacity: 0;
}
}
}
}
.custom-scrollbar {
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
&::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 4px;
&:hover {
background: rgba(255, 255, 255, 0.3);
}
}
}
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}