@@ -4,6 +4,7 @@ import React from 'react';
44
55import BitcodeInlineExplainer from './BitcodeInlineExplainer' ;
66import { BITCODE_TRANSACTION_FILTER_EXPLAINERS } from './bitcode-transaction-explainers' ;
7+ import { SearchableSelect } from '@/components/base/bitcode/forms/SearchableSelect' ;
78import type { TransactionFilters , TransactionOwnership , TransactionSort } from './bitcode-transaction-types' ;
89
910interface 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+
1947export 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