fix(jira): adapt issue list columns to panel width (#28583)

Adapt Jira rows and sort headers to their panel width so titles remain readable in narrow side panels.

Refs #16627.

Co-authored-by: Leopold Tripot <leopold.tripot@gmail.com>
This commit is contained in:
Neil
2026-10-14 02:57:32 -07:00
committed by GitHub
co-authored by Leopold Tripot
parent 9c0420fa9a
commit b3938e6f37
5 changed files with 119 additions and 17 deletions
@@ -126,24 +126,24 @@ function JiraIssueRow({
}
}}
className={cn(
'group/row grid min-h-12 cursor-pointer grid-cols-[minmax(0,1fr)_auto] items-center gap-3 px-3 py-2 text-left transition hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring md:grid-cols-[90px_minmax(0,1fr)_128px_92px_80px_64px] lg:grid-cols-[96px_minmax(0,1.25fr)_132px_120px_136px_96px_64px] xl:grid-cols-[104px_minmax(0,1.45fr)_144px_132px_160px_128px_72px]',
'group/row grid min-h-12 cursor-pointer grid-cols-[minmax(0,1fr)_auto] items-center gap-3 px-3 py-2 text-left transition hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring @3xl:grid-cols-[90px_minmax(0,1fr)_128px_92px_80px_64px] @5xl:grid-cols-[96px_minmax(0,1.25fr)_132px_120px_136px_96px_64px] @7xl:grid-cols-[104px_minmax(0,1.45fr)_144px_132px_160px_128px_72px]',
selected && 'bg-accent'
)}
>
<span className="block truncate font-mono text-[12px] text-muted-foreground max-md:!hidden">
<span className="block truncate font-mono text-[12px] text-muted-foreground @max-3xl:!hidden">
{issue.key}
</span>
<div className="min-w-0">
<div className="flex min-w-0 items-center gap-2">
<span className="shrink-0 font-mono text-[11px] text-muted-foreground md:hidden">
<span className="shrink-0 font-mono text-[11px] text-muted-foreground @3xl:hidden">
{issue.key}
</span>
<h3 className="min-w-0 truncate text-[13px] font-medium text-foreground">
{issue.title}
</h3>
</div>
<div className="mt-1 flex min-w-0 items-center gap-1.5 md:!hidden">
<div className="mt-1 flex min-w-0 items-center gap-1.5 @3xl:!hidden">
<span
className={cn(
'inline-flex min-w-0 items-center rounded-full border px-1.5 py-0.5 text-[11px] font-medium',
@@ -161,8 +161,8 @@ function JiraIssueRow({
translate('auto.components.TaskPage.42a9160321', 'Unassigned')}
</span>
</div>
<div className="mt-1 flex min-w-0 items-center gap-1 max-lg:!hidden">
<span className="max-w-[160px] truncate text-[10px] text-muted-foreground xl:!hidden">
<div className="mt-1 flex min-w-0 items-center gap-1 @max-5xl:!hidden">
<span className="max-w-[160px] truncate text-[10px] text-muted-foreground @7xl:!hidden">
{contextLabel}
</span>
{labels.map((label) => (
@@ -181,7 +181,7 @@ function JiraIssueRow({
</div>
</div>
<div className="flex min-w-0 max-md:!hidden">
<div className="flex min-w-0 @max-3xl:!hidden">
<span
className={cn(
'inline-flex max-w-full items-center rounded-full border px-2 py-0.5 text-[11px] font-medium',
@@ -192,11 +192,11 @@ function JiraIssueRow({
</span>
</div>
<span className="block truncate text-[12px] text-muted-foreground max-md:!hidden">
<span className="block truncate text-[12px] text-muted-foreground @max-3xl:!hidden">
{issue.priority?.name ?? translate('auto.components.TaskPage.713179dfdc', 'No priority')}
</span>
<div className="flex min-w-0 items-center gap-2 text-[12px] text-muted-foreground max-lg:!hidden">
<div className="flex min-w-0 items-center gap-2 text-[12px] text-muted-foreground @max-5xl:!hidden">
{issue.assignee?.avatarUrl ? (
<img
src={issue.assignee.avatarUrl}
@@ -216,7 +216,7 @@ function JiraIssueRow({
<Tooltip>
<TooltipTrigger asChild>
<div className="block min-w-0 truncate text-[12px] text-muted-foreground max-md:!hidden">
<div className="block min-w-0 truncate text-[12px] text-muted-foreground @max-3xl:!hidden">
{formatUpdatedAt(issue.updatedAt)}
</div>
</TooltipTrigger>
@@ -225,7 +225,7 @@ function JiraIssueRow({
</TooltipContent>
</Tooltip>
<div className="flex shrink-0 items-center justify-end gap-1 md:opacity-0 md:transition-opacity md:group-hover/row:opacity-100 md:group-focus-within/row:opacity-100">
<div className="flex shrink-0 items-center justify-end gap-1 @3xl:opacity-0 @3xl:transition-opacity @3xl:group-hover/row:opacity-100 @3xl:group-focus-within/row:opacity-100">
<Tooltip>
<TooltipTrigger asChild>
<Button
@@ -33,7 +33,7 @@ describe('TaskPage Jira sort controls', () => {
'aria-pressed',
'true'
)
expect(screen.getByRole('button', { name: 'Assignee' })).toHaveClass('flex', 'max-lg:!hidden')
expect(screen.getByRole('button', { name: 'Assignee' })).toHaveClass('flex', '@max-5xl:!hidden')
expect(screen.getByRole('button', { name: 'Assignee' })).not.toHaveClass('block')
await user.click(screen.getByRole('button', { name: 'Key' }))
@@ -45,7 +45,7 @@ describe('TaskPage Jira sort controls', () => {
const onSort = vi.fn()
renderControls(onSort)
expect(screen.getByTestId('jira-mobile-sort-controls')).toHaveClass('hidden', 'max-md:!flex')
expect(screen.getByTestId('jira-mobile-sort-controls')).toHaveClass('hidden', '@max-3xl:!flex')
await user.click(screen.getByRole('combobox', { name: 'Sort by' }))
await user.click(screen.getByRole('option', { name: 'Priority' }))
expect(onSort).toHaveBeenCalledWith('priority')
@@ -34,7 +34,7 @@ function getJiraSortColumns(): JiraSortColumn[] {
{
id: 'assignee',
label: translate('auto.components.TaskPage.d2a876ca53', 'Assignee'),
className: 'max-lg:!hidden'
className: '@max-5xl:!hidden'
},
{ id: 'updated', label: translate('auto.components.TaskPage.f362667d55', 'Updated') }
]
@@ -63,7 +63,7 @@ export function TaskPageJiraSortControls({
return (
<>
<div className="grid h-8 flex-none grid-cols-[90px_minmax(0,1fr)_128px_92px_80px_64px] items-center gap-3 border-b border-border/50 bg-muted/25 px-3 text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground max-md:!hidden lg:grid-cols-[96px_minmax(0,1.25fr)_132px_120px_136px_96px_64px] xl:grid-cols-[104px_minmax(0,1.45fr)_144px_132px_160px_128px_72px]">
<div className="grid h-8 flex-none grid-cols-[90px_minmax(0,1fr)_128px_92px_80px_64px] items-center gap-3 border-b border-border/50 bg-muted/25 px-3 text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground @max-3xl:!hidden @5xl:grid-cols-[96px_minmax(0,1.25fr)_132px_120px_136px_96px_64px] @7xl:grid-cols-[104px_minmax(0,1.45fr)_144px_132px_160px_128px_72px]">
{columns.map((column) => (
<button
key={column.id}
@@ -90,7 +90,7 @@ export function TaskPageJiraSortControls({
<div
data-testid="jira-mobile-sort-controls"
className="hidden h-10 flex-none items-center gap-2 border-b border-border/50 bg-muted/25 px-3 max-md:!flex"
className="hidden h-10 flex-none items-center gap-2 border-b border-border/50 bg-muted/25 px-3 @max-3xl:!flex"
>
<span className="shrink-0 text-[11px] font-semibold tracking-[0.05em] text-muted-foreground uppercase">
{sortByLabel}
@@ -70,7 +70,7 @@ export function TaskPageJiraContent({
</div>
</div>
) : (
<div className="flex min-h-0 max-h-full flex-col overflow-hidden rounded-md rounded-t-none border border-t-0 border-border/50 bg-background shadow-sm">
<div className="@container flex min-h-0 max-h-full flex-col overflow-hidden rounded-md rounded-t-none border border-t-0 border-border/50 bg-background shadow-sm">
<div className="flex h-10 flex-none items-center justify-between gap-3 border-b border-border/50 bg-muted/35 px-3">
<div className="min-w-0 text-[11px] font-medium uppercase tracking-[0.12em] text-muted-foreground">
{translate('auto.components.TaskPage.63b2abd3aa', 'Jira issues')}
+102
View File
@@ -0,0 +1,102 @@
import type { JiraIssue } from '../../src/shared/jira-types'
import { expect, test } from './helpers/orca-app'
import { waitForSessionReady } from './helpers/store'
const ISSUE_TITLE = 'Keep planning notes visible while switching between project tasks'
const ISSUE: JiraIssue = {
id: 'layout-fixture',
key: 'DEMO-101',
siteId: 'layout-site',
title: ISSUE_TITLE,
url: 'https://example.atlassian.net/browse/DEMO-101',
project: { id: 'layout-project', key: 'DEMO', name: 'Demo' },
issueType: { id: 'task', name: 'Task' },
status: { id: 'open', name: 'To Do', categoryKey: 'new', categoryName: 'To Do' },
labels: [],
priority: { id: 'high', name: 'High' },
assignee: { accountId: 'layout-user', displayName: 'Demo User' },
createdAt: '2026-10-01T00:00:00Z',
updatedAt: '2026-10-13T00:00:00Z'
}
test('Jira titles and sort headings adapt to panel width inside a wide window', async ({
orcaPage
}) => {
await waitForSessionReady(orcaPage)
await orcaPage.setViewportSize({ width: 1500, height: 900 })
await orcaPage.evaluate((issue) => {
const store = window.__store
if (!store) {
throw new Error('Store unavailable')
}
const state = store.getState()
if (!state.settings) {
throw new Error('Settings unavailable')
}
const status = {
connected: true,
viewer: null,
selectedSiteId: 'layout-site',
sites: [
{
id: 'layout-site',
siteUrl: 'https://example.atlassian.net',
email: '',
displayName: 'Demo Jira',
accountId: 'layout-user'
}
]
}
store.setState({
settings: { ...state.settings, defaultTaskSource: 'jira', visibleTaskProviders: ['jira'] },
jiraStatus: status,
jiraStatusChecked: true,
jiraStatusContextKey: 'local#0',
checkJiraConnection: async () => {},
readJiraStatus: async () => status,
listJiraIssues: async () => [issue],
searchJiraIssues: async () => [issue]
})
store.getState().openTaskPage({ taskSource: 'jira' })
}, ISSUE)
const title = orcaPage.getByRole('heading', { name: ISSUE_TITLE })
await expect(title).toBeVisible()
for (const width of [500, 780, 1050, 1320]) {
await title.evaluate((element, width) => {
const host =
element.closest('[role="button"]')?.parentElement?.parentElement?.parentElement
?.parentElement?.parentElement
if (!(host instanceof HTMLElement)) {
throw new Error('Jira list host unavailable')
}
host.style.width = `${width}px`
host.style.maxWidth = 'none'
}, width)
await expect
.poll(() => title.evaluate((element) => element.getBoundingClientRect().width))
.toBeGreaterThan(200)
const mobileSort = orcaPage.getByTestId('jira-mobile-sort-controls')
const assignee = orcaPage.getByRole('button', { name: 'Assignee', exact: true })
await (width < 768 ? expect(mobileSort).toBeVisible() : expect(mobileSort).toBeHidden())
await (width >= 1024 ? expect(assignee).toBeVisible() : expect(assignee).toBeHidden())
if (width >= 768) {
const grids = await title.evaluate((element) => {
const row = element.closest('[role="button"]')
const host = row?.parentElement?.parentElement?.parentElement?.parentElement?.parentElement
const header = host?.children[1]
if (!row || !header) {
throw new Error('Jira header or row unavailable')
}
return [
getComputedStyle(row).gridTemplateColumns.split(' '),
getComputedStyle(header).gridTemplateColumns.split(' ')
]
})
// The scroll gutter narrows the flexible title; fixed tracks must still match.
expect(grids[0].filter((_, index) => index !== 1)).toEqual(
grids[1].filter((_, index) => index !== 1)
)
}
}
})