Skip to content

Commit 0418207

Browse files
V48 Gate 3 (implementation-only): compact filter mosaic + SearchableSelect dropdowns in the transactions filter bar
The pipelines/terminal/exchange filter bar no longer stacks one full-width filter per row below xl: the grid is a compact mosaic at every width (2 columns base, 4 at tablet, one row at xl) with the search cell spanning the first row — the bar spends horizontal space instead of vertical. Every native <select> is replaced with the shared rich dropdown (SearchableSelect, the component extracted from the repository picker): matching combobox styling, text searching inside each dropdown, badge/ description support, keyboard behavior — status, ownership, action lens, repository, participant, proof posture, and sort. Tests: filter-bar suite extended — all seven controls are comboboxes, dropdown selection emits filter changes, in-dropdown text search filters options. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 92142f8 commit 0418207

2 files changed

Lines changed: 202 additions & 158 deletions

File tree

uapi/components/base/bitcode/execution/BitcodeTransactionsFilterBar.tsx

Lines changed: 130 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React from 'react';
44

55
import BitcodeInlineExplainer from './BitcodeInlineExplainer';
66
import { BITCODE_TRANSACTION_FILTER_EXPLAINERS } from './bitcode-transaction-explainers';
7+
import { SearchableSelect } from '@/components/base/bitcode/forms/SearchableSelect';
78
import type { TransactionFilters, TransactionOwnership, TransactionSort } from './bitcode-transaction-types';
89

910
interface BitcodeTransactionsFilterBarProps {
@@ -16,6 +17,33 @@ interface BitcodeTransactionsFilterBarProps {
1617
proofStatusOptions: string[];
1718
}
1819

20+
// The one rich searchable dropdown (SearchableSelect, extracted from the
21+
// repository picker) styled for the dark filter mosaic.
22+
const FILTER_TRIGGER_CLASS =
23+
'mt-1.5 h-9 rounded-xl border-white/10 bg-[rgba(10,15,30,0.88)] px-3 text-sm text-white hover:bg-white/10 hover:text-white';
24+
25+
function FilterCell({
26+
label,
27+
explainer,
28+
children,
29+
className,
30+
}: {
31+
label: string;
32+
explainer: (typeof BITCODE_TRANSACTION_FILTER_EXPLAINERS)[keyof typeof BITCODE_TRANSACTION_FILTER_EXPLAINERS];
33+
children: React.ReactNode;
34+
className?: string;
35+
}) {
36+
return (
37+
<div className={`rounded-[1rem] border border-white/8 bg-white/5 px-2.5 py-2 ${className ?? ''}`}>
38+
<span className="flex items-center gap-2 text-[0.6rem] uppercase tracking-[0.16em] text-neutral-500">
39+
<span>{label}</span>
40+
<BitcodeInlineExplainer explainer={explainer} />
41+
</span>
42+
{children}
43+
</div>
44+
);
45+
}
46+
1947
export default function BitcodeTransactionsFilterBar({
2048
filters,
2149
onFiltersChange,
@@ -35,13 +63,20 @@ export default function BitcodeTransactionsFilterBar({
3563
onFiltersChange({ ...filters, [key]: value });
3664
};
3765

66+
const withAll = (allLabel: string, options: string[]) => [
67+
{ key: 'all', label: allLabel },
68+
...options.map((option) => ({ key: option, label: option })),
69+
];
70+
3871
return (
39-
<div className="mt-4 grid gap-2 xl:grid-cols-[minmax(0,1.4fr)_repeat(6,minmax(0,0.76fr))]">
40-
<label className="rounded-[1rem] border border-white/8 bg-white/5 px-3 py-2.5">
41-
<span className="flex items-center gap-2 text-[0.62rem] uppercase tracking-[0.18em] text-neutral-500">
42-
<span>Search transactions</span>
43-
<BitcodeInlineExplainer explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.search} />
44-
</span>
72+
// Compact mosaic: multi-column at every width (never a one-filter-per-row
73+
// stack) so the bar spends horizontal space instead of vertical.
74+
<div className="mt-4 grid grid-cols-2 gap-2 tablet:grid-cols-4 xl:grid-cols-[minmax(0,1.6fr)_repeat(7,minmax(0,0.8fr))]">
75+
<FilterCell
76+
label="Search transactions"
77+
explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.search}
78+
className="col-span-2 tablet:col-span-4 xl:col-span-1"
79+
>
4580
<input
4681
aria-label="Search transactions"
4782
value={searchValue}
@@ -51,145 +86,112 @@ export default function BitcodeTransactionsFilterBar({
5186
updateFilter('searchTerm', nextValue);
5287
}}
5388
placeholder="Search ids, repos, branches, proof posture, participants…"
54-
className="mt-2 w-full rounded-xl border border-white/10 bg-[rgba(10,15,30,0.88)] px-3 py-2 text-sm text-white outline-none transition placeholder:text-neutral-500 focus:border-emerald-400/40"
89+
className="mt-1.5 h-9 w-full rounded-xl border border-white/10 bg-[rgba(10,15,30,0.88)] px-3 text-sm text-white outline-none transition placeholder:text-neutral-500 focus:border-emerald-400/40"
5590
/>
56-
</label>
57-
58-
<label className="rounded-[1rem] border border-white/8 bg-white/5 px-3 py-2.5">
59-
<span className="flex items-center gap-2 text-[0.62rem] uppercase tracking-[0.18em] text-neutral-500">
60-
<span>Status</span>
61-
<BitcodeInlineExplainer explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.status} />
62-
</span>
63-
<select
91+
</FilterCell>
92+
93+
<FilterCell label="Status" explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.status}>
94+
<SearchableSelect
6495
aria-label="Status"
96+
items={withAll('All statuses', statusOptions)}
6597
value={filters.status}
66-
onChange={(event) => updateFilter('status', event.target.value)}
67-
className="mt-2 w-full rounded-xl border border-white/10 bg-[rgba(10,15,30,0.88)] px-3 py-2 text-sm text-white outline-none transition focus:border-emerald-400/40"
68-
>
69-
<option value="all">All statuses</option>
70-
{statusOptions.map((status) => (
71-
<option key={status} value={status}>
72-
{status}
73-
</option>
74-
))}
75-
</select>
76-
</label>
77-
78-
<label className="rounded-[1rem] border border-white/8 bg-white/5 px-3 py-2.5">
79-
<span className="flex items-center gap-2 text-[0.62rem] uppercase tracking-[0.18em] text-neutral-500">
80-
<span>Ownership</span>
81-
<BitcodeInlineExplainer explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.ownership} />
82-
</span>
83-
<select
98+
onSelect={(key) => updateFilter('status', key ?? 'all')}
99+
placeholder="All statuses"
100+
searchPlaceholder="Search statuses…"
101+
className={FILTER_TRIGGER_CLASS}
102+
/>
103+
</FilterCell>
104+
105+
<FilterCell label="Ownership" explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.ownership}>
106+
<SearchableSelect
84107
aria-label="Ownership"
108+
items={[
109+
{ key: 'all', label: 'All participants' },
110+
{ key: 'mine', label: 'My transactions' },
111+
{ key: 'network', label: 'Exchange transactions' },
112+
]}
85113
value={filters.ownership}
86-
onChange={(event) => updateFilter('ownership', event.target.value as TransactionOwnership)}
87-
className="mt-2 w-full rounded-xl border border-white/10 bg-[rgba(10,15,30,0.88)] px-3 py-2 text-sm text-white outline-none transition focus:border-emerald-400/40"
88-
>
89-
<option value="all">All participants</option>
90-
<option value="mine">My transactions</option>
91-
<option value="network">Exchange transactions</option>
92-
</select>
93-
</label>
94-
95-
<label className="rounded-[1rem] border border-white/8 bg-white/5 px-3 py-2.5">
96-
<span className="flex items-center gap-2 text-[0.62rem] uppercase tracking-[0.18em] text-neutral-500">
97-
<span>Action lens</span>
98-
<BitcodeInlineExplainer explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.transactionLens} />
99-
</span>
100-
<select
114+
onSelect={(key) => updateFilter('ownership', (key ?? 'all') as TransactionOwnership)}
115+
placeholder="All participants"
116+
searchPlaceholder="Search ownership…"
117+
className={FILTER_TRIGGER_CLASS}
118+
/>
119+
</FilterCell>
120+
121+
<FilterCell label="Action lens" explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.transactionLens}>
122+
<SearchableSelect
101123
aria-label="Action lens"
124+
items={[
125+
{ key: 'all', label: 'All lenses' },
126+
{ key: 'deposit', label: 'Deposit' },
127+
{ key: 'read', label: 'Read' },
128+
{ key: 'closure', label: 'Closure' },
129+
]}
102130
value={filters.transactionLens}
103-
onChange={(event) => updateFilter('transactionLens', event.target.value as TransactionFilters['transactionLens'])}
104-
className="mt-2 w-full rounded-xl border border-white/10 bg-[rgba(10,15,30,0.88)] px-3 py-2 text-sm text-white outline-none transition focus:border-emerald-400/40"
105-
>
106-
<option value="all">All lenses</option>
107-
<option value="deposit">Deposit</option>
108-
<option value="read">Read</option>
109-
<option value="closure">Closure</option>
110-
</select>
111-
</label>
112-
113-
<label className="rounded-[1rem] border border-white/8 bg-white/5 px-3 py-2.5">
114-
<span className="flex items-center gap-2 text-[0.62rem] uppercase tracking-[0.18em] text-neutral-500">
115-
<span>Repository</span>
116-
<BitcodeInlineExplainer explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.repository} />
117-
</span>
118-
<select
131+
onSelect={(key) =>
132+
updateFilter('transactionLens', (key ?? 'all') as TransactionFilters['transactionLens'])
133+
}
134+
placeholder="All lenses"
135+
searchPlaceholder="Search lenses…"
136+
className={FILTER_TRIGGER_CLASS}
137+
/>
138+
</FilterCell>
139+
140+
<FilterCell label="Repository" explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.repository}>
141+
<SearchableSelect
119142
aria-label="Repository"
143+
items={withAll('All repositories', repositoryOptions)}
120144
value={filters.repository}
121-
onChange={(event) => updateFilter('repository', event.target.value)}
122-
className="mt-2 w-full rounded-xl border border-white/10 bg-[rgba(10,15,30,0.88)] px-3 py-2 text-sm text-white outline-none transition focus:border-emerald-400/40"
123-
>
124-
<option value="all">All repositories</option>
125-
{repositoryOptions.map((repository) => (
126-
<option key={repository} value={repository}>
127-
{repository}
128-
</option>
129-
))}
130-
</select>
131-
</label>
132-
133-
<label className="rounded-[1rem] border border-white/8 bg-white/5 px-3 py-2.5">
134-
<span className="flex items-center gap-2 text-[0.62rem] uppercase tracking-[0.18em] text-neutral-500">
135-
<span>Participant</span>
136-
<BitcodeInlineExplainer explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.participant} />
137-
</span>
138-
<select
145+
onSelect={(key) => updateFilter('repository', key ?? 'all')}
146+
placeholder="All repositories"
147+
searchPlaceholder="Search repositories…"
148+
className={FILTER_TRIGGER_CLASS}
149+
/>
150+
</FilterCell>
151+
152+
<FilterCell label="Participant" explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.participant}>
153+
<SearchableSelect
139154
aria-label="Participant"
155+
items={withAll('All participants', participantOptions)}
140156
value={filters.participant}
141-
onChange={(event) => updateFilter('participant', event.target.value)}
142-
className="mt-2 w-full rounded-xl border border-white/10 bg-[rgba(10,15,30,0.88)] px-3 py-2 text-sm text-white outline-none transition focus:border-emerald-400/40"
143-
>
144-
<option value="all">All participants</option>
145-
{participantOptions.map((participant) => (
146-
<option key={participant} value={participant}>
147-
{participant}
148-
</option>
149-
))}
150-
</select>
151-
</label>
152-
153-
<label className="rounded-[1rem] border border-white/8 bg-white/5 px-3 py-2.5">
154-
<span className="flex items-center gap-2 text-[0.62rem] uppercase tracking-[0.18em] text-neutral-500">
155-
<span>Proof posture</span>
156-
<BitcodeInlineExplainer explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.proofStatus} />
157-
</span>
158-
<select
157+
onSelect={(key) => updateFilter('participant', key ?? 'all')}
158+
placeholder="All participants"
159+
searchPlaceholder="Search participants…"
160+
className={FILTER_TRIGGER_CLASS}
161+
/>
162+
</FilterCell>
163+
164+
<FilterCell label="Proof posture" explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.proofStatus}>
165+
<SearchableSelect
159166
aria-label="Proof posture"
167+
items={withAll('All proof states', proofStatusOptions)}
160168
value={filters.proofStatus}
161-
onChange={(event) => updateFilter('proofStatus', event.target.value)}
162-
className="mt-2 w-full rounded-xl border border-white/10 bg-[rgba(10,15,30,0.88)] px-3 py-2 text-sm text-white outline-none transition focus:border-emerald-400/40"
163-
>
164-
<option value="all">All proof states</option>
165-
{proofStatusOptions.map((proofStatus) => (
166-
<option key={proofStatus} value={proofStatus}>
167-
{proofStatus}
168-
</option>
169-
))}
170-
</select>
171-
</label>
172-
173-
<label className="rounded-[1rem] border border-white/8 bg-white/5 px-3 py-2.5 xl:col-start-7">
174-
<span className="flex items-center gap-2 text-[0.62rem] uppercase tracking-[0.18em] text-neutral-500">
175-
<span>Sort</span>
176-
<BitcodeInlineExplainer explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.sort} />
177-
</span>
178-
<select
169+
onSelect={(key) => updateFilter('proofStatus', key ?? 'all')}
170+
placeholder="All proof states"
171+
searchPlaceholder="Search proof states…"
172+
className={FILTER_TRIGGER_CLASS}
173+
/>
174+
</FilterCell>
175+
176+
<FilterCell label="Sort" explainer={BITCODE_TRANSACTION_FILTER_EXPLAINERS.sort}>
177+
<SearchableSelect
179178
aria-label="Sort"
179+
items={[
180+
{ key: 'newest', label: 'Newest first' },
181+
{ key: 'oldest', label: 'Oldest first' },
182+
{ key: 'most-tokens', label: 'Most tokens' },
183+
{ key: 'highest-btc-fee-basis', label: 'Highest BTC Fee Basis' },
184+
]}
180185
value={filters.sort}
181-
onChange={(event) => updateFilter('sort', event.target.value as TransactionSort)}
182-
className="mt-2 w-full rounded-xl border border-white/10 bg-[rgba(10,15,30,0.88)] px-3 py-2 text-sm text-white outline-none transition focus:border-emerald-400/40"
183-
>
184-
<option value="newest">Newest first</option>
185-
<option value="oldest">Oldest first</option>
186-
<option value="most-tokens">Most tokens</option>
187-
<option value="highest-btc-fee-basis">Highest BTC Fee Basis</option>
188-
</select>
189-
</label>
186+
onSelect={(key) => updateFilter('sort', (key ?? 'newest') as TransactionSort)}
187+
placeholder="Newest first"
188+
searchPlaceholder="Search sort orders…"
189+
className={FILTER_TRIGGER_CLASS}
190+
/>
191+
</FilterCell>
190192

191193
{onResetFilters ? (
192-
<div className="xl:col-span-full">
194+
<div className="col-span-full">
193195
<button
194196
type="button"
195197
onClick={onResetFilters}

0 commit comments

Comments
 (0)