1+ 'use client' ;
2+
3+ import { useState } from 'react' ;
4+ import { RiSearchLine } from '@remixicon/react' ;
5+ import { BarList as TremorBarList , Card , Dialog , DialogPanel , TextInput } from '@tremor/react' ;
6+
7+ interface BarListItem {
8+ name : string ;
9+ value : number ;
10+ }
11+
12+ interface BarListProps {
13+ data : Array < {
14+ name : string ;
15+ value : number ;
16+ } > ;
17+ valueFormatter ?: ( value : number ) => string ;
18+ onSelectionChange ?: ( selectedItems : string [ ] ) => void ;
19+ }
20+
21+ const defaultFormatter = ( number : number ) =>
22+ `${ Intl . NumberFormat ( 'us' ) . format ( number ) . toString ( ) } ` ;
23+
24+ export default function BarList ( {
25+ data,
26+ valueFormatter = defaultFormatter ,
27+ onSelectionChange
28+ } : BarListProps ) {
29+ const [ isOpen , setIsOpen ] = useState ( false ) ;
30+ const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
31+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
32+ const [ selectedItems , setSelectedItems ] = useState < string [ ] > ( [ ] ) ;
33+
34+ const filteredItems = data . filter ( ( item ) =>
35+ item . name . toLowerCase ( ) . includes ( searchQuery . toLowerCase ( ) ) ,
36+ ) ;
37+
38+ // Calculate total value for header
39+ const totalValue = data . reduce ( ( sum , item ) => sum + item . value , 0 ) ;
40+ const hasMoreItems = data . length > 5 ;
41+
42+ const handleBarClick = ( itemName : string ) => {
43+ setSelectedItems ( prev => {
44+ const newSelection = prev . includes ( itemName )
45+ ? prev . filter ( name => name !== itemName )
46+ : [ ...prev , itemName ] ;
47+
48+ onSelectionChange ?.( newSelection ) ;
49+ return newSelection ;
50+ } ) ;
51+ } ;
52+
53+ const renderBarList = ( items : BarListItem [ ] ) => (
54+ < TremorBarList < BarListItem >
55+ data = { items }
56+ valueFormatter = { valueFormatter }
57+ className = "mt-4"
58+ onValueChange = { ( item : BarListItem ) => handleBarClick ( item . name ) }
59+ />
60+ ) ;
61+
62+ return (
63+ < >
64+ < Card className = "h-full w-full rounded-none border-0" style = { { boxShadow : '-1px 0 0 0 rgb(55 65 81)' } } >
65+ < p className = "text-tremor-default text-tremor-content dark:text-dark-tremor-content" >
66+ Total
67+ </ p >
68+ < p className = "text-tremor-metric font-semibold text-tremor-content-strong dark:text-dark-tremor-content-strong" >
69+ { valueFormatter ( totalValue ) }
70+ </ p >
71+ < div className = "mt-6 flex items-center justify-between" >
72+ < p className = "text-tremor-default font-medium text-tremor-content-strong dark:text-dark-tremor-content-strong" >
73+ Top { Math . min ( 5 , data . length ) }
74+ </ p >
75+ < p className = "text-tremor-label font-medium uppercase text-tremor-content dark:text-dark-tremor-content" >
76+ Count
77+ </ p >
78+ </ div >
79+ { renderBarList ( data . slice ( 0 , 5 ) ) }
80+ { hasMoreItems && (
81+ < div className = "absolute inset-x-0 bottom-0 flex justify-center rounded-b-tremor-default bg-gradient-to-t from-tremor-background to-transparent py-7 dark:from-dark-tremor-background" >
82+ < button
83+ className = "flex items-center justify-center rounded-tremor-small border border-tremor-border bg-tremor-background px-2.5 py-2 text-tremor-default font-medium text-tremor-content-strong shadow-tremor-input hover:bg-tremor-background-muted dark:border-dark-tremor-border dark:bg-dark-tremor-background dark:text-dark-tremor-content-strong dark:shadow-dark-tremor-input hover:dark:bg-dark-tremor-background-muted"
84+ onClick = { ( ) => setIsOpen ( true ) }
85+ >
86+ Show more
87+ </ button >
88+ </ div >
89+ ) }
90+ < Dialog
91+ open = { isOpen }
92+ onClose = { ( ) => setIsOpen ( false ) }
93+ static = { true }
94+ className = "z-[100]"
95+ >
96+ < DialogPanel className = "overflow-hidden p-0" >
97+ < div className = "px-6 pb-4 pt-6" >
98+ < TextInput
99+ icon = { RiSearchLine }
100+ placeholder = "Search..."
101+ className = "rounded-tremor-small"
102+ value = { searchQuery }
103+ onValueChange = { setSearchQuery }
104+ />
105+ < div className = "flex items-center justify-between pt-4" >
106+ < p className = "text-tremor-default font-medium text-tremor-content-strong dark:text-dark-tremor-content-strong" >
107+ Name
108+ </ p >
109+ < p className = "text-tremor-label font-medium uppercase text-tremor-content dark:text-dark-tremor-content" >
110+ Count
111+ </ p >
112+ </ div >
113+ </ div >
114+ < div className = "h-96 overflow-y-scroll px-6" >
115+ { filteredItems . length > 0 ? (
116+ renderBarList ( filteredItems )
117+ ) : (
118+ < p className = "flex h-full items-center justify-center text-tremor-default text-tremor-content-strong dark:text-dark-tremor-content-strong" >
119+ No results.
120+ </ p >
121+ ) }
122+ </ div >
123+ < div className = "mt-4 border-t border-tremor-border bg-tremor-background-muted p-6 dark:border-dark-tremor-border dark:bg-dark-tremor-background" >
124+ < button
125+ className = "flex w-full items-center justify-center rounded-tremor-small border border-tremor-border bg-tremor-background py-2 text-tremor-default font-medium text-tremor-content-strong shadow-tremor-input hover:bg-tremor-background-muted dark:border-dark-tremor-border dark:bg-dark-tremor-background dark:text-dark-tremor-content-strong dark:shadow-dark-tremor-input hover:dark:bg-dark-tremor-background-muted"
126+ onClick = { ( ) => setIsOpen ( false ) }
127+ >
128+ Go back
129+ </ button >
130+ </ div >
131+ </ DialogPanel >
132+ </ Dialog >
133+ </ Card >
134+ </ >
135+ ) ;
136+ }
0 commit comments