refactor(web): use @bytelyst/ui CardButton primitive (UI audit #9)
Now that @bytelyst/ui@0.1.6 ships <CardButton>, replace the local `.card-button` className shim (added to layout-fixes.css §25 in the prior commit) with the real primitive. Conversions: - components/StrategyWizard.tsx — risk style (3 cards) + hours pickers - views/SimpleView.tsx — new-buy + manage-holding plan cards - tabs/MyStrategiesTab.tsx — diagnostic accordion toggle Removes layout-fixes.css §25 (.card-button) — no longer needed. Updates docs/ui/UI_AUDIT.md Pattern A to reflect that the design-system fix has shipped. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
c10de34a11
commit
63f17bf40e
@ -76,7 +76,7 @@ So a Button intended as a "card-style action" (e.g. a title above a prompt) will
|
|||||||
grep -rEn '<Button[^>]*>.*\n[^<]*<' --include='*.tsx' web/src
|
grep -rEn '<Button[^>]*>.*\n[^<]*<' --include='*.tsx' web/src
|
||||||
```
|
```
|
||||||
|
|
||||||
**Recommendation for design system:** add a documented `<CardButton>` primitive that drops `whitespace-nowrap` and removes fixed height, OR add an `as="card"` variant to `<Button>`. This is upstream work in `learning_ai_common_plat`.
|
**Recommendation for design system:** ✅ shipped in `@bytelyst/ui@0.1.6` as `<CardButton>`. Drops `whitespace-nowrap`, removes fixed height, preserves the focus-visible ring. Use it whenever a `<Button>` would otherwise wrap a `<div>` or stacked `<span>`s. The 5 Pattern A sites in this repo were converted in commit `c10de34`+ (StrategyWizard ×2, SimpleView ×2, MyStrategiesTab ×1).
|
||||||
|
|
||||||
### Pattern B — Inline styles instead of classes
|
### Pattern B — Inline styles instead of classes
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import { createTradeProfile, updateTradeProfile } from '../lib/profileApi';
|
|||||||
import { Button } from './ui/button';
|
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 { CardButton } from '@bytelyst/ui';
|
||||||
|
|
||||||
interface WizardState {
|
interface WizardState {
|
||||||
step: number;
|
step: number;
|
||||||
@ -185,11 +186,10 @@ export const StrategyWizard: React.FC<{
|
|||||||
const isLocked = !isFeatureAllowed(tier, 'risk_style', style.id);
|
const isLocked = !isFeatureAllowed(tier, 'risk_style', style.id);
|
||||||
return (
|
return (
|
||||||
<div key={style.id} className="relative">
|
<div key={style.id} className="relative">
|
||||||
<button
|
<CardButton
|
||||||
type="button"
|
|
||||||
disabled={isLocked}
|
disabled={isLocked}
|
||||||
onClick={() => setState({ ...state, riskStyle: style })}
|
onClick={() => setState({ ...state, riskStyle: style })}
|
||||||
className={`card-button ${optionBaseClass} ${isLocked ? 'cursor-not-allowed opacity-40 grayscale' : 'hover:scale-[1.01] active:scale-[0.99]'
|
className={`${optionBaseClass} ${isLocked ? 'cursor-not-allowed opacity-40 grayscale' : 'hover:scale-[1.01] active:scale-[0.99]'
|
||||||
} ${state.riskStyle?.id === style.id
|
} ${state.riskStyle?.id === style.id
|
||||||
? optionSelectedClass
|
? optionSelectedClass
|
||||||
: optionIdleClass
|
: optionIdleClass
|
||||||
@ -212,7 +212,7 @@ export const StrategyWizard: React.FC<{
|
|||||||
<p className="text-sm leading-relaxed text-[var(--muted-foreground)]">{style.description}</p>
|
<p className="text-sm leading-relaxed text-[var(--muted-foreground)]">{style.description}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</CardButton>
|
||||||
{isLocked && (
|
{isLocked && (
|
||||||
<div className="absolute top-4 right-4 text-[9px] font-black uppercase text-amber-500/80 tracking-tighter bg-amber-500/10 px-2 py-0.5 rounded border border-amber-500/20">
|
<div className="absolute top-4 right-4 text-[9px] font-black uppercase text-amber-500/80 tracking-tighter bg-amber-500/10 px-2 py-0.5 rounded border border-amber-500/20">
|
||||||
Pro/Elite Only
|
Pro/Elite Only
|
||||||
@ -338,11 +338,10 @@ export const StrategyWizard: React.FC<{
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 gap-4">
|
<div className="grid grid-cols-1 gap-4">
|
||||||
{(['24/7', 'London + New York', 'Asia only'] as const).map(option => (
|
{(['24/7', 'London + New York', 'Asia only'] as const).map(option => (
|
||||||
<button
|
<CardButton
|
||||||
type="button"
|
|
||||||
key={option}
|
key={option}
|
||||||
onClick={() => setState({ ...state, hours: option })}
|
onClick={() => setState({ ...state, hours: option })}
|
||||||
className={`card-button rounded-2xl border-2 p-6 text-left transition-all ${state.hours === option
|
className={`rounded-2xl border-2 p-6 text-left transition-all ${state.hours === option
|
||||||
? optionSelectedClass
|
? optionSelectedClass
|
||||||
: optionIdleClass
|
: optionIdleClass
|
||||||
}`}
|
}`}
|
||||||
@ -360,7 +359,7 @@ export const StrategyWizard: React.FC<{
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</CardButton>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -494,35 +494,3 @@
|
|||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
|
||||||
Section 25 — card-button utility (UI audit Pattern A)
|
|
||||||
Use on a native <button> when you need a button-shaped CARD with stacked
|
|
||||||
block content. The @bytelyst/ui Button primitive has whitespace-nowrap +
|
|
||||||
fixed h-{size} that collapses multi-line content. This class restores the
|
|
||||||
focus ring + reset behavior without those constraints.
|
|
||||||
--------------------------------------------------------------------------- */
|
|
||||||
.card-button {
|
|
||||||
appearance: none;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
font: inherit;
|
|
||||||
color: inherit;
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
text-align: left;
|
|
||||||
white-space: normal;
|
|
||||||
height: auto;
|
|
||||||
transition: background-color 150ms ease, border-color 150ms ease, box-shadow 150ms ease;
|
|
||||||
}
|
|
||||||
.card-button:disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
.card-button:focus-visible {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 0 0 2px var(--bl-bg-canvas, #0b0f17),
|
|
||||||
0 0 0 4px var(--bl-focus-ring, var(--bl-accent, #5A8CFF));
|
|
||||||
border-radius: inherit;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import { BacktestRunnerPanel } from '../backtest/components/BacktestRunnerPanel'
|
|||||||
import { useBacktestFeatureGate } from '../backtest/useBacktestFeatureGate';
|
import { useBacktestFeatureGate } from '../backtest/useBacktestFeatureGate';
|
||||||
import { deleteTradeProfile, fetchTradeProfiles, setTradeProfileActive } from '../lib/profileApi';
|
import { deleteTradeProfile, fetchTradeProfiles, setTradeProfileActive } from '../lib/profileApi';
|
||||||
import { Button, IconButton } from '../components/ui/Primitives';
|
import { Button, IconButton } from '../components/ui/Primitives';
|
||||||
|
import { CardButton } from '@bytelyst/ui';
|
||||||
|
|
||||||
function getStrategyKindLabel(config: any) {
|
function getStrategyKindLabel(config: any) {
|
||||||
if (config?.type === 'visual') return 'Visual Builder';
|
if (config?.type === 'visual') return 'Visual Builder';
|
||||||
@ -150,10 +151,8 @@ const ActiveStrategyCard: React.FC<{
|
|||||||
|
|
||||||
{/* 5. Health Diagnostic (Education Layer) */}
|
{/* 5. Health Diagnostic (Education Layer) */}
|
||||||
<div style={{ marginBottom: '32px' }}>
|
<div style={{ marginBottom: '32px' }}>
|
||||||
<button
|
<CardButton
|
||||||
type="button"
|
|
||||||
onClick={() => onToggleExpand(profile.id)}
|
onClick={() => onToggleExpand(profile.id)}
|
||||||
className="card-button"
|
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '16px',
|
padding: '16px',
|
||||||
@ -190,7 +189,7 @@ const ActiveStrategyCard: React.FC<{
|
|||||||
<p style={{ fontSize: '11px', color: 'var(--bl-warning)', margin: 0, fontStyle: 'italic', fontWeight: 600 }}>{explanation.recommendation}</p>
|
<p style={{ fontSize: '11px', color: 'var(--bl-warning)', margin: 0, fontStyle: 'italic', fontWeight: 600 }}>{explanation.recommendation}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</button>
|
</CardButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 6. Action */}
|
{/* 6. Action */}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import {
|
|||||||
type TriggerMode,
|
type TriggerMode,
|
||||||
} from './tradePlansState';
|
} from './tradePlansState';
|
||||||
import { useTradePlansNavigationState } from './useTradePlansNavigationState';
|
import { useTradePlansNavigationState } from './useTradePlansNavigationState';
|
||||||
|
import { CardButton } from '@bytelyst/ui';
|
||||||
|
|
||||||
type SimpleHolding = {
|
type SimpleHolding = {
|
||||||
symbol: string;
|
symbol: string;
|
||||||
@ -1006,14 +1007,13 @@ export function SimpleView() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-3 md:grid-cols-2">
|
<div className="grid gap-3 md:grid-cols-2">
|
||||||
<button
|
<CardButton
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch({ type: 'clear-feedback' });
|
dispatch({ type: 'clear-feedback' });
|
||||||
dispatch({ type: 'set-selected-holding-trade-id', value: null });
|
dispatch({ type: 'set-selected-holding-trade-id', value: null });
|
||||||
updateDraft('side', 'buy');
|
updateDraft('side', 'buy');
|
||||||
}}
|
}}
|
||||||
className={`card-button h-auto justify-start rounded-[1.25rem] border px-5 py-5 text-left transition ${
|
className={`h-auto justify-start rounded-[1.25rem] border px-5 py-5 text-left transition ${
|
||||||
draft.side === 'buy'
|
draft.side === 'buy'
|
||||||
? 'border-[var(--primary)] bg-[var(--accent-soft)]'
|
? 'border-[var(--primary)] bg-[var(--accent-soft)]'
|
||||||
: 'border-[var(--border)] bg-[var(--card-elevated)]'
|
: 'border-[var(--border)] bg-[var(--card-elevated)]'
|
||||||
@ -1023,9 +1023,8 @@ export function SimpleView() {
|
|||||||
<div className="text-sm font-bold text-[var(--foreground)]">New short-term buy plan</div>
|
<div className="text-sm font-bold text-[var(--foreground)]">New short-term buy plan</div>
|
||||||
<div className="mt-2 text-sm font-normal leading-6 text-[var(--muted-foreground)]">Arm a dip-buy trigger and let the app manage the profit exit after fill.</div>
|
<div className="mt-2 text-sm font-normal leading-6 text-[var(--muted-foreground)]">Arm a dip-buy trigger and let the app manage the profit exit after fill.</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</CardButton>
|
||||||
<button
|
<CardButton
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch({ type: 'clear-feedback' });
|
dispatch({ type: 'clear-feedback' });
|
||||||
if (availableSellHoldings.length > 0) {
|
if (availableSellHoldings.length > 0) {
|
||||||
@ -1034,7 +1033,7 @@ export function SimpleView() {
|
|||||||
updateDraft('side', 'sell');
|
updateDraft('side', 'sell');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={`card-button h-auto justify-start rounded-[1.25rem] border px-5 py-5 text-left transition ${
|
className={`h-auto justify-start rounded-[1.25rem] border px-5 py-5 text-left transition ${
|
||||||
draft.side === 'sell'
|
draft.side === 'sell'
|
||||||
? 'border-[var(--primary)] bg-[var(--accent-soft)]'
|
? 'border-[var(--primary)] bg-[var(--accent-soft)]'
|
||||||
: 'border-[var(--border)] bg-[var(--card-elevated)]'
|
: 'border-[var(--border)] bg-[var(--card-elevated)]'
|
||||||
@ -1044,7 +1043,7 @@ export function SimpleView() {
|
|||||||
<div className="text-sm font-bold text-[var(--foreground)]">Manage an existing holding</div>
|
<div className="text-sm font-bold text-[var(--foreground)]">Manage an existing holding</div>
|
||||||
<div className="mt-2 text-sm font-normal leading-6 text-[var(--muted-foreground)]">Choose a filled holding and place it back under managed profit-taking.</div>
|
<div className="mt-2 text-sm font-normal leading-6 text-[var(--muted-foreground)]">Choose a filled holding and place it back under managed profit-taking.</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</CardButton>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user