Skip to content

Commit 5ca5a29

Browse files
BillLeoutsakosvl346Bill Leoutsakos
andauthored
Share table sidebar shell and scroll body (#8249)
* Share table sidebar shell and scroll body * test(tables): cover shared sidebar dialog state * test(tables): preserve sidebar scroll state --------- Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
1 parent 9d5746a commit 5ca5a29

6 files changed

Lines changed: 129 additions & 36 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx‎

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
Button,
66
ChipCombobox,
77
ChipInput,
8-
cn,
98
FieldDivider,
109
Label,
1110
Switch,
@@ -29,6 +28,10 @@ import {
2928
TableSidebarHeader,
3029
TableSidebarHeaderAction,
3130
} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-header/table-sidebar-header'
31+
import {
32+
TableSidebarScrollBody,
33+
TableSidebarShell,
34+
} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout'
3235
import { useAddTableColumn, useUpdateColumn } from '@/hooks/queries/tables'
3336
import { SelectOptionsEditor } from '../select-field'
3437
import { columnTypeOptionsForTable } from './column-types'
@@ -100,18 +103,11 @@ export function ColumnConfigSidebar(props: ColumnConfigSidebarProps) {
100103
// different column / mode remounts and re-seeds state from props.
101104
const open = props.config !== null
102105
return (
103-
<aside
104-
role='dialog'
105-
aria-label='Configure column'
106-
className={cn(
107-
'absolute top-0 right-0 bottom-0 z-[var(--z-modal)] flex w-[400px] flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--bg)] transition-transform duration-200 ease-out',
108-
open ? 'translate-x-0 shadow-overlay' : 'translate-x-full'
109-
)}
110-
>
106+
<TableSidebarShell open={open} aria-label='Configure column'>
111107
{props.config && (
112108
<ColumnConfigBody key={configKey(props.config)} {...props} config={props.config} />
113109
)}
114-
</aside>
110+
</TableSidebarShell>
115111
)
116112
}
117113

@@ -274,7 +270,7 @@ function ColumnConfigBody({
274270
</TableSidebarHeaderAction>
275271
</TableSidebarHeader>
276272

277-
<div className='flex-1 overflow-y-auto overflow-x-hidden px-2 pt-3 pb-2 [overflow-anchor:none]'>
273+
<TableSidebarScrollBody>
278274
{/* `disabled` on the fieldset reaches every native control inside,
279275
including the comboboxes' trigger buttons; `contents` keeps the
280276
existing layout. Values stay readable and selectable. */}
@@ -383,7 +379,7 @@ function ColumnConfigBody({
383379
</>
384380
)}
385381
</fieldset>
386-
</div>
382+
</TableSidebarScrollBody>
387383

388384
<div className='flex items-center justify-end gap-2 border-[var(--border)] border-t px-2 py-3'>
389385
<Button variant='default' size='sm' onClick={onClose}>

‎apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
TableSidebarHeader,
2626
TableSidebarHeaderAction,
2727
} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-header/table-sidebar-header'
28+
import { TableSidebarScrollBody } from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout'
2829
import type { EnrichmentConfig as EnrichmentDef } from '@/enrichments/types'
2930
import {
3031
useAddWorkflowGroup,
@@ -252,7 +253,7 @@ export function EnrichmentConfig({
252253
</TableSidebarHeaderAction>
253254
</TableSidebarHeader>
254255

255-
<div className='flex-1 overflow-y-auto overflow-x-hidden px-2 pt-3 pb-2 [overflow-anchor:none]'>
256+
<TableSidebarScrollBody>
256257
<div className='flex flex-col gap-[9.5px]'>
257258
<Label className='flex items-baseline gap-1.5 whitespace-nowrap pl-0.5'>Inputs</Label>
258259
{enrichment.inputs.length === 0 ? (
@@ -362,7 +363,7 @@ export function EnrichmentConfig({
362363
/>
363364
</>
364365
)}
365-
</div>
366+
</TableSidebarScrollBody>
366367

367368
<div className='flex items-center justify-end gap-2 border-[var(--border)] border-t px-2 py-3'>
368369
<Button variant='default' size='sm' onClick={onClose}>

‎apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx‎

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
'use client'
22

33
import { useState } from 'react'
4-
import { ChipInput, cn } from '@sim/emcn'
4+
import { ChipInput } from '@sim/emcn'
55
import { Search, X } from '@sim/emcn/icons'
66
import type { ColumnDefinition, WorkflowGroup } from '@/lib/table'
77
import {
88
TableSidebarHeader,
99
TableSidebarHeaderAction,
1010
} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-header/table-sidebar-header'
11+
import { TableSidebarShell } from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout'
1112
import { ALL_ENRICHMENTS } from '@/enrichments'
1213
import { getEnrichment } from '@/enrichments/registry'
1314
import type { EnrichmentConfig as EnrichmentDef } from '@/enrichments/types'
@@ -31,16 +32,9 @@ interface EnrichmentsSidebarProps {
3132
*/
3233
export function EnrichmentsSidebar({ open, ...rest }: EnrichmentsSidebarProps) {
3334
return (
34-
<aside
35-
role='dialog'
36-
aria-label='Enrichments'
37-
className={cn(
38-
'absolute top-0 right-0 bottom-0 z-[var(--z-modal)] flex w-[400px] flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--bg)] transition-transform duration-200 ease-out',
39-
open ? 'translate-x-0 shadow-overlay' : 'translate-x-full'
40-
)}
41-
>
35+
<TableSidebarShell open={open} aria-label='Enrichments'>
4236
{open && <EnrichmentsSidebarBody {...rest} />}
43-
</aside>
37+
</TableSidebarShell>
4438
)
4539
}
4640

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { act } from 'react'
5+
import { createRoot, type Root } from 'react-dom/client'
6+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
7+
import {
8+
TableSidebarScrollBody,
9+
TableSidebarShell,
10+
} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout'
11+
12+
let container: HTMLDivElement
13+
let root: Root
14+
15+
beforeEach(() => {
16+
globalThis.IS_REACT_ACT_ENVIRONMENT = true
17+
container = document.createElement('div')
18+
document.body.appendChild(container)
19+
root = createRoot(container)
20+
})
21+
22+
afterEach(() => {
23+
act(() => root.unmount())
24+
container.remove()
25+
})
26+
27+
describe('table sidebar layout', () => {
28+
it('keeps the labeled dialog and its form mounted while the sidebar slides closed', () => {
29+
function render(open: boolean) {
30+
act(() => {
31+
root.render(
32+
<TableSidebarShell open={open} aria-label='Configure workflow'>
33+
<TableSidebarScrollBody>
34+
<input aria-label='Workflow name' defaultValue='Existing workflow' />
35+
</TableSidebarScrollBody>
36+
</TableSidebarShell>
37+
)
38+
})
39+
}
40+
41+
render(true)
42+
const dialog = container.querySelector<HTMLElement>('[role="dialog"]')!
43+
const input = container.querySelector<HTMLInputElement>('input')!
44+
const scrollBody = input.parentElement!
45+
expect(dialog.getAttribute('aria-label')).toBe('Configure workflow')
46+
expect(dialog.classList.contains('translate-x-0')).toBe(true)
47+
expect(dialog.classList.contains('shadow-overlay')).toBe(true)
48+
49+
input.value = 'Edited workflow'
50+
scrollBody.scrollTop = 64
51+
render(false)
52+
expect(container.querySelector('[role="dialog"]')).toBe(dialog)
53+
expect(container.querySelector('input')).toBe(input)
54+
expect(input.value).toBe('Edited workflow')
55+
expect(input.parentElement).toBe(scrollBody)
56+
expect(scrollBody.scrollTop).toBe(64)
57+
expect(dialog.classList.contains('translate-x-full')).toBe(true)
58+
expect(dialog.classList.contains('shadow-overlay')).toBe(false)
59+
60+
render(true)
61+
expect(dialog.classList.contains('translate-x-0')).toBe(true)
62+
expect(input.value).toBe('Edited workflow')
63+
expect(scrollBody.scrollTop).toBe(64)
64+
})
65+
})
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { ReactNode } from 'react'
2+
import { cn } from '@sim/emcn'
3+
4+
interface TableSidebarShellProps {
5+
open: boolean
6+
'aria-label': string
7+
children: ReactNode
8+
}
9+
10+
/** The shared sliding shell for table configuration sidebars. */
11+
export function TableSidebarShell({
12+
open,
13+
children,
14+
'aria-label': ariaLabel,
15+
}: TableSidebarShellProps) {
16+
return (
17+
<aside
18+
role='dialog'
19+
aria-label={ariaLabel}
20+
className={cn(
21+
'absolute top-0 right-0 bottom-0 z-[var(--z-modal)] flex w-[400px] flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--bg)] transition-transform duration-200 ease-out',
22+
open ? 'translate-x-0 shadow-overlay' : 'translate-x-full'
23+
)}
24+
>
25+
{children}
26+
</aside>
27+
)
28+
}
29+
30+
interface TableSidebarScrollBodyProps {
31+
children: ReactNode
32+
}
33+
34+
/** The scrolling form area shared by column, workflow, and enrichment settings. */
35+
export function TableSidebarScrollBody({ children }: TableSidebarScrollBodyProps) {
36+
return (
37+
<div className='flex-1 overflow-y-auto overflow-x-hidden px-2 pt-3 pb-2 [overflow-anchor:none]'>
38+
{children}
39+
</div>
40+
)
41+
}

‎apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx‎

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
ChipCombobox,
77
ChipInput,
88
type ComboboxOptionGroup,
9-
cn,
109
FieldDisclosure,
1110
FieldDivider,
1211
Label,
@@ -50,6 +49,10 @@ import {
5049
TableSidebarHeader,
5150
TableSidebarHeaderAction,
5251
} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-header/table-sidebar-header'
52+
import {
53+
TableSidebarScrollBody,
54+
TableSidebarShell,
55+
} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-layout'
5356
import { PreviewWorkflow } from '@/app/workspace/[workspaceId]/w/components/preview'
5457
import { BlockTile } from '@/blocks/block-tile'
5558
import { useDeployedWorkflowState } from '@/hooks/queries/deployments'
@@ -150,18 +153,11 @@ interface BlockOutputGroup {
150153
export function WorkflowSidebar(props: WorkflowSidebarProps) {
151154
const open = props.config !== null
152155
return (
153-
<aside
154-
role='dialog'
155-
aria-label='Configure workflow'
156-
className={cn(
157-
'absolute top-0 right-0 bottom-0 z-[var(--z-modal)] flex w-[400px] flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--bg)] transition-transform duration-200 ease-out',
158-
open ? 'translate-x-0 shadow-overlay' : 'translate-x-full'
159-
)}
160-
>
156+
<TableSidebarShell open={open} aria-label='Configure workflow'>
161157
{props.config && (
162158
<WorkflowSidebarBody key={configKey(props.config)} {...props} config={props.config} />
163159
)}
164-
</aside>
160+
</TableSidebarShell>
165161
)
166162
}
167163

@@ -654,7 +650,7 @@ export function WorkflowSidebarBody({
654650
</TableSidebarHeaderAction>
655651
</TableSidebarHeader>
656652

657-
<div className='flex-1 overflow-y-auto overflow-x-hidden px-2 pt-3 pb-2 [overflow-anchor:none]'>
653+
<TableSidebarScrollBody>
658654
{/* Single-output mode renames this column directly. */}
659655
{isEditOutputMode && (
660656
<>
@@ -840,7 +836,7 @@ export function WorkflowSidebarBody({
840836
)}
841837
</>
842838
)}
843-
</div>
839+
</TableSidebarScrollBody>
844840

845841
<div className='flex items-center justify-end gap-2 border-[var(--border)] border-t px-2 py-3'>
846842
<Button variant='default' size='sm' onClick={onClose}>

0 commit comments

Comments
 (0)