Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
Duplicate,
OverlayActionButton,
Search as SearchIcon,
Tooltip,
useCopyToClipboard,
Expand Down Expand Up @@ -494,7 +495,8 @@ function DetailCodeSection({
<Code.Viewer
code={jsonString}
language='json'
className='max-w-full rounded-md border-0 bg-[var(--surface-4)]! [word-break:break-all] dark:bg-[var(--surface-3)]!'
appearance='inspection'
className='max-w-full [word-break:break-all]'
wrapText
searchQuery={isSearchActive ? searchQuery : undefined}
currentMatchIndex={currentMatchIndex}
Expand All @@ -504,39 +506,35 @@ function DetailCodeSection({
<div className='absolute top-[7px] right-[6px] z-10 flex gap-1'>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button
<OverlayActionButton
aria-label={copied ? 'Copied' : 'Copy'}
type='button'
variant='default'
onClick={(e) => {
e.stopPropagation()
handleCopy()
}}
className='size-[20px] cursor-pointer border-[var(--border-1)] bg-transparent p-0 backdrop-blur-xs hover-hover:bg-[var(--surface-3)]'
>
{copied ? (
<Check className='size-[10px] text-[var(--text-success)]' />
) : (
<Clipboard className='size-[10px]' />
)}
</Button>
</OverlayActionButton>
</Tooltip.Trigger>
<Tooltip.Content side='top'>{copied ? 'Copied' : 'Copy'}</Tooltip.Content>
</Tooltip.Root>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button
<OverlayActionButton
aria-label='Search'
type='button'
variant='default'
onClick={(e) => {
e.stopPropagation()
activateSearch()
}}
className='size-[20px] cursor-pointer border-[var(--border-1)] bg-transparent p-0 backdrop-blur-xs hover-hover:bg-[var(--surface-3)]'
>
<Search className='size-[10px]' />
</Button>
</OverlayActionButton>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Search</Tooltip.Content>
</Tooltip.Root>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
Duplicate,
Eye,
handleKeyboardActivation,
OverlayActionButton,
Redo,
Search as SearchIcon,
Tooltip,
Expand Down Expand Up @@ -158,7 +159,8 @@ export const WorkflowOutputSection = memo(
<Code.Viewer
code={jsonString}
language='json'
className='max-h-[300px] min-h-0 max-w-full rounded-md border-0 bg-[var(--surface-4)]! [word-break:break-all] dark:bg-[var(--surface-3)]!'
appearance='inspection'
className='max-h-[300px] min-h-0 max-w-full [word-break:break-all]'
wrapText
searchQuery={isSearchActive ? searchQuery : undefined}
currentMatchIndex={currentMatchIndex}
Expand All @@ -169,39 +171,35 @@ export const WorkflowOutputSection = memo(
<div className='absolute top-[7px] right-[6px] z-10 flex gap-1'>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button
<OverlayActionButton
aria-label={copied ? 'Copied' : 'Copy'}
type='button'
variant='default'
onClick={(e) => {
e.stopPropagation()
handleCopy()
}}
className='size-[20px] cursor-pointer border-[var(--border-1)] bg-transparent p-0 backdrop-blur-xs hover-hover:bg-[var(--surface-3)]'
>
{copied ? (
<Check className='size-[10px] text-[var(--text-success)]' />
) : (
<Clipboard className='size-[10px]' />
)}
</Button>
</OverlayActionButton>
</Tooltip.Trigger>
<Tooltip.Content side='top'>{copied ? 'Copied' : 'Copy'}</Tooltip.Content>
</Tooltip.Root>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button
<OverlayActionButton
aria-label='Search'
type='button'
variant='default'
onClick={(e) => {
e.stopPropagation()
activateSearch()
}}
className='size-[20px] cursor-pointer border-[var(--border-1)] bg-transparent p-0 backdrop-blur-xs hover-hover:bg-[var(--surface-3)]'
>
<Search className='size-[10px]' />
</Button>
</OverlayActionButton>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Search</Tooltip.Content>
</Tooltip.Root>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ function ServerDetailView({
: 'json'
}
wrapText
className='min-h-0! rounded-sm border border-[var(--border-1)]'
className='min-h-0!'
/>
{activeConfigTab === 'cursor' && (
<a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
Button,
ChipCombobox,
ChipInput,
cn,
FieldDivider,
Label,
Switch,
Expand All @@ -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'
Expand Down Expand Up @@ -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 (
<aside
role='dialog'
aria-label='Configure column'
className={cn(
'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',
open ? 'translate-x-0 shadow-overlay' : 'translate-x-full'
)}
>
<TableSidebarShell open={open} aria-label='Configure column'>
{props.config && (
<ColumnConfigBody key={configKey(props.config)} {...props} config={props.config} />
)}
</aside>
</TableSidebarShell>
)
}

Expand Down Expand Up @@ -274,7 +270,7 @@ function ColumnConfigBody({
</TableSidebarHeaderAction>
</TableSidebarHeader>

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

<div className='flex items-center justify-end gap-2 border-[var(--border)] border-t px-2 py-3'>
<Button variant='default' size='sm' onClick={onClose}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -252,7 +253,7 @@ export function EnrichmentConfig({
</TableSidebarHeaderAction>
</TableSidebarHeader>

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

<div className='flex items-center justify-end gap-2 border-[var(--border)] border-t px-2 py-3'>
<Button variant='default' size='sm' onClick={onClose}>
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -31,16 +32,9 @@ interface EnrichmentsSidebarProps {
*/
export function EnrichmentsSidebar({ open, ...rest }: EnrichmentsSidebarProps) {
return (
<aside
role='dialog'
aria-label='Enrichments'
className={cn(
'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',
open ? 'translate-x-0 shadow-overlay' : 'translate-x-full'
)}
>
<TableSidebarShell open={open} aria-label='Enrichments'>
{open && <EnrichmentsSidebarBody {...rest} />}
</aside>
</TableSidebarShell>
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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(
<TableSidebarShell open={open} aria-label='Configure workflow'>
<TableSidebarScrollBody>
<input aria-label='Workflow name' defaultValue='Existing workflow' />
</TableSidebarScrollBody>
</TableSidebarShell>
)
})
}

render(true)
const dialog = container.querySelector<HTMLElement>('[role="dialog"]')!
const input = container.querySelector<HTMLInputElement>('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)
})
})
Original file line number Diff line number Diff line change
@@ -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 (
<aside
role='dialog'
aria-label={ariaLabel}
className={cn(
'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',
open ? 'translate-x-0 shadow-overlay' : 'translate-x-full'
)}
>
{children}
</aside>
)
}

interface TableSidebarScrollBodyProps {
children: ReactNode
}

/** The scrolling form area shared by column, workflow, and enrichment settings. */
export function TableSidebarScrollBody({ children }: TableSidebarScrollBodyProps) {
return (
<div className='flex-1 overflow-y-auto overflow-x-hidden px-2 pt-3 pb-2 [overflow-anchor:none]'>
{children}
</div>
)
}
Loading
Loading