@@ -6,38 +6,32 @@ import { Eye, PlayOutline, RefreshCw, Square } from '@/components/emcn/icons'
66import { cn } from '@/lib/core/utils/cn'
77
88interface TableActionBarProps {
9- /** Number of rows currently selected (checkbox + multi-row range). 0 in
10- * single-cell mode (use `singleCell` instead). */
11- selectedCount : number
12- /** Total running/queued workflow cells across the selected rows. Drives the
13- * Stop button's visibility (hidden when 0) and label. */
9+ /** Number of (row × group) cells the run/stop buttons would target. Drives
10+ * the bar's leading label ("N cells"). */
11+ selectedCellCount : number
12+ /** Total running/queued workflow cells in the selection. Drives Stop. */
1413 runningCount : number
15- /** Whether the table has any workflow columns. The bar is hidden entirely
16- * when there are none — Run/Stop have nothing to act on. */
14+ /** Whether the table has any workflow columns. The bar hides entirely when
15+ * there are none — Run/Stop have nothing to act on. */
1716 hasWorkflowColumns : boolean
18- /** Smart run: fire workflows only on rows whose cells are empty / errored
19- * / cancelled. Skips already-completed cells. Maps to server
20- * `runMode: 'incomplete'`. The default action — what "play" should
21- * intuitively do. */
22- onRun : ( ) => void
23- /** Forceful re-run: fire workflows on every selected row, including ones
24- * that already have results. Maps to server `runMode: 'all'`. */
25- onRerun : ( ) => void
26- /** Cancel running/queued cells across selected rows. */
17+ /** Show the Play (incomplete-mode) button — true when any selected cell is
18+ * empty / errored / cancelled. */
19+ showPlay : boolean
20+ /** Show the Refresh (all-mode) button — true when any selected cell is
21+ * already completed. */
22+ showRefresh : boolean
23+ /** Smart run: fire workflows only on cells that are empty / errored /
24+ * cancelled. Maps to server `runMode: 'incomplete'`. */
25+ onPlay : ( ) => void
26+ /** Forceful re-run: fire workflows on every selected cell, including
27+ * completed ones. Maps to server `runMode: 'all'`. */
28+ onRefresh : ( ) => void
29+ /** Cancel running/queued cells in the selection. */
2730 onStopWorkflows : ( ) => void
28- /**
29- * When the user has a single workflow-output cell highlighted (no row
30- * selection), the bar switches to a per-cell mode showing the cell's
31- * status + an Eye button to open the execution log. `null` for multi-row
32- * selections.
33- */
34- singleCell ?: {
35- canViewExecution : boolean
36- onViewExecution : ( ) => void
37- isRunning : boolean
38- onRunCell : ( ) => void
39- onStopCell : ( ) => void
40- } | null
31+ /** When the user has highlighted exactly one workflow cell (or N adjacent
32+ * cells in the same row + group), surface a "View execution" affordance
33+ * alongside the run buttons. Omit when no single-execution view applies. */
34+ onViewExecution ?: ( ) => void
4135 /** Disables actions while a bulk mutation is in flight. */
4236 isLoading ?: boolean
4337 /** Additional className for the floating wrapper — used to lift the bar
@@ -46,34 +40,39 @@ interface TableActionBarProps {
4640}
4741
4842/**
49- * Floating action bar shown at the bottom of the table when one or more rows
50- * are selected, OR when a single workflow-output cell is highlighted. Mirrors
51- * the shell + interaction pattern from the knowledge-base `<ActionBar>`.
43+ * Floating action bar shown at the bottom of the table when one or more
44+ * workflow cells are highlighted. Play / Refresh visibility is data-driven:
45+ * Play appears when there's anything empty/failed in the selection; Refresh
46+ * appears when there's anything already completed; both when the selection is
47+ * mixed.
5248 *
5349 * Rendered with `position: absolute` inside the table's container (not
5450 * `fixed`) so it scopes to the table's bounds — important for embedded mode,
5551 * where the table sits inside a panel and a fixed-positioned bar would land
5652 * centered on the whole viewport instead of the panel.
5753 */
5854export function TableActionBar ( {
59- selectedCount ,
55+ selectedCellCount ,
6056 runningCount,
6157 hasWorkflowColumns,
62- onRun,
63- onRerun,
58+ showPlay,
59+ showRefresh,
60+ onPlay,
61+ onRefresh,
6462 onStopWorkflows,
65- singleCell ,
63+ onViewExecution ,
6664 isLoading = false ,
6765 className,
6866} : TableActionBarProps ) {
69- const isMultiRow = selectedCount > 0
70- const isSingleCell = ! isMultiRow && Boolean ( singleCell )
71- const visible = hasWorkflowColumns && ( isMultiRow || isSingleCell )
67+ const visible =
68+ hasWorkflowColumns &&
69+ selectedCellCount > 0 &&
70+ ( showPlay || showRefresh || runningCount > 0 || Boolean ( onViewExecution ) )
7271 const stopLabel =
7372 runningCount === 1 ? 'Stop running workflow' : `Stop ${ runningCount } running workflows`
74- const runLabel = 'Run workflows on empty or failed cells'
75- const rerunLabel =
76- selectedCount === 1 ? 'Re-run workflows on row ' : `Re-run workflows on ${ selectedCount } rows `
73+ const playLabel =
74+ selectedCellCount === 1 ? 'Run cell' : `Run ${ selectedCellCount } empty or failed cells`
75+ const refreshLabel = selectedCellCount === 1 ? 'Re-run cell ' : `Re-run ${ selectedCellCount } cells `
7776
7877 return (
7978 < AnimatePresence >
@@ -91,114 +90,76 @@ export function TableActionBar({
9190 >
9291 < div className = 'pointer-events-auto flex items-center gap-2 rounded-[10px] border border-[var(--border)] bg-[var(--surface-2)] px-2 py-1.5' >
9392 < span className = 'px-1 text-[var(--text-secondary)] text-small' >
94- { isMultiRow ? `${ selectedCount } selected` : 'Cell' }
93+ { selectedCellCount === 1 ? 'Cell' : `${ selectedCellCount } cells` }
9594 </ span >
9695
9796 < div className = 'flex items-center gap-[5px]' >
98- { isMultiRow && (
99- < >
100- < Tooltip . Root >
101- < Tooltip . Trigger asChild >
102- < Button
103- variant = 'ghost'
104- onClick = { onRun }
105- disabled = { isLoading }
106- className = 'hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
107- aria-label = { runLabel }
108- >
109- < PlayOutline className = 'h-[12px] w-[12px]' />
110- </ Button >
111- </ Tooltip . Trigger >
112- < Tooltip . Content side = 'top' > { runLabel } </ Tooltip . Content >
113- </ Tooltip . Root >
114-
115- < Tooltip . Root >
116- < Tooltip . Trigger asChild >
117- < Button
118- variant = 'ghost'
119- onClick = { onRerun }
120- disabled = { isLoading }
121- className = 'hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
122- aria-label = { rerunLabel }
123- >
124- < RefreshCw className = 'h-[12px] w-[12px]' />
125- </ Button >
126- </ Tooltip . Trigger >
127- < Tooltip . Content side = 'top' > { rerunLabel } </ Tooltip . Content >
128- </ Tooltip . Root >
129-
130- { runningCount > 0 && (
131- < Tooltip . Root >
132- < Tooltip . Trigger asChild >
133- < Button
134- variant = 'ghost'
135- onClick = { onStopWorkflows }
136- disabled = { isLoading }
137- className = 'hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
138- aria-label = { stopLabel }
139- >
140- < Square className = 'h-[12px] w-[12px]' />
141- </ Button >
142- </ Tooltip . Trigger >
143- < Tooltip . Content side = 'top' > { stopLabel } </ Tooltip . Content >
144- </ Tooltip . Root >
145- ) }
146- </ >
97+ { showPlay && (
98+ < Tooltip . Root >
99+ < Tooltip . Trigger asChild >
100+ < Button
101+ variant = 'ghost'
102+ onClick = { onPlay }
103+ disabled = { isLoading }
104+ className = 'hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
105+ aria-label = { playLabel }
106+ >
107+ < PlayOutline className = 'h-[12px] w-[12px]' />
108+ </ Button >
109+ </ Tooltip . Trigger >
110+ < Tooltip . Content side = 'top' > { playLabel } </ Tooltip . Content >
111+ </ Tooltip . Root >
147112 ) }
148113
149- { isSingleCell && singleCell && (
150- < >
151- { ! singleCell . isRunning && (
152- < Tooltip . Root >
153- < Tooltip . Trigger asChild >
154- < Button
155- variant = 'ghost'
156- onClick = { singleCell . onRunCell }
157- disabled = { isLoading }
158- className = 'hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
159- aria-label = 'Run cell'
160- >
161- < PlayOutline className = 'h-[12px] w-[12px]' />
162- </ Button >
163- </ Tooltip . Trigger >
164- < Tooltip . Content side = 'top' > Run cell</ Tooltip . Content >
165- </ Tooltip . Root >
166- ) }
114+ { showRefresh && (
115+ < Tooltip . Root >
116+ < Tooltip . Trigger asChild >
117+ < Button
118+ variant = 'ghost'
119+ onClick = { onRefresh }
120+ disabled = { isLoading }
121+ className = 'hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
122+ aria-label = { refreshLabel }
123+ >
124+ < RefreshCw className = 'h-[12px] w-[12px]' />
125+ </ Button >
126+ </ Tooltip . Trigger >
127+ < Tooltip . Content side = 'top' > { refreshLabel } </ Tooltip . Content >
128+ </ Tooltip . Root >
129+ ) }
167130
168- { singleCell . isRunning && (
169- < Tooltip . Root >
170- < Tooltip . Trigger asChild >
171- < Button
172- variant = 'ghost'
173- onClick = { singleCell . onStopCell }
174- disabled = { isLoading }
175- className = 'hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
176- aria-label = 'Stop cell'
177- >
178- < Square className = 'h-[12px] w-[12px]' />
179- </ Button >
180- </ Tooltip . Trigger >
181- < Tooltip . Content side = 'top' > Stop cell </ Tooltip . Content >
182- </ Tooltip . Root >
183- ) }
131+ { runningCount > 0 && (
132+ < Tooltip . Root >
133+ < Tooltip . Trigger asChild >
134+ < Button
135+ variant = 'ghost'
136+ onClick = { onStopWorkflows }
137+ disabled = { isLoading }
138+ className = 'hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
139+ aria-label = { stopLabel }
140+ >
141+ < Square className = 'h-[12px] w-[12px]' />
142+ </ Button >
143+ </ Tooltip . Trigger >
144+ < Tooltip . Content side = 'top' > { stopLabel } </ Tooltip . Content >
145+ </ Tooltip . Root >
146+ ) }
184147
185- { singleCell . canViewExecution && (
186- < Tooltip . Root >
187- < Tooltip . Trigger asChild >
188- < Button
189- variant = 'ghost'
190- onClick = { singleCell . onViewExecution }
191- disabled = { isLoading }
192- className = 'hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
193- aria-label = 'View execution'
194- >
195- < Eye className = 'h-[12px] w-[12px]' />
196- </ Button >
197- </ Tooltip . Trigger >
198- < Tooltip . Content side = 'top' > View execution</ Tooltip . Content >
199- </ Tooltip . Root >
200- ) }
201- </ >
148+ { onViewExecution && (
149+ < Tooltip . Root >
150+ < Tooltip . Trigger asChild >
151+ < Button
152+ variant = 'ghost'
153+ onClick = { onViewExecution }
154+ disabled = { isLoading }
155+ className = 'hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
156+ aria-label = 'View execution'
157+ >
158+ < Eye className = 'h-[12px] w-[12px]' />
159+ </ Button >
160+ </ Tooltip . Trigger >
161+ < Tooltip . Content side = 'top' > View execution</ Tooltip . Content >
162+ </ Tooltip . Root >
202163 ) }
203164 </ div >
204165 </ div >
0 commit comments