diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx
index 2ad4ebc773b..a1db5e3daf5 100644
--- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx
@@ -5,7 +5,6 @@ import {
Button,
ChipCombobox,
ChipInput,
- cn,
FieldDivider,
Label,
Switch,
@@ -29,6 +28,10 @@ import {
TableSidebarHeader,
TableSidebarHeaderAction,
} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-header/table-sidebar-header'
+import {
+ TableSidebarScrollBody,
+ TableSidebarShell,
+} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout'
import { useAddTableColumn, useUpdateColumn } from '@/hooks/queries/tables'
import { SelectOptionsEditor } from '../select-field'
import { columnTypeOptionsForTable } from './column-types'
@@ -100,18 +103,11 @@ export function ColumnConfigSidebar(props: ColumnConfigSidebarProps) {
// different column / mode remounts and re-seeds state from props.
const open = props.config !== null
return (
-
+
{props.config && (
)}
-
+
)
}
@@ -274,7 +270,7 @@ function ColumnConfigBody({
-
+
{/* `disabled` on the fieldset reaches every native control inside,
including the comboboxes' trigger buttons; `contents` keeps the
existing layout. Values stay readable and selectable. */}
@@ -383,7 +379,7 @@ function ColumnConfigBody({
>
)}
-
+
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx
index e158e4ab159..2cbf1f8c564 100644
--- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx
@@ -25,6 +25,7 @@ import {
TableSidebarHeader,
TableSidebarHeaderAction,
} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-header/table-sidebar-header'
+import { TableSidebarScrollBody } from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout'
import type { EnrichmentConfig as EnrichmentDef } from '@/enrichments/types'
import {
useAddWorkflowGroup,
@@ -252,7 +253,7 @@ export function EnrichmentConfig({
-
+
Inputs
{enrichment.inputs.length === 0 ? (
@@ -362,7 +363,7 @@ export function EnrichmentConfig({
/>
>
)}
-
+
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx
index 421055477fc..22a0134a7f2 100644
--- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx
@@ -1,13 +1,14 @@
'use client'
import { useState } from 'react'
-import { ChipInput, cn } from '@sim/emcn'
+import { ChipInput } from '@sim/emcn'
import { Search, X } from '@sim/emcn/icons'
import type { ColumnDefinition, WorkflowGroup } from '@/lib/table'
import {
TableSidebarHeader,
TableSidebarHeaderAction,
} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-header/table-sidebar-header'
+import { TableSidebarShell } from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout'
import { ALL_ENRICHMENTS } from '@/enrichments'
import { getEnrichment } from '@/enrichments/registry'
import type { EnrichmentConfig as EnrichmentDef } from '@/enrichments/types'
@@ -31,16 +32,9 @@ interface EnrichmentsSidebarProps {
*/
export function EnrichmentsSidebar({ open, ...rest }: EnrichmentsSidebarProps) {
return (
-
+
)
}
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout.test.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout.test.tsx
new file mode 100644
index 00000000000..a9b03496744
--- /dev/null
+++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout.test.tsx
@@ -0,0 +1,65 @@
+/**
+ * @vitest-environment jsdom
+ */
+import { act } from 'react'
+import { createRoot, type Root } from 'react-dom/client'
+import { afterEach, beforeEach, describe, expect, it } from 'vitest'
+import {
+ TableSidebarScrollBody,
+ TableSidebarShell,
+} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout'
+
+let container: HTMLDivElement
+let root: Root
+
+beforeEach(() => {
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true
+ container = document.createElement('div')
+ document.body.appendChild(container)
+ root = createRoot(container)
+})
+
+afterEach(() => {
+ act(() => root.unmount())
+ container.remove()
+})
+
+describe('table sidebar layout', () => {
+ it('keeps the labeled dialog and its form mounted while the sidebar slides closed', () => {
+ function render(open: boolean) {
+ act(() => {
+ root.render(
+
+
+
+
+
+ )
+ })
+ }
+
+ render(true)
+ const dialog = container.querySelector('[role="dialog"]')!
+ const input = container.querySelector('input')!
+ const scrollBody = input.parentElement!
+ expect(dialog.getAttribute('aria-label')).toBe('Configure workflow')
+ expect(dialog.classList.contains('translate-x-0')).toBe(true)
+ expect(dialog.classList.contains('shadow-overlay')).toBe(true)
+
+ input.value = 'Edited workflow'
+ scrollBody.scrollTop = 64
+ render(false)
+ expect(container.querySelector('[role="dialog"]')).toBe(dialog)
+ expect(container.querySelector('input')).toBe(input)
+ expect(input.value).toBe('Edited workflow')
+ expect(input.parentElement).toBe(scrollBody)
+ expect(scrollBody.scrollTop).toBe(64)
+ expect(dialog.classList.contains('translate-x-full')).toBe(true)
+ expect(dialog.classList.contains('shadow-overlay')).toBe(false)
+
+ render(true)
+ expect(dialog.classList.contains('translate-x-0')).toBe(true)
+ expect(input.value).toBe('Edited workflow')
+ expect(scrollBody.scrollTop).toBe(64)
+ })
+})
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout.tsx
new file mode 100644
index 00000000000..7315df41c52
--- /dev/null
+++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout.tsx
@@ -0,0 +1,41 @@
+import type { ReactNode } from 'react'
+import { cn } from '@sim/emcn'
+
+interface TableSidebarShellProps {
+ open: boolean
+ 'aria-label': string
+ children: ReactNode
+}
+
+/** The shared sliding shell for table configuration sidebars. */
+export function TableSidebarShell({
+ open,
+ children,
+ 'aria-label': ariaLabel,
+}: TableSidebarShellProps) {
+ return (
+
+ )
+}
+
+interface TableSidebarScrollBodyProps {
+ children: ReactNode
+}
+
+/** The scrolling form area shared by column, workflow, and enrichment settings. */
+export function TableSidebarScrollBody({ children }: TableSidebarScrollBodyProps) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx
index 62c96b80f78..672fe326307 100644
--- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx
@@ -6,7 +6,6 @@ import {
ChipCombobox,
ChipInput,
type ComboboxOptionGroup,
- cn,
FieldDisclosure,
FieldDivider,
Label,
@@ -50,6 +49,10 @@ import {
TableSidebarHeader,
TableSidebarHeaderAction,
} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-header/table-sidebar-header'
+import {
+ TableSidebarScrollBody,
+ TableSidebarShell,
+} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout'
import { PreviewWorkflow } from '@/app/workspace/[workspaceId]/w/components/preview'
import { BlockTile } from '@/blocks/block-tile'
import { useDeployedWorkflowState } from '@/hooks/queries/deployments'
@@ -150,18 +153,11 @@ interface BlockOutputGroup {
export function WorkflowSidebar(props: WorkflowSidebarProps) {
const open = props.config !== null
return (
-
+
{props.config && (
)}
-
+
)
}
@@ -654,7 +650,7 @@ export function WorkflowSidebarBody({
-
+
{/* Single-output mode renames this column directly. */}
{isEditOutputMode && (
<>
@@ -840,7 +836,7 @@ export function WorkflowSidebarBody({
)}
>
)}
-
+