Skip to content

Commit d70bb4d

Browse files
committed
Max height on the chart legend for fullscreen
1 parent dc62e05 commit d70bb4d

File tree

4 files changed

+77
-53
lines changed

4 files changed

+77
-53
lines changed

apps/webapp/app/components/code/QueryResultsChart.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ interface QueryResultsChartProps {
5353
fullLegend?: boolean;
5454
/** Callback when "View all" legend button is clicked */
5555
onViewAllLegendItems?: () => void;
56+
/** When true, constrains legend to max 50% height with scrolling */
57+
legendScrollable?: boolean;
5658
}
5759

5860
interface TransformedData {
@@ -725,6 +727,7 @@ export const QueryResultsChart = memo(function QueryResultsChart({
725727
config,
726728
fullLegend = false,
727729
onViewAllLegendItems,
730+
legendScrollable = false,
728731
}: QueryResultsChartProps) {
729732
const {
730733
xAxisColumn,
@@ -895,6 +898,7 @@ export const QueryResultsChart = memo(function QueryResultsChart({
895898
minHeight="300px"
896899
fillContainer
897900
onViewAllLegendItems={onViewAllLegendItems}
901+
legendScrollable={legendScrollable}
898902
>
899903
<Chart.Bar
900904
xAxisProps={xAxisPropsForBar}
@@ -919,6 +923,7 @@ export const QueryResultsChart = memo(function QueryResultsChart({
919923
minHeight="300px"
920924
fillContainer
921925
onViewAllLegendItems={onViewAllLegendItems}
926+
legendScrollable={legendScrollable}
922927
>
923928
<Chart.Line
924929
xAxisProps={xAxisPropsForLine}

apps/webapp/app/components/primitives/charts/ChartLegendCompound.tsx

Lines changed: 64 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

apps/webapp/app/components/primitives/charts/ChartRoot.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export type ChartRootProps = {
3131
legendTotalLabel?: string;
3232
/** Callback when "View all" legend button is clicked */
3333
onViewAllLegendItems?: () => void;
34+
/** When true, constrains legend to max 50% height with scrolling */
35+
legendScrollable?: boolean;
3436
/** When true, chart fills its parent container height and distributes space between chart and legend */
3537
fillContainer?: boolean;
3638
children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>["children"];
@@ -72,6 +74,7 @@ export function ChartRoot({
7274
maxLegendItems = 5,
7375
legendTotalLabel,
7476
onViewAllLegendItems,
77+
legendScrollable = false,
7578
fillContainer = false,
7679
children,
7780
}: ChartRootProps) {
@@ -94,6 +97,7 @@ export function ChartRoot({
9497
maxLegendItems={maxLegendItems}
9598
legendTotalLabel={legendTotalLabel}
9699
onViewAllLegendItems={onViewAllLegendItems}
100+
legendScrollable={legendScrollable}
97101
fillContainer={fillContainer}
98102
>
99103
{children}
@@ -109,6 +113,7 @@ type ChartRootInnerProps = {
109113
maxLegendItems?: number;
110114
legendTotalLabel?: string;
111115
onViewAllLegendItems?: () => void;
116+
legendScrollable?: boolean;
112117
fillContainer?: boolean;
113118
children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>["children"];
114119
};
@@ -120,6 +125,7 @@ function ChartRootInner({
120125
maxLegendItems = 5,
121126
legendTotalLabel,
122127
onViewAllLegendItems,
128+
legendScrollable = false,
123129
fillContainer = false,
124130
children,
125131
}: ChartRootInnerProps) {
@@ -160,6 +166,7 @@ function ChartRootInner({
160166
maxItems={maxLegendItems}
161167
totalLabel={legendTotalLabel}
162168
onViewAllLegendItems={onViewAllLegendItems}
169+
scrollable={legendScrollable}
163170
/>
164171
)}
165172
</div>

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/route.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1053,7 +1053,7 @@ function ResultsChart({
10531053
{queryTitle ?? "Chart"}
10541054
</DialogHeader>
10551055
<div className="h-full min-h-0 flex-1 overflow-hidden w-full pt-4">
1056-
<QueryResultsChart rows={rows} columns={columns} config={chartConfig} fullLegend={true} />
1056+
<QueryResultsChart rows={rows} columns={columns} config={chartConfig} fullLegend={true} legendScrollable={true} />
10571057
</div>
10581058
</DialogContent>
10591059
</Dialog>

0 commit comments

Comments
 (0)