Skip to content

Commit 98fc6bf

Browse files
committed
fix: move container query to chart-container
1 parent d67c076 commit 98fc6bf

File tree

5 files changed

+72
-61
lines changed

5 files changed

+72
-61
lines changed

src/core/chart-container.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import { useCallback, useRef, useState } from "react";
55
import clsx from "clsx";
66

7+
import { useContainerQuery } from "@cloudscape-design/component-toolkit";
78
import { useResizeObserver } from "@cloudscape-design/component-toolkit/internal";
89

910
import * as Styles from "../internal/chart-styles";
@@ -18,6 +19,8 @@ import testClasses from "./test-classes/styles.css.js";
1819
const DEFAULT_CHART_HEIGHT = 400;
1920
const DEFAULT_CHART_MIN_HEIGHT = 200;
2021

22+
const SHOULD_STACK_MAX_WIDTH = 800;
23+
2124
interface ChartContainerProps {
2225
// The header, footer, vertical axis title, and legend are rendered as is, and we measure the height of these components
2326
// to compute the available height for the chart plot when fitHeight=true. When there is not enough vertical space, the
@@ -28,8 +31,8 @@ interface ChartContainerProps {
2831
header?: React.ReactNode;
2932
filter?: React.ReactNode;
3033
navigator?: React.ReactNode;
31-
primaryLegend?: React.ReactNode;
32-
secondaryLegend?: React.ReactNode;
34+
primaryLegend?: (stack: boolean) => React.ReactNode;
35+
secondaryLegend?: (stack: boolean) => React.ReactNode;
3336
legendBottomMaxHeight?: number;
3437
legendPosition: "bottom" | "side";
3538
footer?: React.ReactNode;
@@ -60,6 +63,13 @@ export function ChartContainer({
6063
}: ChartContainerProps) {
6164
const { refs, measures } = useContainerQueries();
6265

66+
const showBottomLegend = primaryLegend !== undefined && legendPosition === "bottom";
67+
const shouldObserveWidth = showBottomLegend && secondaryLegend !== undefined;
68+
const [stackLegends, legendContainerQueryRef] = useContainerQuery(
69+
(rect) => shouldObserveWidth && rect.borderBoxWidth <= SHOULD_STACK_MAX_WIDTH,
70+
[shouldObserveWidth],
71+
);
72+
6373
// The vertical axis title is rendered above the chart, and is technically not a part of the chart plot.
6474
// However, we want to include it to the chart's height computations as it does belong to the chart logically.
6575
// We do so by taking the title's constant height into account, when "top" axis placement is chosen.
@@ -96,8 +106,8 @@ export function ChartContainer({
96106
className={styles["side-legend-container"]}
97107
style={{ maxBlockSize: effectiveChartHeight }}
98108
>
99-
{primaryLegend}
100-
{secondaryLegend}
109+
{primaryLegend(true)}
110+
{secondaryLegend?.(true)}
101111
</div>
102112
</div>
103113
) : (
@@ -113,14 +123,15 @@ export function ChartContainer({
113123

114124
<div ref={refs.footer} style={chartMinWidth !== undefined ? { minInlineSize: chartMinWidth } : {}}>
115125
{navigator && <div className={testClasses["chart-navigator"]}>{navigator}</div>}
116-
{primaryLegend && legendPosition === "bottom" && (
126+
{showBottomLegend && (
117127
<div
118128
aria-label={"Legend"}
129+
ref={legendContainerQueryRef}
119130
className={styles["bottom-legend-container"]}
120131
style={{ maxBlockSize: legendBottomMaxHeight ? `${legendBottomMaxHeight}px` : undefined }}
121132
>
122-
{primaryLegend}
123-
{secondaryLegend}
133+
{primaryLegend(stackLegends ?? false)}
134+
{secondaryLegend?.(stackLegends ?? false)}
124135
</div>
125136
)}
126137
{footer}

src/core/chart-core.tsx

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -302,34 +302,38 @@ export function InternalCoreChart({
302302
}}
303303
navigator={navigator}
304304
primaryLegend={
305-
context.legendEnabled && hasVisibleLegendItems(options) ? (
306-
<ChartLegend
307-
api={api}
308-
type={legendOptions?.type === "dual" ? "primary" : "single"}
309-
alignment={legendOptions?.position === "side" ? "vertical" : "horizontal"}
310-
i18nStrings={i18nStrings}
311-
title={legendOptions?.title}
312-
actions={legendOptions?.actions}
313-
horizontalAlignment={legendOptions?.horizontalAlignment}
314-
onItemHighlight={onLegendItemHighlight}
315-
getLegendTooltipContent={rest.getLegendTooltipContent}
316-
/>
317-
) : null
305+
context.legendEnabled && hasVisibleLegendItems(options)
306+
? (stack) => (
307+
<ChartLegend
308+
api={api}
309+
contentAlignment={legendOptions?.type === "dual" ? "primary" : "single"}
310+
alignment={legendOptions?.position === "side" || stack ? "vertical" : "horizontal"}
311+
i18nStrings={i18nStrings}
312+
title={legendOptions?.title}
313+
actions={legendOptions?.actions}
314+
horizontalAlignment={legendOptions?.horizontalAlignment}
315+
onItemHighlight={onLegendItemHighlight}
316+
getLegendTooltipContent={rest.getLegendTooltipContent}
317+
/>
318+
)
319+
: undefined
318320
}
319321
secondaryLegend={
320-
context.legendEnabled && legendOptions && legendOptions.type === "dual" ? (
321-
<ChartLegend
322-
api={api}
323-
type={"secondary"}
324-
alignment={legendOptions?.position === "side" ? "vertical" : "horizontal"}
325-
i18nStrings={i18nStrings}
326-
title={legendOptions.secondaryLegendTitle}
327-
actions={legendOptions.secondaryLegendActions}
328-
horizontalAlignment={legendOptions.horizontalAlignment}
329-
onItemHighlight={onLegendItemHighlight}
330-
getLegendTooltipContent={rest.getLegendTooltipContent}
331-
/>
332-
) : null
322+
context.legendEnabled && legendOptions && legendOptions.type === "dual"
323+
? (stack) => (
324+
<ChartLegend
325+
api={api}
326+
contentAlignment={"secondary"}
327+
alignment={legendOptions?.position === "side" || stack ? "vertical" : "horizontal"}
328+
i18nStrings={i18nStrings}
329+
title={legendOptions.secondaryLegendTitle}
330+
actions={legendOptions.secondaryLegendActions}
331+
horizontalAlignment={legendOptions.horizontalAlignment}
332+
onItemHighlight={onLegendItemHighlight}
333+
getLegendTooltipContent={rest.getLegendTooltipContent}
334+
/>
335+
)
336+
: undefined
333337
}
334338
verticalAxisTitle={
335339
verticalAxisTitlePlacement === "top" ? <VerticalAxisTitle api={api} inverted={!!inverted} /> : null

src/core/components/core-legend.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import { BaseI18nStrings, CoreChartProps } from "../interfaces";
1111

1212
export function ChartLegend({
1313
api,
14-
type,
1514
alignment,
15+
contentAlignment,
1616
title,
1717
actions,
1818
i18nStrings,
@@ -22,7 +22,7 @@ export function ChartLegend({
2222
}: {
2323
api: ChartAPI;
2424
alignment: "horizontal" | "vertical";
25-
type: "single" | "primary" | "secondary";
25+
contentAlignment: "single" | "primary" | "secondary";
2626
title?: string;
2727
actions?: React.ReactNode;
2828
horizontalAlignment?: CoreChartProps.LegendOptionsHorizontalAlignment;
@@ -37,9 +37,9 @@ export function ChartLegend({
3737

3838
const someHighlighted = legendItems.some((item) => item.highlighted);
3939
const filteredItems =
40-
type === "single"
40+
contentAlignment === "single"
4141
? legendItems
42-
: type === "primary"
42+
: contentAlignment === "primary"
4343
? legendItems.filter((item) => !item.isSecondary)
4444
: legendItems.filter((item) => item.isSecondary);
4545

@@ -70,8 +70,8 @@ export function ChartLegend({
7070
}
7171
return (
7272
<ChartLegendComponent
73-
type={type}
7473
alignment={alignment}
74+
contentAlignment={contentAlignment}
7575
ariaLabel={ariaLabel}
7676
legendTitle={title}
7777
horizontalAlignment={horizontalAlignment}

src/internal-do-not-use/core-legend/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export const CoreLegend = ({
5050
return (
5151
<ChartLegendComponent
5252
items={items}
53-
type={"single"}
53+
contentAlignment={"single"}
5454
alignment={alignment}
5555
actions={actions}
5656
legendTitle={title}

src/internal/components/chart-legend/index.tsx

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import { forwardRef, Ref, useEffect, useMemo, useRef, useState } from "react";
55
import clsx from "clsx";
66

7-
import { useContainerQuery } from "@cloudscape-design/component-toolkit";
87
import {
98
circleIndex,
109
handleKey,
@@ -27,13 +26,12 @@ import testClasses from "./test-classes/styles.css.js";
2726
const TOOLTIP_BLUR_DELAY = 50;
2827
const HIGHLIGHT_LOST_DELAY = 50;
2928
const SCROLL_DELAY = 100;
30-
const SHOULD_STACK_SIZE = 400;
3129

3230
export interface ChartLegendProps {
3331
someHighlighted: boolean;
3432
items: readonly LegendItem[];
3533
alignment: "horizontal" | "vertical";
36-
type: "single" | "primary" | "secondary";
34+
contentAlignment: "single" | "primary" | "secondary";
3735
legendTitle?: string;
3836
ariaLabel?: string;
3937
actions?: React.ReactNode;
@@ -47,8 +45,8 @@ export interface ChartLegendProps {
4745

4846
export const ChartLegend = ({
4947
items,
50-
type,
5148
alignment,
49+
contentAlignment,
5250
someHighlighted,
5351
legendTitle,
5452
ariaLabel,
@@ -60,9 +58,7 @@ export const ChartLegend = ({
6058
getTooltipContent,
6159
horizontalAlignment,
6260
}: ChartLegendProps) => {
63-
const containerObjectRef = useRef<HTMLDivElement>();
64-
const [shouldAutoStack, containerQueryRef] = useContainerQuery((rect) => rect.borderBoxWidth <= SHOULD_STACK_SIZE);
65-
const containerRef = useMergeRefs(containerQueryRef, containerObjectRef);
61+
const containerRef = useRef<HTMLDivElement>(null);
6662
const elementsByIndexRef = useRef<Record<number, HTMLElement>>([]);
6763
const elementsByIdRef = useRef<Record<string, HTMLElement>>({});
6864
const tooltipRef = useRef<HTMLElement>(null);
@@ -112,7 +108,7 @@ export const ChartLegend = ({
112108
if (isMouseInContainer.current) {
113109
return;
114110
}
115-
const container = containerObjectRef?.current;
111+
const container = containerRef?.current;
116112
const element = elementsByIndexRef.current?.[highlightedIndex];
117113
if (!container || !element) {
118114
return;
@@ -127,7 +123,7 @@ export const ChartLegend = ({
127123
container.scrollTo({ top, behavior: "smooth" });
128124
}
129125
}, SCROLL_DELAY);
130-
}, [containerObjectRef, items, scrollIntoViewControl]);
126+
}, [containerRef, items, scrollIntoViewControl]);
131127

132128
const showHighlight = (itemId: string) => {
133129
const item = items.find((item) => item.id === itemId);
@@ -191,8 +187,8 @@ export const ChartLegend = ({
191187
}
192188

193189
function getNextFocusTarget(): null | HTMLElement {
194-
if (containerObjectRef.current) {
195-
const buttons: HTMLButtonElement[] = Array.from(containerObjectRef.current.querySelectorAll(`.${styles.item}`));
190+
if (containerRef.current) {
191+
const buttons: HTMLButtonElement[] = Array.from(containerRef.current.querySelectorAll(`.${styles.item}`));
196192
return buttons[selectedIndex] ?? null;
197193
}
198194
return null;
@@ -213,18 +209,18 @@ export const ChartLegend = ({
213209
navigationAPI.current!.updateFocusTarget();
214210
});
215211

216-
const isStacked = alignment === "vertical" || shouldAutoStack;
217-
const isHorizontalSecondary = alignment === "horizontal" && type === "secondary";
212+
const isVertical = alignment === "vertical";
213+
const isHorizontalSecondary = alignment === "horizontal" && contentAlignment === "secondary";
218214

219215
const tooltipTrack = useRef<null | HTMLElement>(null);
220216
const tooltipTarget = items.find((item) => item.id === tooltipItemId) ?? null;
221217
tooltipTrack.current = tooltipItemId ? elementsByIdRef.current[tooltipItemId] : null;
222218
const tooltipContent = tooltipTarget && getTooltipContent({ legendItem: tooltipTarget });
223-
const tooltipPosition = isStacked ? "left" : "bottom";
219+
const tooltipPosition = isVertical ? "left" : "bottom";
224220

225221
function getTitleAlignment() {
226-
if (!isStacked) {
227-
switch (type) {
222+
if (!isVertical) {
223+
switch (contentAlignment) {
228224
case "primary":
229225
return "left";
230226
case "secondary":
@@ -261,19 +257,19 @@ export const ChartLegend = ({
261257
tabIndex={-1}
262258
ref={containerRef}
263259
className={clsx(styles.list, {
264-
[styles["list-side"]]: isStacked,
265-
[styles["list-bottom"]]: !isStacked,
266-
[styles["list-bottom-end"]]: !isStacked && isHorizontalSecondary,
267-
[styles[`list-bottom-${horizontalAlignment}`]]: !isStacked && type === "single",
260+
[styles["list-side"]]: isVertical,
261+
[styles["list-bottom"]]: !isVertical,
262+
[styles["list-bottom-end"]]: !isVertical && isHorizontalSecondary,
263+
[styles[`list-bottom-${horizontalAlignment}`]]: !isVertical && contentAlignment === "single",
268264
})}
269265
>
270266
{actions && (
271267
<>
272268
<div className={clsx(testClasses.actions, styles.actions, styles["actions-bottom"])}>
273269
{actions}
274-
{!isStacked && <div className={styles["actions-divider-bottom"]} />}
270+
{!isVertical && <div className={styles["actions-divider-bottom"]} />}
275271
</div>
276-
{isStacked && <div className={styles["actions-divider-side"]} />}
272+
{isVertical && <div className={styles["actions-divider-side"]} />}
277273
</>
278274
)}
279275
{items.map((item, index) => {

0 commit comments

Comments
 (0)