refactor(ui): systematize screener controls

This commit is contained in:
Saravana Achu Mac 2026-05-08 21:37:58 -07:00
parent 3375dfcfce
commit 4d2f18ea45
3 changed files with 121 additions and 51 deletions

View File

@ -1783,12 +1783,24 @@ body {
min-width: 0;
}
.screener-search-icon {
position: absolute;
top: 50%;
left: 10px;
color: var(--muted-foreground);
transform: translateY(-50%);
}
.screener-search-input {
padding-left: 32px !important;
}
.screener-filter-row input,
.screener-filter-row select {
min-height: 40px;
}
.screener-filter-row > select {
.screener-cap-select {
width: 100% !important;
}
@ -1800,6 +1812,85 @@ body {
gap: 7px;
}
.screener-sector-chip {
min-height: 32px !important;
border-radius: 999px !important;
padding: 0 12px !important;
font-size: 11px !important;
font-weight: 700 !important;
}
.screener-sector-chip[data-active="true"] {
border-color: var(--accent) !important;
background: var(--accent-soft) !important;
color: var(--accent) !important;
}
.screener-more-select {
width: 150px;
border-radius: 999px !important;
font-weight: 650;
}
.screener-more-select[data-active="true"] {
border-color: var(--accent) !important;
background: var(--accent-soft) !important;
color: var(--accent) !important;
}
.screener-results-grid {
display: grid;
grid-template-columns: 100px minmax(220px, 1fr) 90px 90px 110px 80px 110px;
align-items: center;
gap: 0;
padding: 10px 16px;
}
.screener-sort-button {
justify-content: flex-start !important;
min-height: 28px !important;
padding: 0 !important;
border: 0 !important;
background: transparent !important;
color: var(--muted-foreground) !important;
box-shadow: none !important;
}
.screener-sort-icon {
margin-left: 4px;
color: var(--muted-foreground);
font-size: 10px;
}
.screener-sort-icon[data-active="true"] {
color: var(--accent);
}
.screener-symbol-cell,
.screener-price-cell {
color: var(--foreground);
font-weight: 750;
}
.screener-change-cell {
font-size: 12px;
font-weight: 750;
}
.screener-change-cell.is-positive {
color: var(--bl-success);
}
.screener-change-cell.is-negative {
color: var(--bl-danger);
}
.screener-results-summary {
margin-top: 8px;
color: var(--muted-foreground);
font-size: 11px;
}
.positions-tab,
.history-tab {
width: 100%;

View File

@ -65,11 +65,8 @@ describe('ScreenerView sector filters', () => {
await user.selectOptions(moreSectors, 'Energy');
expect(moreSectors).toHaveValue('Energy');
expect(moreSectors).toHaveStyle({
background: 'var(--accent-soft)',
color: 'var(--primary)',
fontWeight: '700',
});
expect(moreSectors).toHaveClass('screener-more-select');
expect(moreSectors).toHaveAttribute('data-active', 'true');
await waitFor(() => expect(globalThis.fetch).toHaveBeenCalledTimes(2));
expect(String((globalThis.fetch as any).mock.calls[1][0])).toContain('sector=Energy');
});

View File

@ -126,7 +126,7 @@ export function ScreenerView() {
};
const SortIcon = ({ k }: { k: keyof ScreenerRow }) => (
<span style={{ color: sortKey === k ? 'var(--primary)' : 'var(--muted-foreground)', marginLeft: 3, fontSize: 10 }}>
<span className="screener-sort-icon" data-active={sortKey === k}>
{sortKey === k ? (sortAsc ? '▲' : '▼') : '⇅'}
</span>
);
@ -180,23 +180,20 @@ export function ScreenerView() {
<CardContent>
<div className="screener-filter-row">
<div className="screener-search-field">
<Search size={14} style={{
position: 'absolute', left: 10, top: '50%',
transform: 'translateY(-50%)', color: 'var(--muted-foreground)',
}} />
<Search size={14} className="screener-search-icon" />
<Input
type="text"
placeholder="Filter by name or ticker…"
value={query}
onChange={(e: ChangeEvent<HTMLInputElement>) => setQuery(e.target.value)}
style={{ paddingLeft: 32 }}
className="screener-search-input"
/>
</div>
<Select
value={String(capIdx)}
onChange={(e: ChangeEvent<HTMLSelectElement>) => setCapIdx(Number(e.target.value))}
style={{ width: 180 }}
className="screener-cap-select"
options={CAP_OPTIONS.map((c, i) => ({ value: String(i), label: c.label }))}
/>
@ -208,14 +205,8 @@ export function ScreenerView() {
onClick={() => setSector(s)}
variant={sector === s ? 'secondary' : 'outline'}
size="sm"
style={{
padding: '5px 10px', borderRadius: 20,
border: '1px solid', fontSize: 11, fontWeight: 600,
borderColor: sector === s ? 'var(--primary)' : 'var(--border)',
background: sector === s ? 'var(--accent-soft)' : 'var(--card)',
color: sector === s ? 'var(--primary)' : 'var(--muted-foreground)',
fontFamily: 'inherit',
}}
className="screener-sector-chip"
data-active={sector === s}
>
{s}
</Button>
@ -228,14 +219,8 @@ export function ScreenerView() {
{ value: '', label: 'More sectors…' },
...SECTORS.slice(6).map(s => ({ value: s, label: s })),
]}
style={{
width: 140,
borderRadius: 999,
borderColor: moreSectorSelected ? 'var(--primary)' : 'var(--border)',
background: moreSectorSelected ? 'var(--accent-soft)' : 'var(--card)',
color: moreSectorSelected ? 'var(--primary)' : 'var(--muted-foreground)',
fontWeight: moreSectorSelected ? 700 : 500,
}}
className="screener-more-select"
data-active={moreSectorSelected}
/>
</div>
</div>
@ -249,11 +234,7 @@ export function ScreenerView() {
)}
<div className="ux-data-grid">
<div className="ux-data-grid-header" style={{
display: 'grid',
gridTemplateColumns: '100px 1fr 90px 90px 110px 80px 110px',
padding: '10px 16px',
}}>
<div className="ux-data-grid-header screener-results-grid">
{([
['symbol', 'Symbol'],
['companyName', 'Company'],
@ -263,14 +244,16 @@ export function ScreenerView() {
['pe', 'P/E'],
['volume', 'Volume'],
] as [keyof ScreenerRow, string][]).map(([key, label]) => (
<span
<Button
type="button"
key={key}
onClick={() => handleSort(key)}
className="ux-data-grid-head"
style={{ cursor: 'pointer' }}
variant="ghost"
size="sm"
className="ux-data-grid-head screener-sort-button"
>
{label}<SortIcon k={key} />
</span>
</Button>
))}
</div>
@ -280,23 +263,22 @@ export function ScreenerView() {
<div
key={row.symbol}
onClick={() => handleRowClick(row.symbol)}
className="ux-data-grid-row"
style={{
display: 'grid',
gridTemplateColumns: '100px 1fr 90px 90px 110px 80px 110px',
padding: '11px 16px',
alignItems: 'center',
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleRowClick(row.symbol);
}
}}
className="ux-data-grid-row screener-results-grid"
role="button"
tabIndex={0}
>
<span className="ux-data-grid-cell" style={{ fontWeight: 700, color: 'var(--primary)' }}>{row.symbol}</span>
<span className="ux-data-grid-cell screener-symbol-cell">{row.symbol}</span>
<span className="ux-data-grid-cell">{row.companyName}</span>
<span className="ux-data-grid-cell" style={{ fontWeight: 600 }}>
<span className="ux-data-grid-cell screener-price-cell">
{row.price != null ? `$${row.price.toFixed(2)}` : '—'}
</span>
<span style={{
fontSize: 12, fontWeight: 600,
color: row.changesPercentage >= 0 ? 'var(--bl-success)' : 'var(--bl-danger)',
}}>
<span className={`screener-change-cell ${row.changesPercentage >= 0 ? 'is-positive' : 'is-negative'}`}>
{row.changesPercentage >= 0 ? '+' : ''}{row.changesPercentage?.toFixed(2)}%
</span>
<span className="ux-data-grid-cell">
@ -322,7 +304,7 @@ export function ScreenerView() {
</div>
{!loading && filtered.length > 0 && (
<div style={{ marginTop: 8, fontSize: 11, color: 'var(--muted-foreground)' }}>
<div className="screener-results-summary">
{filtered.length} companies · Click any row to view chart & research
</div>
)}