@@ -13,11 +13,28 @@ import { TreemapVisualizer } from '@/components/treemap-visualizer'
1313
1414import { Badge } from '@/components/ui/badge'
1515import { TreemapSkeleton } from '@/components/ui/skeleton'
16- import { ToggleGroup , ToggleGroupItem } from '@/components/ui/toggle-group'
16+ import {
17+ Select ,
18+ SelectContent ,
19+ SelectItem ,
20+ SelectTrigger ,
21+ SelectValue ,
22+ } from '@/components/ui/select'
23+ import { MultiSelect } from '@/components/ui/multi-select'
1724import { AnalyzeData , ModulesData } from '@/lib/analyze-data'
1825import { computeActiveEntries , computeModuleDepthMap } from '@/lib/module-graph'
1926import { fetchStrict } from '@/lib/utils'
2027import { formatBytes } from '@/lib/utils'
28+ import {
29+ File ,
30+ FileArchive ,
31+ Monitor ,
32+ Server ,
33+ FileCode ,
34+ FileJson ,
35+ Palette ,
36+ Package ,
37+ } from 'lucide-react'
2138
2239enum Environment {
2340 Client = 'client' ,
@@ -153,78 +170,21 @@ export default function Home() {
153170 onMouseMove = { handleMouseMove }
154171 onMouseUp = { handleMouseUp }
155172 >
156- < div className = "flex-none px-4 py-2 border-b border-border flex items-center gap-4" >
157- < div className = "basis-1/3 flex" >
158- < RouteTypeahead
159- selectedRoute = { selectedRoute }
160- onRouteSelected = { ( route ) => {
161- setSelectedRoute ( route )
162- setSelectedSourceIndex ( null )
163- setFocusedSourceIndex ( null )
164- } }
165- />
166- </ div >
167-
168- < div className = "basis-2/3 flex justify-end items-center space-x-4" >
169- { analyzeData && (
170- < >
171- < ToggleGroup
172- type = "single"
173- value = { sizeMode }
174- onValueChange = { ( value ) => {
175- if ( value ) setSizeMode ( value as SizeMode )
176- } }
177- size = "sm"
178- >
179- < ToggleGroupItem value = { SizeMode . Uncompressed } >
180- Uncompressed
181- </ ToggleGroupItem >
182- < ToggleGroupItem value = { SizeMode . Compressed } >
183- Compressed
184- </ ToggleGroupItem >
185- </ ToggleGroup >
186-
187- < ControlDivider />
188-
189- < ToggleGroup
190- type = "single"
191- value = { environmentFilter }
192- onValueChange = { ( value ) => {
193- if ( value ) setEnvironmentFilter ( value as Environment )
194- } }
195- size = "sm"
196- >
197- < ToggleGroupItem value = { Environment . Client } >
198- Client
199- </ ToggleGroupItem >
200- < ToggleGroupItem value = { Environment . Server } >
201- Server
202- </ ToggleGroupItem >
203- </ ToggleGroup >
204-
205- < ControlDivider />
206-
207- < ToggleGroup
208- type = "multiple"
209- value = { typeFilter }
210- onValueChange = { ( value ) => {
211- if ( value . length > 0 ) setTypeFilter ( value )
212- } }
213- size = "sm"
214- >
215- < ToggleGroupItem value = "js" > JS</ ToggleGroupItem >
216- < ToggleGroupItem value = "css" > CSS</ ToggleGroupItem >
217- < ToggleGroupItem value = "json" > JSON</ ToggleGroupItem >
218- < ToggleGroupItem value = "asset" > Asset</ ToggleGroupItem >
219- </ ToggleGroup >
220-
221- < ControlDivider />
222-
223- < FileSearch value = { searchQuery } onChange = { setSearchQuery } />
224- </ >
225- ) }
226- </ div >
227- </ div >
173+ < TopBar
174+ analyzeData = { analyzeData }
175+ selectedRoute = { selectedRoute }
176+ setSelectedRoute = { setSelectedRoute }
177+ sizeMode = { sizeMode }
178+ setSizeMode = { setSizeMode }
179+ environmentFilter = { environmentFilter }
180+ setEnvironmentFilter = { setEnvironmentFilter }
181+ setSelectedSourceIndex = { setSelectedSourceIndex }
182+ setFocusedSourceIndex = { setFocusedSourceIndex }
183+ typeFilter = { typeFilter }
184+ setTypeFilter = { setTypeFilter }
185+ searchQuery = { searchQuery }
186+ setSearchQuery = { setSearchQuery }
187+ />
228188
229189 < div className = "flex-1 flex min-h-0" >
230190 { error && ! analyzeData ? (
@@ -323,6 +283,138 @@ export default function Home() {
323283 )
324284}
325285
286+ const typeFilterOptions = [
287+ {
288+ value : 'js' ,
289+ label : 'JavaScript' ,
290+ icon : < FileCode className = "h-3.5 w-3.5" /> ,
291+ } ,
292+ { value : 'css' , label : 'CSS' , icon : < Palette className = "h-3.5 w-3.5" /> } ,
293+ {
294+ value : 'json' ,
295+ label : 'JSON' ,
296+ icon : < FileJson className = "h-3.5 w-3.5" /> ,
297+ } ,
298+ {
299+ value : 'asset' ,
300+ label : 'Asset' ,
301+ icon : < Package className = "h-3.5 w-3.5" /> ,
302+ } ,
303+ ]
304+
305+ function TopBar ( {
306+ analyzeData,
307+ selectedRoute,
308+ setSelectedRoute,
309+ sizeMode,
310+ setSizeMode,
311+ environmentFilter,
312+ setEnvironmentFilter,
313+ setSelectedSourceIndex,
314+ setFocusedSourceIndex,
315+ typeFilter,
316+ setTypeFilter,
317+ searchQuery,
318+ setSearchQuery,
319+ } : {
320+ analyzeData : AnalyzeData | undefined
321+ selectedRoute : string | null
322+ setSelectedRoute : ( route : string | null ) => void
323+ sizeMode : SizeMode
324+ setSizeMode : ( mode : SizeMode ) => void
325+ environmentFilter : Environment
326+ setEnvironmentFilter : ( env : Environment ) => void
327+ setSelectedSourceIndex : ( index : number | null ) => void
328+ setFocusedSourceIndex : ( index : number | null ) => void
329+ typeFilter : string [ ]
330+ setTypeFilter : ( types : string [ ] ) => void
331+ searchQuery : string
332+ setSearchQuery : ( query : string ) => void
333+ } ) {
334+ return (
335+ < div className = "flex-none px-4 py-2 border-b border-border flex items-center gap-3" >
336+ < div className = "flex-1 flex" >
337+ < RouteTypeahead
338+ selectedRoute = { selectedRoute }
339+ onRouteSelected = { ( route ) => {
340+ setSelectedRoute ( route )
341+ setSelectedSourceIndex ( null )
342+ setFocusedSourceIndex ( null )
343+ } }
344+ />
345+ </ div >
346+
347+ < div className = "flex items-center gap-2" >
348+ { analyzeData && (
349+ < >
350+ < Select
351+ value = { sizeMode }
352+ onValueChange = { ( value ) => setSizeMode ( value as SizeMode ) }
353+ >
354+ < SelectTrigger className = "w-fit min-w-[120px] h-8 text-xs" >
355+ < SelectValue />
356+ </ SelectTrigger >
357+ < SelectContent >
358+ < SelectItem value = { SizeMode . Uncompressed } className = "text-xs" >
359+ < div className = "flex items-center gap-1.5" >
360+ < File className = "h-3.5 w-3.5" />
361+ < span className = "text-xs" > Uncompressed</ span >
362+ </ div >
363+ </ SelectItem >
364+ < SelectItem value = { SizeMode . Compressed } className = "text-xs" >
365+ < div className = "flex items-center gap-1.5" >
366+ < FileArchive className = "h-3.5 w-3.5" />
367+ < span className = "text-xs" > Compressed</ span >
368+ </ div >
369+ </ SelectItem >
370+ </ SelectContent >
371+ </ Select >
372+
373+ < Select
374+ value = { environmentFilter }
375+ onValueChange = { ( value ) =>
376+ setEnvironmentFilter ( value as Environment )
377+ }
378+ >
379+ < SelectTrigger className = "w-fit min-w-[100px] h-8 text-xs" >
380+ < SelectValue />
381+ </ SelectTrigger >
382+ < SelectContent >
383+ < SelectItem value = { Environment . Client } className = "text-xs" >
384+ < div className = "flex items-center gap-1.5" >
385+ < Monitor className = "h-3.5 w-3.5" />
386+ < span className = "text-xs" > Client</ span >
387+ </ div >
388+ </ SelectItem >
389+ < SelectItem value = { Environment . Server } className = "text-xs" >
390+ < div className = "flex items-center gap-1.5" >
391+ < Server className = "h-3.5 w-3.5" />
392+ < span className = "text-xs" > Server</ span >
393+ </ div >
394+ </ SelectItem >
395+ </ SelectContent >
396+ </ Select >
397+
398+ < MultiSelect
399+ options = { typeFilterOptions }
400+ value = { typeFilter }
401+ onValueChange = { setTypeFilter }
402+ emptyMessage = "All Types"
403+ selectionName = "type"
404+ triggerIcon = { < FileCode className = "h-3.5 w-3.5" /> }
405+ aria-label = "Filter by file type"
406+ />
407+
408+ < ControlDivider />
409+
410+ < FileSearch value = { searchQuery } onChange = { setSearchQuery } />
411+ </ >
412+ ) }
413+ </ div >
414+ </ div >
415+ )
416+ }
417+
326418function ControlDivider ( ) {
327419 return < span className = "h-6 w-px bg-muted-foreground/30" />
328420}
0 commit comments