@@ -17,6 +17,8 @@ export type ChartLegendCompoundProps = {
1717 totalLabel ?: string ;
1818 /** Callback when "View all" button is clicked */
1919 onViewAllLegendItems ?: ( ) => void ;
20+ /** When true, constrains legend to max 50% height with scrolling */
21+ scrollable ?: boolean ;
2022} ;
2123
2224/**
@@ -37,6 +39,7 @@ export function ChartLegendCompound({
3739 className,
3840 totalLabel = "Total" ,
3941 onViewAllLegendItems,
42+ scrollable = false ,
4043} : ChartLegendCompoundProps ) {
4144 const { config, dataKey, dataKeys, highlight, labelFormatter } = useChartContext ( ) ;
4245 const totals = useSeriesTotal ( ) ;
@@ -128,11 +131,17 @@ export function ChartLegendCompound({
128131 const isHovering = ( highlight . activePayload ?. length ?? 0 ) > 0 ;
129132
130133 return (
131- < div className = { cn ( "flex flex-col pt-4 text-sm" , className ) } >
134+ < div
135+ className = { cn (
136+ "flex flex-col pt-4 text-sm" ,
137+ scrollable && "max-h-[50%] min-h-0" ,
138+ className
139+ ) }
140+ >
132141 { /* Total row */ }
133142 < div
134143 className = { cn (
135- "flex w-full items-center justify-between gap-2 rounded px-2 py-1 transition" ,
144+ "flex w-full shrink-0 items-center justify-between gap-2 rounded px-2 py-1 transition" ,
136145 isHovering ? "text-text-bright" : "text-text-dimmed"
137146 ) }
138147 >
@@ -143,62 +152,65 @@ export function ChartLegendCompound({
143152 </ div >
144153
145154 { /* Separator */ }
146- < div className = "mx-2 my-1 border-t border-charcoal-750" />
155+ < div className = "mx-2 my-1 shrink-0 border-t border-charcoal-750" />
147156
148- { legendItems . visible . map ( ( item ) => {
149- const total = currentData [ item . dataKey ] ?? 0 ;
150- const isActive = highlight . activeBarKey === item . dataKey ;
157+ { /* Legend items - scrollable when scrollable prop is true */ }
158+ < div className = { cn ( "flex flex-col" , scrollable && "min-h-0 flex-1 overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600" ) } >
159+ { legendItems . visible . map ( ( item ) => {
160+ const total = currentData [ item . dataKey ] ?? 0 ;
161+ const isActive = highlight . activeBarKey === item . dataKey ;
151162
152- return (
153- < div
154- key = { item . dataKey }
155- className = { cn (
156- "relative flex w-full cursor-pointer items-center justify-between gap-2 rounded px-2 py-1 transition" ,
157- total === 0 && "opacity-50"
158- ) }
159- onMouseEnter = { ( ) => highlight . setHoveredLegendItem ( item . dataKey ) }
160- onMouseLeave = { ( ) => highlight . reset ( ) }
161- >
162- { /* Active highlight background */ }
163- { isActive && item . color && (
164- < div
165- className = "absolute inset-0 rounded opacity-10"
166- style = { { backgroundColor : item . color } }
167- />
168- ) }
169- < div className = "relative flex w-full items-center justify-between gap-2" >
170- < div className = "flex items-center gap-1.5" >
171- { item . color && (
172- < div
173- className = "h-3 w-1 shrink-0 rounded-[2px]"
174- style = { { backgroundColor : item . color } }
175- />
176- ) }
177- < span className = { isActive ? "text-text-bright" : "text-text-dimmed" } >
178- { item . label }
163+ return (
164+ < div
165+ key = { item . dataKey }
166+ className = { cn (
167+ "relative flex w-full cursor-pointer items-center justify-between gap-2 rounded px-2 py-1 transition" ,
168+ total === 0 && "opacity-50"
169+ ) }
170+ onMouseEnter = { ( ) => highlight . setHoveredLegendItem ( item . dataKey ) }
171+ onMouseLeave = { ( ) => highlight . reset ( ) }
172+ >
173+ { /* Active highlight background */ }
174+ { isActive && item . color && (
175+ < div
176+ className = "absolute inset-0 rounded opacity-10"
177+ style = { { backgroundColor : item . color } }
178+ />
179+ ) }
180+ < div className = "relative flex w-full items-center justify-between gap-2" >
181+ < div className = "flex items-center gap-1.5" >
182+ { item . color && (
183+ < div
184+ className = "h-3 w-1 shrink-0 rounded-[2px]"
185+ style = { { backgroundColor : item . color } }
186+ />
187+ ) }
188+ < span className = { isActive ? "text-text-bright" : "text-text-dimmed" } >
189+ { item . label }
190+ </ span >
191+ </ div >
192+ < span
193+ className = { cn ( "tabular-nums" , isActive ? "text-text-bright" : "text-text-dimmed" ) }
194+ >
195+ < AnimatedNumber value = { total } duration = { 0.25 } />
179196 </ span >
180197 </ div >
181- < span
182- className = { cn ( "tabular-nums" , isActive ? "text-text-bright" : "text-text-dimmed" ) }
183- >
184- < AnimatedNumber value = { total } duration = { 0.25 } />
185- </ span >
186198 </ div >
187- </ div >
188- ) ;
189- } ) }
199+ ) ;
200+ } ) }
190201
191- { /* View more row - replaced by hovered hidden item when applicable */ }
192- { legendItems . remaining > 0 &&
193- ( legendItems . hoveredHiddenItem ? (
194- < HoveredHiddenItemRow
195- item = { legendItems . hoveredHiddenItem }
196- value = { currentData [ legendItems . hoveredHiddenItem . dataKey ] ?? 0 }
197- remainingCount = { legendItems . remaining - 1 }
198- />
199- ) : (
200- < ViewAllDataRow remainingCount = { legendItems . remaining } onViewAll = { onViewAllLegendItems } />
201- ) ) }
202+ { /* View more row - replaced by hovered hidden item when applicable */ }
203+ { legendItems . remaining > 0 &&
204+ ( legendItems . hoveredHiddenItem ? (
205+ < HoveredHiddenItemRow
206+ item = { legendItems . hoveredHiddenItem }
207+ value = { currentData [ legendItems . hoveredHiddenItem . dataKey ] ?? 0 }
208+ remainingCount = { legendItems . remaining - 1 }
209+ />
210+ ) : (
211+ < ViewAllDataRow remainingCount = { legendItems . remaining } onViewAll = { onViewAllLegendItems } />
212+ ) ) }
213+ </ div >
202214 </ div >
203215 ) ;
204216}
0 commit comments