refactor(ui): normalize trade profile controls
This commit is contained in:
parent
1300de98a9
commit
cdc0e57ae9
@ -28,6 +28,7 @@ import { Button } from './ui/button';
|
|||||||
import { Card } from './ui/card';
|
import { Card } from './ui/card';
|
||||||
import { Input } from './ui/input';
|
import { Input } from './ui/input';
|
||||||
import { Select } from './ui/select';
|
import { Select } from './ui/select';
|
||||||
|
import { Textarea } from './ui/Primitives';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
// ChatControl is now rendered globally in App.tsx
|
// ChatControl is now rendered globally in App.tsx
|
||||||
|
|
||||||
@ -254,16 +255,19 @@ export const summarizePortfolioStats = (
|
|||||||
// --- UI COMPONENTS ---
|
// --- UI COMPONENTS ---
|
||||||
|
|
||||||
const ToggleSwitch = ({ checked, onChange }: { checked: boolean, onChange: (v: boolean) => void }) => (
|
const ToggleSwitch = ({ checked, onChange }: { checked: boolean, onChange: (v: boolean) => void }) => (
|
||||||
<button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
role="switch"
|
||||||
|
aria-checked={checked}
|
||||||
onClick={(e) => { e.stopPropagation(); onChange(!checked); }}
|
onClick={(e) => { e.stopPropagation(); onChange(!checked); }}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative inline-flex h-5 w-9 items-center rounded-full border transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]',
|
'relative inline-flex h-5 min-h-0 w-9 items-center rounded-full border px-0 py-0 transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]',
|
||||||
checked ? 'border-[var(--accent)] bg-[var(--accent)]' : 'border-[var(--border)] bg-[var(--muted)]'
|
checked ? 'border-[var(--accent)] bg-[var(--accent)]' : 'border-[var(--border)] bg-[var(--muted)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className={`${checked ? 'translate-x-5 bg-white' : 'translate-x-1 bg-[var(--muted-foreground)]'} inline-block h-3 w-3 transform rounded-full transition-transform duration-200`} />
|
<span className={`${checked ? 'translate-x-5 bg-white' : 'translate-x-1 bg-[var(--muted-foreground)]'} inline-block h-3 w-3 transform rounded-full transition-transform duration-200`} />
|
||||||
</button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Slider = ({ value, onChange, min, max, step, unit, label }: { value: number, onChange: (n: number) => void, min: number, max: number, step?: number, unit?: string, label: string }) => (
|
const Slider = ({ value, onChange, min, max, step, unit, label }: { value: number, onChange: (n: number) => void, min: number, max: number, step?: number, unit?: string, label: string }) => (
|
||||||
@ -272,10 +276,11 @@ const Slider = ({ value, onChange, min, max, step, unit, label }: { value: numbe
|
|||||||
<span className={labelClass}>{label}</span>
|
<span className={labelClass}>{label}</span>
|
||||||
<span className="rounded-lg bg-[var(--accent-soft)] px-2.5 py-0.5 font-mono text-xs font-bold text-[var(--accent)]">{value}{unit}</span>
|
<span className="rounded-lg bg-[var(--accent-soft)] px-2.5 py-0.5 font-mono text-xs font-bold text-[var(--accent)]">{value}{unit}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<Input
|
||||||
type="range" min={min} max={max} step={step || 1} value={value}
|
type="range" min={min} max={max} step={step || 1} value={value}
|
||||||
|
aria-label={label}
|
||||||
onChange={(e) => onChange(Number(e.target.value))}
|
onChange={(e) => onChange(Number(e.target.value))}
|
||||||
className="h-1 w-full accent-[var(--accent)]"
|
className="h-1 min-h-0 w-full px-0 py-0 accent-[var(--accent)]"
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-between font-mono text-[9px] text-[var(--muted-foreground)]">
|
<div className="flex justify-between font-mono text-[9px] text-[var(--muted-foreground)]">
|
||||||
<span>{min}{unit}</span>
|
<span>{min}{unit}</span>
|
||||||
@ -934,17 +939,20 @@ export const TradeProfileManager = ({ botState = DEFAULT_BOT_STATE }: TradeProfi
|
|||||||
{ id: 'logic' as const, label: 'Rules', icon: Cpu },
|
{ id: 'logic' as const, label: 'Rules', icon: Cpu },
|
||||||
{ id: 'advanced' as const, label: 'Risk & Execution', icon: Shield },
|
{ id: 'advanced' as const, label: 'Risk & Execution', icon: Shield },
|
||||||
]).map(t => (
|
]).map(t => (
|
||||||
<button
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
key={t.id}
|
key={t.id}
|
||||||
onClick={() => setDrawerTab(t.id)}
|
onClick={() => setDrawerTab(t.id)}
|
||||||
className={`flex-1 flex items-center justify-center gap-1.5 py-2 rounded-md text-[10px] font-bold uppercase tracking-wider transition-all ${drawerTab === t.id
|
className={`flex-1 flex items-center justify-center gap-1.5 rounded-md py-2 text-[10px] font-bold uppercase tracking-wider transition-all ${drawerTab === t.id
|
||||||
? 'bg-[var(--card)] text-[var(--foreground)] shadow-sm'
|
? 'bg-[var(--card)] text-[var(--foreground)] shadow-sm'
|
||||||
: 'text-[var(--muted-foreground)] hover:text-[var(--foreground)]'
|
: 'text-[var(--muted-foreground)] hover:text-[var(--foreground)]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<t.icon size={11} />
|
<t.icon size={11} />
|
||||||
{t.label}
|
{t.label}
|
||||||
</button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -979,10 +987,10 @@ export const TradeProfileManager = ({ botState = DEFAULT_BOT_STATE }: TradeProfi
|
|||||||
{/* Trading Symbols */}
|
{/* Trading Symbols */}
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className={labelClass}>Trading Symbols</label>
|
<label className={labelClass}>Trading Symbols</label>
|
||||||
<textarea
|
<Textarea
|
||||||
value={editingProfile.symbols || ''}
|
value={editingProfile.symbols || ''}
|
||||||
onChange={e => setEditingProfile({ ...editingProfile, symbols: e.target.value })}
|
onChange={e => setEditingProfile({ ...editingProfile, symbols: e.target.value })}
|
||||||
className="h-20 w-full resize-none rounded-xl border border-[var(--border)] bg-[var(--input)] px-4 py-2.5 font-mono text-sm text-[var(--foreground)] outline-none transition placeholder:text-[var(--muted-foreground)] focus:border-[var(--ring)] focus:ring-2 focus:ring-[var(--ring-soft)]"
|
className="h-20 w-full resize-none font-mono text-sm"
|
||||||
placeholder="BTC/USDT, ETH/USDT, SOL/USDT"
|
placeholder="BTC/USDT, ETH/USDT, SOL/USDT"
|
||||||
/>
|
/>
|
||||||
<p className="text-[9px] text-[var(--muted-foreground)]">Comma-separated trading pairs</p>
|
<p className="text-[9px] text-[var(--muted-foreground)]">Comma-separated trading pairs</p>
|
||||||
@ -1046,7 +1054,10 @@ export const TradeProfileManager = ({ botState = DEFAULT_BOT_STATE }: TradeProfi
|
|||||||
const isMandatory = currentType === 'mandatory';
|
const isMandatory = currentType === 'mandatory';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const newRules = editingProfile.strategy_config?.rules?.map(r =>
|
const newRules = editingProfile.strategy_config?.rules?.map(r =>
|
||||||
@ -1058,13 +1069,13 @@ export const TradeProfileManager = ({ botState = DEFAULT_BOT_STATE }: TradeProfi
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
title="Click to toggle Mandatory/Voting"
|
title="Click to toggle Mandatory/Voting"
|
||||||
className={`px-1.5 py-0.5 rounded text-[8px] font-bold uppercase tracking-tighter border transition-all ${isMandatory
|
className={`min-h-0 rounded border px-1.5 py-0.5 text-[8px] font-bold uppercase tracking-tighter transition-all ${isMandatory
|
||||||
? 'bg-amber-500/10 text-amber-500 border-amber-500/20 hover:bg-amber-500/20'
|
? 'bg-amber-500/10 text-amber-500 border-amber-500/20 hover:bg-amber-500/20'
|
||||||
: 'bg-blue-500/10 text-blue-400 border-blue-500/20 hover:bg-blue-500/20'
|
: 'bg-blue-500/10 text-blue-400 border-blue-500/20 hover:bg-blue-500/20'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isMandatory ? 'Mandatory' : 'Voting'}
|
{isMandatory ? 'Mandatory' : 'Voting'}
|
||||||
</button>
|
</Button>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
@ -1231,7 +1242,10 @@ export const TradeProfileManager = ({ botState = DEFAULT_BOT_STATE }: TradeProfi
|
|||||||
<label className={labelClass}>Order Type</label>
|
<label className={labelClass}>Order Type</label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{(['market', 'limit'] as const).map(t => (
|
{(['market', 'limit'] as const).map(t => (
|
||||||
<button
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
key={t}
|
key={t}
|
||||||
onClick={() => setEditingProfile({
|
onClick={() => setEditingProfile({
|
||||||
...editingProfile,
|
...editingProfile,
|
||||||
@ -1246,7 +1260,7 @@ export const TradeProfileManager = ({ botState = DEFAULT_BOT_STATE }: TradeProfi
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{t}
|
{t}
|
||||||
</button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1257,7 +1271,10 @@ export const TradeProfileManager = ({ botState = DEFAULT_BOT_STATE }: TradeProfi
|
|||||||
{ value: 'both', label: 'Both Sides' },
|
{ value: 'both', label: 'Both Sides' },
|
||||||
{ value: 'long_only', label: 'Long Only' }
|
{ value: 'long_only', label: 'Long Only' }
|
||||||
] as const).map(mode => (
|
] as const).map(mode => (
|
||||||
<button
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
key={mode.value}
|
key={mode.value}
|
||||||
onClick={() => setEditingProfile({
|
onClick={() => setEditingProfile({
|
||||||
...editingProfile,
|
...editingProfile,
|
||||||
@ -1272,7 +1289,7 @@ export const TradeProfileManager = ({ botState = DEFAULT_BOT_STATE }: TradeProfi
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{mode.label}
|
{mode.label}
|
||||||
</button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className={helpTextClass}>
|
<p className={helpTextClass}>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user