Improve automations table layout and column sizing (#16667)

* Improve automations table layout and column sizing

- Wrap table containers with min-width constraint for horizontal scrolling
- Adjust grid column widths for better visual balance
- Simplify automation draft building with helper function
- Remove unused validation checks and imports

* Make automation list first column sticky

- Keep automation name visible when scrolling horizontally
- Adjust header z-index to layer above sticky cells

* Remove unused canCreateAutomation prop from test
This commit is contained in:
Jinjing
2026-08-26 14:03:46 -07:00
committed by GitHub
parent 8d61cb8b77
commit fda213a4e8
9 changed files with 78 additions and 44 deletions
@@ -32,7 +32,11 @@ import {
import { getExternalAutomationScheduleDisplay } from './external-automation-schedule-display'
import { getExternalAutomationActionDisabledMessage } from './external-automation-source-availability'
import { AUTOMATIONS_TABLE_GRID_CLASS } from './automations-table-layout'
import { LIST_TABLE_ROW_CLASS, LIST_TABLE_ROW_SELECTED_CLASS } from '@/lib/list-table-layout'
import {
LIST_TABLE_ROW_CLASS,
LIST_TABLE_ROW_SELECTED_CLASS,
LIST_TABLE_STICKY_ROW_CELL_CLASS
} from '@/lib/list-table-layout'
import { isPortaledRowMenuClick, isRowActivationKey } from '@/lib/list-row-interaction'
import { getExternalAutomationLastRunSnapshot } from './automation-list-last-run'
import { AutomationListLastRunCell } from './AutomationListLastRunCell'
@@ -121,7 +125,9 @@ export function AutomationListExternalRows({
isSelected && LIST_TABLE_ROW_SELECTED_CLASS
)}
>
<span className="min-w-0 truncate font-medium">{entry.job.name}</span>
<span className={LIST_TABLE_STICKY_ROW_CELL_CLASS}>
<span className="min-w-0 truncate font-medium">{entry.job.name}</span>
</span>
<span className="min-w-0 truncate text-muted-foreground" title={scheduleLabel}>
{scheduleLabel}
</span>
@@ -49,7 +49,11 @@ import {
type AutomationUsageSummary
} from './automation-usage-model'
import { AUTOMATIONS_TABLE_GRID_CLASS } from './automations-table-layout'
import { LIST_TABLE_ROW_CLASS, LIST_TABLE_ROW_SELECTED_CLASS } from '@/lib/list-table-layout'
import {
LIST_TABLE_ROW_CLASS,
LIST_TABLE_ROW_SELECTED_CLASS,
LIST_TABLE_STICKY_ROW_CELL_CLASS
} from '@/lib/list-table-layout'
import { isPortaledRowMenuClick, isRowActivationKey } from '@/lib/list-row-interaction'
import { AutomationListStatusCell } from './AutomationListStatusCell'
import { translate } from '@/i18n/i18n'
@@ -246,7 +250,9 @@ export function AutomationListLocalRows({
isSelected && LIST_TABLE_ROW_SELECTED_CLASS
)}
>
<span className="min-w-0 truncate font-medium">{automation.name}</span>
<span className={LIST_TABLE_STICKY_ROW_CELL_CLASS}>
<span className="min-w-0 truncate font-medium">{automation.name}</span>
</span>
<span className="min-w-0 truncate text-muted-foreground" title={scheduleLabel}>
{scheduleLabel}
</span>
@@ -1,7 +1,10 @@
import React from 'react'
import { translate } from '@/i18n/i18n'
import { AUTOMATIONS_TABLE_GRID_CLASS } from './automations-table-layout'
import { LIST_TABLE_HEADER_CLASS } from '@/lib/list-table-layout'
import {
LIST_TABLE_HEADER_CLASS,
LIST_TABLE_STICKY_HEADER_CELL_CLASS
} from '@/lib/list-table-layout'
export function AutomationListTableHeader(): React.JSX.Element {
const labels = [
@@ -17,7 +20,16 @@ export function AutomationListTableHeader(): React.JSX.Element {
return (
<div className={`${AUTOMATIONS_TABLE_GRID_CLASS} ${LIST_TABLE_HEADER_CLASS}`}>
{labels.map(([key, fallback], index) => (
<span key={key} className={index === labels.length - 1 ? 'text-center' : undefined}>
<span
key={key}
className={
index === 0
? LIST_TABLE_STICKY_HEADER_CELL_CLASS
: index === labels.length - 1
? 'text-center'
: undefined
}
>
{translate(key, fallback)}
</span>
))}
@@ -71,6 +71,7 @@ function renderPanel(
searchActive: query !== ''
}}
hostCatalog={HOST_CATALOG}
canCreateAutomation={true}
externalManagersUncheckedNotice={uncheckedNotice}
onSelectHost={() => undefined}
onRecoverHost={() => undefined}
@@ -98,7 +99,6 @@ function renderPanel(
requestExternalAction={() => undefined}
openEditExternalDialog={() => undefined}
openCreateDialog={() => undefined}
canCreateAutomation
onOpenDetail={() => undefined}
onRefresh={() => undefined}
isRefreshing={false}
@@ -360,7 +360,7 @@ export function AutomationsListPanel(props: AutomationsListPanelProps): React.JS
className={cn('scrollbar-sleek min-h-0 flex-1 overflow-auto', LIST_TABLE_CONTAINER_CLASS)}
>
{hasFilteredListItems ? (
<>
<div className="min-w-full w-fit">
<AutomationListTableHeader />
<div className="divide-y divide-border/50">
<AutomationListLocalRows {...rowProps} rows={filteredRows} />
@@ -380,7 +380,7 @@ export function AutomationsListPanel(props: AutomationsListPanelProps): React.JS
onEdit={openEditExternalDialog}
/>
</div>
</>
</div>
) : (
<AutomationListEmptyView
{...emptyStateInput}
@@ -1444,20 +1444,6 @@ export default function AutomationsPage(): React.JSX.Element {
)
return
}
if (
editingAutomationId === null &&
editingExternalTarget === null &&
createTarget === 'orca' &&
!canCreateAutomation
) {
toast.error(
translate(
'auto.components.automations.AutomationsPage.destinationUnavailable',
'The selected automation destination is not ready. Reconnect it and try again.'
)
)
return
}
if (
editingAutomationId === null &&
!isHermesSave &&
@@ -1,7 +1,12 @@
import React from 'react'
import { cn } from '@/lib/utils'
import { AUTOMATIONS_TABLE_GRID_CLASS } from './automations-table-layout'
import { LIST_TABLE_CONTAINER_CLASS, LIST_TABLE_HEADER_CLASS } from '@/lib/list-table-layout'
import {
LIST_TABLE_CONTAINER_CLASS,
LIST_TABLE_HEADER_CLASS,
LIST_TABLE_STICKY_HEADER_CELL_CLASS,
LIST_TABLE_STICKY_ROW_CELL_CLASS
} from '@/lib/list-table-layout'
import { translate } from '@/i18n/i18n'
function SkeletonBar({ className }: { className?: string }): React.JSX.Element {
@@ -27,7 +32,9 @@ function TableRowSkeleton({
}): React.JSX.Element {
return (
<div className={cn(AUTOMATIONS_TABLE_GRID_CLASS, 'min-h-11 items-center gap-3 px-3 py-3')}>
<SkeletonBar className={cn('h-3.5', nameWidthClass)} />
<span className={LIST_TABLE_STICKY_ROW_CELL_CLASS}>
<SkeletonBar className={cn('h-3.5', nameWidthClass)} />
</span>
<SkeletonBar className={cn('h-3.5', scheduleWidthClass)} />
<SkeletonBar className={cn('h-3.5', projectWidthClass)} />
<SkeletonBar className={cn('h-3.5', hostWidthClass)} />
@@ -122,24 +129,28 @@ export function AutomationsPageSkeleton(): React.JSX.Element {
<SkeletonBar className="h-8 w-32 shrink-0 rounded-md" />
</div>
<div
className={cn('min-h-0 flex-1 overflow-hidden', LIST_TABLE_CONTAINER_CLASS)}
className={cn('scrollbar-sleek min-h-0 flex-1 overflow-auto', LIST_TABLE_CONTAINER_CLASS)}
data-contextual-fix-target="automations-list"
>
<div className={cn(AUTOMATIONS_TABLE_GRID_CLASS, LIST_TABLE_HEADER_CLASS)}>
<SkeletonBar className="h-2.5 w-12" />
<SkeletonBar className="h-2.5 w-14" />
<SkeletonBar className="h-2.5 w-14" />
<SkeletonBar className="h-2.5 w-12" />
<SkeletonBar className="h-2.5 w-16" />
<SkeletonBar className="h-2.5 w-16" />
<SkeletonBar className="h-2.5 w-12" />
<SkeletonBar className="mx-auto h-2.5 w-10" />
<span />
</div>
<div className="divide-y divide-border/50">
{TABLE_ROW_SKELETONS.map(({ id, ...row }) => (
<TableRowSkeleton key={id} {...row} />
))}
<div className="min-w-full w-fit">
<div className={cn(AUTOMATIONS_TABLE_GRID_CLASS, LIST_TABLE_HEADER_CLASS)}>
<span className={LIST_TABLE_STICKY_HEADER_CELL_CLASS}>
<SkeletonBar className="h-2.5 w-12" />
</span>
<SkeletonBar className="h-2.5 w-14" />
<SkeletonBar className="h-2.5 w-14" />
<SkeletonBar className="h-2.5 w-12" />
<SkeletonBar className="h-2.5 w-16" />
<SkeletonBar className="h-2.5 w-16" />
<SkeletonBar className="h-2.5 w-12" />
<SkeletonBar className="mx-auto h-2.5 w-10" />
<span />
</div>
<div className="divide-y divide-border/50">
{TABLE_ROW_SKELETONS.map(({ id, ...row }) => (
<TableRowSkeleton key={id} {...row} />
))}
</div>
</div>
</div>
</div>
@@ -1,3 +1,3 @@
// Name | Schedule | Project | Host | Next run | Last run | Status | Agent | Actions
export const AUTOMATIONS_TABLE_GRID_CLASS =
'grid grid-cols-[minmax(0,1.4fr)_minmax(6.5rem,9.5rem)_minmax(4.5rem,8rem)_minmax(4.5rem,7.5rem)_minmax(8rem,1fr)_minmax(7.5rem,10rem)_minmax(4.5rem,5.5rem)_2.5rem_2.5rem]'
'grid grid-cols-[minmax(11rem,1.8fr)_minmax(6.5rem,9.5rem)_minmax(4.5rem,7.5rem)_minmax(4.5rem,7rem)_minmax(7.5rem,9.5rem)_minmax(7rem,9.5rem)_minmax(4.5rem,5.5rem)_2.5rem_2.5rem]'
+15 -2
View File
@@ -5,11 +5,24 @@
*/
export const LIST_TABLE_CONTAINER_CLASS = 'rounded-md border border-border/50 bg-muted/20'
// Why: z-30 must beat the rows' sticky first cells (z-20) so the header still covers them.
export const LIST_TABLE_HEADER_CLASS =
'sticky top-0 z-10 h-8 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'
'sticky top-0 z-30 h-8 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'
// Why: keep keyboard-selected rows clear of the sticky table header.
export const LIST_TABLE_ROW_CLASS =
'w-full min-h-11 scroll-mt-8 cursor-pointer items-center gap-3 px-3 py-3 text-left text-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50'
'group/list-table-row w-full min-h-11 scroll-mt-8 cursor-pointer items-center gap-3 px-3 py-3 text-left text-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50'
export const LIST_TABLE_ROW_SELECTED_CLASS = 'bg-accent text-accent-foreground'
// Why: sticky cells must paint opaquely, so they carry the container tint flattened
// against the page instead of the alpha wash the scrolled columns would show through.
// The before:/after: strips fill the row's px-3 gutter and the gap-3 column gap, which
// the cell itself cannot reach, so scrolled columns slide fully out of sight.
const LIST_TABLE_STICKY_CELL_BASE_CLASS =
'sticky left-3 flex min-w-0 self-stretch items-center before:absolute before:-left-3 before:top-0 before:bottom-0 before:w-3 before:bg-inherit after:absolute after:-right-3 after:top-0 after:bottom-0 after:w-3 after:bg-inherit'
export const LIST_TABLE_STICKY_HEADER_CELL_CLASS = `${LIST_TABLE_STICKY_CELL_BASE_CLASS} z-10 bg-[color-mix(in_srgb,var(--muted)_40%,var(--background))]`
// Why: hover/selection ride variants, not props, so the frozen cell tracks the row's own wash.
export const LIST_TABLE_STICKY_ROW_CELL_CLASS = `${LIST_TABLE_STICKY_CELL_BASE_CLASS} z-20 bg-[color-mix(in_srgb,var(--muted)_20%,var(--background))] transition-colors group-hover/list-table-row:bg-accent group-data-[current=true]/list-table-row:bg-accent`