Skip to content

Commit e5199a1

Browse files
refactor(tables): rename frozen columns to pinned, fix sticky-zone UX
- rename frozenColumns → pinnedColumns across types, contract, undo actions, grid state/refs/props, and dropdown labels - add Pin / PinOff emcn icons; use them in the column menu in place of Lock / Unlock - pinned body cells render at z-[6], above the cell selection border (z-[5]), so the blue selection border can't draw on top of the sticky-left zone - restrict column drag-reorder to within the pinned or unpinned zone in both handleColumnDragOver and handleScrollDragOver; cross-zone drop indicators are suppressed - on unpin, slide the column to the first unpinned slot so the sticky zone stays contiguous; consolidates pin and unpin into one branch that always re-enforces pinned-at-front Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c9b848e commit e5199a1

12 files changed

Lines changed: 259 additions & 184 deletions

File tree

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/data-row.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ export interface DataRowProps {
5757
* queued indicators across page refresh during long Run-all dispatches.
5858
*/
5959
activeDispatches: ActiveDispatch[] | undefined
60-
/** Pixel `left` value for each frozen column key; absent keys are not frozen. */
61-
frozenOffsets?: Map<string, number>
62-
/** Key of the rightmost frozen column, used to render a separator shadow. */
63-
lastFrozenColKey?: string | null
60+
/** Pixel `left` value for each pinned column key; absent keys are not pinned. */
61+
pinnedOffsets?: Map<string, number>
62+
/** Key of the rightmost pinned column, used to render a separator shadow. */
63+
lastPinnedColKey?: string | null
6464
}
6565

6666
function cellRangeRowChanged(
@@ -118,8 +118,8 @@ function dataRowPropsAreEqual(prev: DataRowProps, next: DataRowProps): boolean {
118118
prev.onRunRow !== next.onRunRow ||
119119
prev.workflowGroups !== next.workflowGroups ||
120120
prev.activeDispatches !== next.activeDispatches ||
121-
prev.frozenOffsets !== next.frozenOffsets ||
122-
prev.lastFrozenColKey !== next.lastFrozenColKey
121+
prev.pinnedOffsets !== next.pinnedOffsets ||
122+
prev.lastPinnedColKey !== next.lastPinnedColKey
123123
) {
124124
return false
125125
}
@@ -163,8 +163,8 @@ export const DataRow = React.memo(function DataRow({
163163
onRunRow,
164164
workflowGroups,
165165
activeDispatches,
166-
frozenOffsets,
167-
lastFrozenColKey,
166+
pinnedOffsets,
167+
lastPinnedColKey,
168168
}: DataRowProps) {
169169
const sel = normalizedSelection
170170
/**
@@ -272,9 +272,9 @@ export const DataRow = React.memo(function DataRow({
272272
const isLeftEdge = inRange ? colIndex === sel!.startCol : colIndex === 0
273273
const isRightEdge = inRange ? colIndex === sel!.endCol : colIndex === columns.length - 1
274274

275-
const frozenLeft = frozenOffsets?.get(column.key)
276-
const isFrozenCell = frozenLeft !== undefined
277-
const isFrozenSeparator = column.key === lastFrozenColKey
275+
const pinnedLeft = pinnedOffsets?.get(column.key)
276+
const isPinnedCell = pinnedLeft !== undefined
277+
const isPinnedSeparator = column.key === lastPinnedColKey
278278

279279
return (
280280
<td
@@ -285,10 +285,10 @@ export const DataRow = React.memo(function DataRow({
285285
className={cn(
286286
CELL,
287287
(isHighlighted || isAnchor || isEditing) && 'relative',
288-
isFrozenCell && 'z-[5] bg-[var(--bg)]',
289-
isFrozenSeparator && '[box-shadow:2px_0_0_0_var(--border)]'
288+
isPinnedCell && 'z-[6] bg-[var(--bg)]',
289+
isPinnedSeparator && '[box-shadow:2px_0_0_0_var(--border)]'
290290
)}
291-
style={isFrozenCell ? { position: 'sticky', left: frozenLeft } : undefined}
291+
style={isPinnedCell ? { position: 'sticky', left: pinnedLeft } : undefined}
292292
onMouseDown={(e) => {
293293
if (e.button !== 0 || isEditing) return
294294
onCellMouseDown(rowIndex, colIndex, e.shiftKey)

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/column-header-menu.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ interface ColumnHeaderMenuProps {
4141
/** Opens a popup preview of the column's underlying workflow. Surfaced in
4242
* the chevron menu for workflow-output columns. */
4343
onViewWorkflow?: (workflowId: string) => void
44-
/** Whether this column is currently frozen (pinned to the left). */
45-
isFrozen?: boolean
46-
/** Toggle the frozen state for this column. */
47-
onFreezeToggle?: (columnName: string) => void
48-
/** Left offset in pixels when frozen (drives `position: sticky`). */
44+
/** Whether this column is currently pinned to the left. */
45+
isPinned?: boolean
46+
/** Toggle the pinned state for this column. */
47+
onPinToggle?: (columnName: string) => void
48+
/** Left offset in pixels when pinned (drives `position: sticky`). */
4949
stickyLeft?: number
50-
/** Whether this is the rightmost frozen column (renders a separator shadow). */
51-
isLastFrozen?: boolean
50+
/** Whether this is the rightmost pinned column (renders a separator shadow). */
51+
isLastPinned?: boolean
5252
}
5353

5454
/**
@@ -83,10 +83,10 @@ export const ColumnHeaderMenu = React.memo(function ColumnHeaderMenu({
8383
sourceInfo,
8484
onOpenConfig,
8585
onViewWorkflow,
86-
isFrozen,
87-
onFreezeToggle,
86+
isPinned,
87+
onPinToggle,
8888
stickyLeft,
89-
isLastFrozen,
89+
isLastPinned,
9090
}: ColumnHeaderMenuProps) {
9191
const renameInputRef = useRef<HTMLInputElement>(null)
9292
const didDragRef = useRef(false)
@@ -242,7 +242,7 @@ export const ColumnHeaderMenu = React.memo(function ColumnHeaderMenu({
242242
className={cn(
243243
'group relative border-[var(--border)] border-r border-b bg-[var(--bg)] p-0 text-left align-middle',
244244
stickyLeft !== undefined && 'z-[11]',
245-
isLastFrozen && '[box-shadow:2px_0_0_0_var(--border)]'
245+
isLastPinned && '[box-shadow:2px_0_0_0_var(--border)]'
246246
)}
247247
style={stickyLeft !== undefined ? { position: 'sticky', left: stickyLeft } : undefined}
248248
draggable={!readOnly && !isRenaming}
@@ -332,8 +332,8 @@ export const ColumnHeaderMenu = React.memo(function ColumnHeaderMenu({
332332
onViewWorkflow={
333333
onViewWorkflow && ownGroup ? () => onViewWorkflow(ownGroup.workflowId) : undefined
334334
}
335-
isFrozen={isFrozen}
336-
onFreezeToggle={onFreezeToggle}
335+
isPinned={isPinned}
336+
onPinToggle={onPinToggle}
337337
/>
338338
</div>
339339
)}

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/workflow-group-meta-cell.tsx

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ import {
1717
ArrowRight,
1818
Eye,
1919
EyeOff,
20-
Lock,
2120
Pencil,
21+
Pin,
22+
PinOff,
2223
PlayOutline,
2324
Trash,
24-
Unlock,
2525
} from '@/components/emcn/icons'
2626
import type { RunLimit, RunMode } from '@/lib/api/contracts/tables'
2727
import { cn } from '@/lib/core/utils/cn'
@@ -69,10 +69,10 @@ interface ColumnOptionsMenuProps {
6969
/** When set, the menu surfaces a "View workflow" item that opens a popup
7070
* preview of the configured workflow. */
7171
onViewWorkflow?: () => void
72-
/** Whether this column is currently frozen (pinned to the left). */
73-
isFrozen?: boolean
74-
/** Toggle the frozen state of this column. */
75-
onFreezeToggle?: (columnName: string) => void
72+
/** Whether this column is currently pinned to the left. */
73+
isPinned?: boolean
74+
/** Toggle the pinned state of this column. */
75+
onPinToggle?: (columnName: string) => void
7676
}
7777

7878
/**
@@ -99,8 +99,8 @@ export function ColumnOptionsMenu({
9999
onRunColumnSelected,
100100
selectedRowCount = 0,
101101
onViewWorkflow,
102-
isFrozen,
103-
onFreezeToggle,
102+
isPinned,
103+
onPinToggle,
104104
}: ColumnOptionsMenuProps) {
105105
const showRunActions = Boolean(onRunColumnAll && onRunColumnIncomplete)
106106
const showRunSelected = Boolean(onRunColumnSelected) && selectedRowCount > 0
@@ -167,10 +167,10 @@ export function ColumnOptionsMenu({
167167
<Pencil />
168168
Edit column
169169
</DropdownMenuItem>
170-
{onFreezeToggle && (
171-
<DropdownMenuItem onSelect={() => onFreezeToggle(column.name)}>
172-
{isFrozen ? <Unlock /> : <Lock />}
173-
{isFrozen ? 'Unfreeze column' : 'Freeze column'}
170+
{onPinToggle && (
171+
<DropdownMenuItem onSelect={() => onPinToggle(column.name)}>
172+
{isPinned ? <PinOff /> : <Pin />}
173+
{isPinned ? 'Unpin column' : 'Pin column'}
174174
</DropdownMenuItem>
175175
)}
176176
<DropdownMenuSeparator />
@@ -233,14 +233,14 @@ interface WorkflowGroupMetaCellProps {
233233
onDragEnd?: () => void
234234
onDragLeave?: () => void
235235
readOnly?: boolean
236-
/** Left offset in pixels when frozen (drives `position: sticky`). */
236+
/** Left offset in pixels when pinned (drives `position: sticky`). */
237237
stickyLeft?: number
238-
/** Whether this is the rightmost frozen column group (renders a separator shadow). */
239-
isLastFrozen?: boolean
240-
/** Whether this column group is currently frozen (pinned to the left). */
241-
isFrozen?: boolean
242-
/** Toggle the frozen state for this column group. */
243-
onFreezeToggle?: (columnName: string) => void
238+
/** Whether this is the rightmost pinned column group (renders a separator shadow). */
239+
isLastPinned?: boolean
240+
/** Whether this column group is currently pinned to the left. */
241+
isPinned?: boolean
242+
/** Toggle the pinned state for this column group. */
243+
onPinToggle?: (columnName: string) => void
244244
}
245245

246246
/**
@@ -275,9 +275,9 @@ export function WorkflowGroupMetaCell({
275275
onDragLeave,
276276
readOnly,
277277
stickyLeft,
278-
isLastFrozen,
279-
isFrozen,
280-
onFreezeToggle,
278+
isLastPinned,
279+
isPinned,
280+
onPinToggle,
281281
}: WorkflowGroupMetaCellProps) {
282282
const isEnrichment = groupType === 'enrichment'
283283
const enrichment = isEnrichment ? getEnrichment(enrichmentId) : undefined
@@ -400,7 +400,7 @@ export function WorkflowGroupMetaCell({
400400
className={cn(
401401
'group relative cursor-pointer border-[var(--border)] border-r border-b bg-[var(--bg)] px-2 py-[5px] text-left align-middle before:pointer-events-none before:absolute before:top-0 before:bottom-0 before:left-[-1px] before:w-px before:bg-[var(--border)] before:content-[""]',
402402
stickyLeft !== undefined && 'z-[11]',
403-
isLastFrozen && '[box-shadow:2px_0_0_0_var(--border)]'
403+
isLastPinned && '[box-shadow:2px_0_0_0_var(--border)]'
404404
)}
405405
style={stickyLeft !== undefined ? { position: 'sticky', left: stickyLeft } : undefined}
406406
>
@@ -488,8 +488,8 @@ export function WorkflowGroupMetaCell({
488488
onRunColumnSelected={onRunColumn && selectedCount > 0 ? handleRunSelected : undefined}
489489
selectedRowCount={selectedCount}
490490
onViewWorkflow={onViewWorkflow ? () => onViewWorkflow(workflowId) : undefined}
491-
isFrozen={isFrozen}
492-
onFreezeToggle={onFreezeToggle}
491+
isPinned={isPinned}
492+
onPinToggle={onPinToggle}
493493
/>
494494
)}
495495
</th>

0 commit comments

Comments
 (0)