mirror of
https://github.com/stablyai/orca.git
synced 2026-10-05 16:02:22 +00:00
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:
@@ -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]'
|
||||
|
||||
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user