Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion packages/data-app-react/src/ui/BarChart.css
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@
justify-content: center;
gap: var(--atbl-space-xs);
min-height: var(--atbl-control-compact-height);
padding-top: 4px;
min-width: 0;
padding: 4px 4px 0;
color: var(--atbl-muted);
}
.altertable-chart-item-label-row > span {
Expand All @@ -94,11 +95,23 @@
display: inline-flex;
align-items: center;
gap: 4px;
min-width: 0;
max-width: 100%;
min-height: 0;
padding: 0;
color: inherit;
}

.altertable-selectable-bars-label > span {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}

.altertable-selectable-bars-label > svg {
flex-shrink: 0;
}

.altertable-chart-item-label-row button.altertable-selectable-bars-label {
padding: var(--atbl-space-xs) 6px;
border: 1px solid transparent;
Expand Down
4 changes: 2 additions & 2 deletions packages/data-app-react/src/ui/BarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export function BarChart<Row extends BarChartItem>({
}}
aria-label={`${item.label}: Actions`}
>
{item.label}
<span title={item.label}>{item.label}</span>
<AppIcon name="disclosure" size={12} />
</MenuButton>
<ExplorationActionMenu
Expand All @@ -192,7 +192,7 @@ export function BarChart<Row extends BarChartItem>({
</MenuTrigger>
) : (
<span className="altertable-selectable-bars-label">
{item.label}
<span title={item.label}>{item.label}</span>
</span>
)}
</div>
Expand Down
84 changes: 84 additions & 0 deletions tests/public-api/apps-charts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1207,3 +1207,87 @@ test('legend controls stay separate from plot inspection', async ({ page }) => {
await page.keyboard.press('ArrowRight');
await expect.poll(() => tooltip.getByRole('listitem').count()).toBe(2);
});

test('long bar category labels fit their columns and retain full readings', async ({
page,
}) => {
await page.goto('/chart-legends');
for (const theme of ['light', 'dark']) {
if (theme === 'dark')
await page.getByRole('button', { name: 'Switch to dark theme' }).click();
for (const width of [1280, 320]) {
await page.setViewportSize({ width, height: 900 });
for (const mode of ['plain', 'with actions']) {
const chart = page.getByRole('region', {
name: `Long category bars ${mode}`,
exact: true,
});
const labels = chart.getByTitle(/B2B|Business|International/);
await expect.poll(() => labels.count()).toBe(6);
await expect
.poll(async () => {
const bounds = await labels.evaluateAll(elements =>
elements.map(element => {
const rect = element.getBoundingClientRect();
return {
left: rect.left,
right: rect.right,
width: rect.width,
};
})
);
const columns = await chart
.getByRole('button', { name: /: \d+ people/ })
.evaluateAll(elements =>
elements.map(element => {
const rect = element.getBoundingClientRect();
return { left: rect.left, right: rect.right };
})
);
const centers = columns.map(
column => (column.left + column.right) / 2
);
const halfColumn = (centers[1]! - centers[0]!) / 2;
return bounds.every(
(label, index) =>
label.width > 0 &&
label.left >= centers[index]! - halfColumn - 1 &&
label.right <= centers[index]! + halfColumn + 1
);
})
.toBe(true);
const last = 'B2BReactivationWithoutSpaces';
expect(
await chart.getByTitle(last, { exact: true }).evaluate(element => {
const style = getComputedStyle(element);
return (
element.scrollWidth > element.clientWidth &&
style.overflowX === 'hidden' &&
style.textOverflow === 'ellipsis'
);
})
).toBe(true);
await chart
.getByRole('button', { name: new RegExp(`${last}: 20 people`) })
.hover();
await expect
.poll(() => page.getByRole('tooltip').textContent())
.toContain(last);
await page.keyboard.press('Escape');
if (mode === 'with actions') {
await chart
.getByRole('button', { name: `${last}: Actions`, exact: true })
.click();
await expect
.poll(() =>
page
.getByRole('menuitem', { name: 'Inspect', exact: true })
.isVisible()
)
.toBe(true);
await page.keyboard.press('Escape');
}
}
}
}
});
31 changes: 31 additions & 0 deletions tests/public-api/fixtures/chart-legends.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,37 @@ createRoot(document.getElementById('root')!).render(
</ChartLegend>
</Stack>
</Example>
{[false, true].map(withActions => (
<Example
key={String(withActions)}
name={`Long category bars ${withActions ? 'with actions' : 'plain'}`}
description="Long category labels stay within their bar columns."
>
<BarChart
ariaLabel={`Long category bars ${withActions ? 'with actions' : 'plain'}`}
unit="people"
items={[
'B2B Preonboarding',
'B2B Retention',
'Business Development',
'B2B Onboarding',
'International After Sales',
'B2BReactivationWithoutSpaces',
].map((label, index) => ({
id: String(index),
label,
value: 60 - index * 8,
}))}
itemActions={
withActions
? () => [
{ id: 'inspect', label: 'Inspect', onSelect: () => {} },
]
: undefined
}
/>
</Example>
))}
<Example
name="Standalone bar"
description="Original category input contract."
Expand Down