Загрузка...

NeumorphicTradeDashboard: UI-компонент дашборда для трейдинга в стиле неоморфизм. Позволяет визуализировать рыночные данные.
# NeumorphicTradeDashboard
You are given a task to integrate an existing React component in the codebase
~~~/README.md
# Neumorphic Trade Dashboard
A visually stunning trading dashboard variant implementing a "Soft UI" design system.
## Design Philosophy
This component follows strict Neumorphism principles:
- **Monochromatic Palette**: Built on a `#E0E5EC` cool grey base.
- **Physical Depth**: Uses dual shadows (light top-left, dark bottom-right) to create extruded and inset elements.
- **Soft Shapes**: Hyper-rounded corners (32px) for an organic feel.
- **Typography**: Uses "Plus Jakarta Sans" for modern headings and "DM Sans" for clean body text.
## Features
- **Equity Curve**: Large, inset-container chart visualization.
- **Stats Cards**: Extruded cards with clear hierarchy.
- **Journaling**: Deep inset text areas for a tactile writing experience.
- **Responsive**: Mobile-first navigation with smooth transitions.
## Usage
```tsx
import { NeumorphicTradeDashboard } from '@/sd-components/4970efd4-871d-4d1e-a029-aac3f9fa44ec';
function App() {
return <NeumorphicTradeDashboard />;
}
```
~~~
~~~/src/App.tsx
import React from 'react';
import NeumorphicTradeDashboard from './Component';
export default function App() {
return <NeumorphicTradeDashboard />;
}
~~~
~~~/package.json
{
"name": "trading-analytics-dashboard",
"version": "1.0.0",
"description": "Dark-themed trading analytics dashboard",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"framer-motion": "^11.0.0",
"lucide-react": "^0.344.0",
"clsx": "^2.1.0",
"tailwind-merge": "^2.2.0",
"recharts": "^2.12.0",
"date-fns": "^3.3.0"
}
}
~~~
~~~/src/Component.tsx
import React, { useState, useEffect } from 'react';
import {
LayoutDashboard,
BookOpen,
TrendingUp,
Settings,
Bell,
Search,
Menu,
X,
ChevronRight,
Plus,
ArrowUpRight,
ArrowDownRight,
Wallet,
Calendar,
MoreHorizontal
} from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { Area, AreaChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
// --- Design System Constants ---
const COLORS = {
bg: '#E0E5EC',
text: {
primary: '#3D4852',
muted: '#6B7280',
accent: '#6C63FF',
success: '#38B2AC',
},
shadows: {
extruded: '9px 9px 16px rgb(163,177,198,0.6), -9px -9px 16px rgba(255,255,255,0.5)',
extrudedHover: '12px 12px 20px rgb(163,177,198,0.7), -12px -12px 20px rgba(255,255,255,0.6)',
inset: 'inset 6px 6px 10px rgb(163,177,198,0.6), inset -6px -6px 10px rgba(255,255,255,0.5)',
insetDeep: 'inset 10px 10px 20px rgb(163,177,198,0.7), inset -10px -10px 20px rgba(255,255,255,0.6)',
insetSmall: 'inset 3px 3px 6px rgb(163,177,198,0.6), inset -3px -3px 6px rgba(255,255,255,0.5)',
}
};
// --- Mock Data ---
const equityData = [
{ date: 'Mon', value: 10000 },
{ date: 'Tue', value: 10250 },
{ date: 'Wed', value: 10100 },
{ date: 'Thu', value: 10450 },
{ date: 'Fri', value: 10300 },
{ date: 'Sat', value: 10600 },
{ date: 'Sun', value: 10850 },
];
const recentTrades = [
{ id: 1, pair: 'EUR/USD', type: 'Long', result: 'Win', amount: '+$1,240', date: 'Today, 10:30 AM' },
{ id: 2, pair: 'GBP/JPY', type: 'Short', result: 'Loss', amount: '-$450', date: 'Yesterday, 2:15 PM' },
{ id: 3, pair: 'XAU/USD', type: 'Long', result: 'Win', amount: '+$890', date: 'Yesterday, 9:00 AM' },
];
// --- Reusable Components ---
const NeumorphicCard = ({ children, className = '', onClick }: { children: React.ReactNode, className?: string, onClick?: () => void }) => (
<motion.div
whileHover={{ y: -2, boxShadow: COLORS.shadows.extrudedHover }}
transition={{ duration: 0.3, ease: "easeOut" }}
className={`bg-[#E0E5EC] rounded-[32px] p-8 ${className}`}
style={{ boxShadow: COLORS.shadows.extruded }}
onClick={onClick}
>
{children}
</motion.div>
);
const NeumorphicButton = ({ children, active = false, className = '', iconOnly = false, onClick }: { children: React.ReactNode, active?: boolean, className?: string, iconOnly?: boolean, onClick?: () => void }) => (
<motion.button
whileHover={{ y: -1, boxShadow: COLORS.shadows.extrudedHover }}
whileTap={{ y: 1, boxShadow: COLORS.shadows.insetSmall }}
onClick={onClick}
className={`
${iconOnly ? 'p-3 rounded-full' : 'px-6 py-3 rounded-2xl'}
${active ? 'text-[#6C63FF]' : 'text-[#6B7280]'}
flex items-center justify-center font-medium transition-colors duration-300
${className}
`}
style={{
boxShadow: active ? COLORS.shadows.inset : COLORS.shadows.extruded,
color: active ? COLORS.text.accent : COLORS.text.muted
}}
>
{children}
</motion.button>
);
const NeumorphicInput = ({ placeholder, icon: Icon }: { placeholder: string, icon?: any }) => (
<div className="relative w-full group">
<input
type="text"
placeholder={placeholder}
className="w-full bg-[#E0E5EC] rounded-2xl py-4 pl-12 pr-6 text-[#3D4852] placeholder-[#A0AEC0] outline-none transition-all duration-300 focus:ring-2 focus:ring-[#6C63FF] focus:ring-offset-2 focus:ring-offset-[#E0E5EC]"
style={{ boxShadow: COLORS.shadows.insetDeep }}
/>
{Icon && (
<Icon className="absolute left-4 top-1/2 transform -translate-y-1/2 text-[#A0AEC0] w-5 h-5" />
)}
</div>
);
const IconButton = ({ icon: Icon, active = false }: { icon: any, active?: boolean }) => (
<div
className={`
w-12 h-12 rounded-full flex items-center justify-center
${active ? 'text-[#6C63FF]' : 'text-[#6B7280]'}
`}
style={{ boxShadow: active ? COLORS.shadows.insetDeep : COLORS.shadows.extruded }}
>
<Icon className="w-5 h-5" />
</div>
);
const StatCard = ({ title, value, change, isPositive }: { title: string, value: string, change: string, isPositive: boolean }) => (
<NeumorphicCard className="flex flex-col justify-between h-40">
<div className="flex justify-between items-start">
<h3 className="text-[#6B7280] font-medium text-sm font-dm-sans">{title}</h3>
<div
className={`w-10 h-10 rounded-full flex items-center justify-center ${isPositive ? 'text-[#38B2AC]' : 'text-red-500'}`}
style={{ boxShadow: COLORS.shadows.insetSmall }}
>
{isPositive ? <ArrowUpRight size={18} /> : <ArrowDownRight size={18} />}
</div>
</div>
<div>
<h2 className="text-3xl font-extrabold text-[#3D4852] font-jakarta tracking-tight">{value}</h2>
<p className={`text-sm font-medium mt-1 ${isPositive ? 'text-[#38B2AC]' : 'text-red-500'}`}>
{change} <span className="text-[#A0AEC0]">vs last week</span>
</p>
</div>
</NeumorphicCard>
);
// --- Main Component ---
export function NeumorphicTradeDashboard() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
return (
<div className="min-h-screen bg-[#E0E5EC] text-[#3D4852] font-dm-sans overflow-x-hidden selection:bg-[#6C63FF] selection:text-white">
{/* Global Styles for Fonts & Scrollbar */}
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@500;600;700;800&family=DM+Sans:wght@400;500;700&display=swap');
.font-jakarta { font-family: 'Plus Jakarta Sans', sans-serif; }
.font-dm-sans { font-family: 'DM Sans', sans-serif; }
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #E0E5EC;
}
::-webkit-scrollbar-thumb {
background: #A0AEC0;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #6B7280;
}
`}</style>
{/* --- Mobile Navigation Overlay --- */}
<AnimatePresence>
{isMobileMenuOpen && (
<motion.div
initial={{ opacity: 0, x: -100 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -100 }}
className="fixed inset-0 z-50 bg-[#E0E5EC] p-6 md:hidden"
>
<div className="flex justify-between items-center mb-8">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-[#6C63FF] flex items-center justify-center text-white font-bold text-lg shadow-lg">
TG
</div>
<span className="font-jakarta font-bold text-xl text-[#3D4852]">TradeGrow</span>
</div>
<button onClick={() => setIsMobileMenuOpen(false)}>
<IconButton icon={X} />
</button>
</div>
<nav className="space-y-6">
<NeumorphicButton active className="w-full justify-start gap-4">
<LayoutDashboard size={20} /> Dashboard
</NeumorphicButton>
<NeumorphicButton className="w-full justify-start gap-4">
<BookOpen size={20} /> Journal
</NeumorphicButton>
<NeumorphicButton className="w-full justify-start gap-4">
<TrendingUp size={20} /> Analytics
</NeumorphicButton>
<NeumorphicButton className="w-full justify-start gap-4">
<Settings size={20} /> Settings
</NeumorphicButton>
</nav>
</motion.div>
)}
</AnimatePresence>
<div className="flex max-w-[1600px] mx-auto">
{/* --- Sidebar (Desktop) --- */}
<aside className="hidden md:flex flex-col w-64 h-screen sticky top-0 py-8 pl-8 pr-4">
<div className="flex items-center gap-3 mb-12 px-2">
<div className="w-12 h-12 rounded-2xl bg-[#E0E5EC] flex items-center justify-center text-[#6C63FF]" style={{ boxShadow: COLORS.shadows.extruded }}>
<TrendingUp size={24} strokeWidth={3} />
</div>
<span className="font-jakarta font-extrabold text-2xl tracking-tight text-[#3D4852]">TradeGrow</span>
</div>
<nav className="space-y-6 flex-1">
<NeumorphicButton active className="w-full justify-start gap-4">
<LayoutDashboard size={20} /> Dashboard
</NeumorphicButton>
<NeumorphicButton className="w-full justify-start gap-4">
<BookOpen size={20} /> Journal
</NeumorphicButton>
<NeumorphicButton className="w-full justify-start gap-4">
<Wallet size={20} /> Accounts
</NeumorphicButton>
<NeumorphicButton className="w-full justify-start gap-4">
<Calendar size={20} /> Calendar
</NeumorphicButton>
</nav>
<div className="mt-auto">
<NeumorphicCard className="p-6 flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-gray-300 overflow-hidden" style={{ boxShadow: COLORS.shadows.inset }}>
<img src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?q=80&w=100&auto=format&fit=crop" alt="User" className="w-full h-full object-cover opacity-80" />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-bold text-[#3D4852] truncate">Alex Trader</p>
<p className="text-xs text-[#6B7280] truncate">Pro Account</p>
</div>
<Settings size={18} className="text-[#6B7280]" />
</NeumorphicCard>
</div>
</aside>
{/* --- Main Content --- */}
<main className="flex-1 p-6 md:p-8 lg:p-12 overflow-y-auto">
{/* Header */}
<header className="flex justify-between items-center mb-12">
<div className="md:hidden">
<button onClick={() => setIsMobileMenuOpen(true)}>
<IconButton icon={Menu} />
</button>
</div>
<div className="hidden md:block">
<h1 className="text-3xl font-jakarta font-extrabold text-[#3D4852]">Dashboard</h1>
<p className="text-[#6B7280] mt-1 font-medium">Welcome back to your growth garden.</p>
</div>
<div className="flex items-center gap-6">
<div className="hidden md:block w-96">
<NeumorphicInput placeholder="Search trades, notes, or tags..." icon={Search} />
</div>
<IconButton icon={Bell} />
</div>
</header>
{/* Stats Grid */}
<section className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-12">
<StatCard title="Total Equity" value="$10,850.00" change="+8.5%" isPositive={true} />
<StatCard title="Win Rate" value="68.4%" change="+2.1%" isPositive={true} />
<StatCard title="Profit Factor" value="2.45" change="-0.2" isPositive={false} />
</section>
{/* Main Chart Section */}
<section className="mb-12">
<div className="flex justify-between items-end mb-6">
<h2 className="text-xl font-jakarta font-bold text-[#3D4852]">Equity Curve</h2>
<div className="flex gap-4">
<NeumorphicButton active className="text-xs py-2 px-4 rounded-xl">1W</NeumorphicButton>
<NeumorphicButton className="text-xs py-2 px-4 rounded-xl">1M</NeumorphicButton>
<NeumorphicButton className="text-xs py-2 px-4 rounded-xl">YTD</NeumorphicButton>
</div>
</div>
<NeumorphicCard className="h-[400px] w-full p-6 relative overflow-hidden">
{/* "Molded" look for the chart container */}
<div className="absolute inset-0 rounded-[32px] pointer-events-none shadow-[inset_10px_10px_20px_rgb(163,177,198,0.3),inset_-10px_-10px_20px_rgba(255,255,255,0.8)] z-10" />
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={equityData}>
<defs>
<linearGradient id="colorValue" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#6C63FF" stopOpacity={0.3}/>
<stop offset="95%" stopColor="#6C63FF" stopOpacity={0}/>
</linearGradient>
</defs>
<Tooltip
contentStyle={{
backgroundColor: '#E0E5EC',
borderRadius: '12px',
border: 'none',
boxShadow: '9px 9px 16px rgb(163,177,198,0.6), -9px -9px 16px rgba(255,255,255,0.5)',
color: '#3D4852'
}}
/>
<XAxis
dataKey="date"
axisLine={false}
tickLine={false}
tick={{ fill: '#6B7280', fontSize: 12 }}
dy={10}
/>
<YAxis
axisLine={false}
tickLine={false}
tick={{ fill: '#6B7280', fontSize: 12 }}
dx={-10}
/>
<Area
type="monotone"
dataKey="value"
stroke="#6C63FF"
strokeWidth={4}
fillOpacity={1}
fill="url(#colorValue)"
/>
</AreaChart>
</ResponsiveContainer>
</NeumorphicCard>
</section>
{/* Bottom Grid: Journal & Trades */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Quick Journal */}
<section className="lg:col-span-2">
<h2 className="text-xl font-jakarta font-bold text-[#3D4852] mb-6">Daily Reflection</h2>
<NeumorphicCard className="h-auto">
<div className="mb-6">
<label className="block text-sm font-bold text-[#6B7280] mb-4 ml-1">What went well today?</label>
<textarea
className="w-full h-32 bg-[#E0E5EC] rounded-2xl p-6 text-[#3D4852] placeholder-[#A0AEC0] outline-none resize-none transition-all duration-300 focus:ring-2 focus:ring-[#6C63FF] focus:ring-offset-2 focus:ring-offset-[#E0E5EC]"
style={{ boxShadow: COLORS.shadows.insetDeep }}
placeholder="I stuck to my plan and waited for the perfect setup..."
/>
</div>
<div className="flex justify-end">
<NeumorphicButton active className="bg-[#E0E5EC]">
Save Entry
</NeumorphicButton>
</div>
</NeumorphicCard>
</section>
{/* Recent Trades */}
<section>
<div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-jakarta font-bold text-[#3D4852]">Recent Trades</h2>
<button className="text-[#6C63FF] text-sm font-bold">View All</button>
</div>
<div className="space-y-6">
{recentTrades.map((trade) => (
<NeumorphicCard key={trade.id} className="p-5 flex items-center justify-between !rounded-2xl cursor-pointer hover:translate-y-[-2px] transition-transform">
<div className="flex items-center gap-4">
<div
className={`w-12 h-12 rounded-xl flex items-center justify-center font-bold text-sm ${trade.result === 'Win' ? 'text-[#38B2AC]' : 'text-red-500'}`}
style={{ boxShadow: COLORS.shadows.inset }}
>
{trade.pair.split('/')[0]}
</div>
<div>
<h4 className="font-bold text-[#3D4852]">{trade.pair}</h4>
<p className="text-xs text-[#6B7280] font-medium">{trade.type} • {trade.date}</p>
</div>
</div>
<div className={`font-bold ${trade.result === 'Win' ? 'text-[#38B2AC]' : 'text-red-500'}`}>
{trade.amount}
</div>
</NeumorphicCard>
))}
<NeumorphicButton className="w-full !rounded-2xl border-2 border-transparent hover:border-[#E0E5EC]">
<Plus size={20} /> <span className="ml-2">Log New Trade</span>
</NeumorphicButton>
</div>
</section>
</div>
</main>
</div>
</div>
);
}
export default NeumorphicTradeDashboard;
~~~
Implementation Guidelines
1. Analyze the component structure, styling, animation implementations
2. Review the component's arguments and state
3. Think through what is the best place to adopt this component/style into the design we are doing
4. Then adopt the component/design to our current system
Help me integrate this into my design