diff --git a/apps/sim/app/(interfaces)/chat/components/message/components/file-download.tsx b/apps/sim/app/(interfaces)/chat/components/message/components/file-download.tsx index 938ea3f2b3c..0e3c40e1f15 100644 --- a/apps/sim/app/(interfaces)/chat/components/message/components/file-download.tsx +++ b/apps/sim/app/(interfaces)/chat/components/message/components/file-download.tsx @@ -254,7 +254,7 @@ export function ChatFileDownloadAll({ files }: ChatFileDownloadAllProps) { variant='ghost-secondary' onClick={handleDownloadAll} disabled={isDownloading} - className='p-0' + size='icon' > {isDownloading ? ( diff --git a/apps/sim/app/(interfaces)/chat/components/message/message.tsx b/apps/sim/app/(interfaces)/chat/components/message/message.tsx index 9458ed16be7..1b99ccf96cc 100644 --- a/apps/sim/app/(interfaces)/chat/components/message/message.tsx +++ b/apps/sim/app/(interfaces)/chat/components/message/message.tsx @@ -274,7 +274,7 @@ export const ClientChatMessage = memo(function ClientChatMessage({ - + +
{/* `disabled` on the fieldset reaches every native control inside, 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 f411281d21e..e158e4ab159 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 @@ -21,6 +21,10 @@ import type { ColumnDefinition, WorkflowGroup, WorkflowGroupOutput } from '@/lib import { columnMatchesRef, getColumnId } from '@/lib/table/column-keys' import { deriveOutputColumnName } from '@/lib/table/column-naming' import { FieldError } from '@/app/workspace/[workspaceId]/tables/[tableId]/components/sidebar-fields' +import { + TableSidebarHeader, + TableSidebarHeaderAction, +} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-header/table-sidebar-header' import type { EnrichmentConfig as EnrichmentDef } from '@/enrichments/types' import { useAddWorkflowGroup, @@ -232,31 +236,21 @@ export function EnrichmentConfig({ return (
-
+
- +
- -
+ +
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 0aebd34cb4c..421055477fc 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,9 +1,13 @@ 'use client' import { useState } from 'react' -import { Button, ChipInput, cn } from '@sim/emcn' +import { ChipInput, cn } 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 { ALL_ENRICHMENTS } from '@/enrichments' import { getEnrichment } from '@/enrichments/registry' import type { EnrichmentConfig as EnrichmentDef } from '@/enrichments/types' @@ -71,19 +75,12 @@ function EnrichmentsSidebarBody({ if (editGroup && !editEnrichment) { return (
-
+

Enrichment

- -
+ +

This enrichment ("{editGroup.enrichmentId}") is no longer available. Delete the column @@ -118,19 +115,12 @@ function EnrichmentsSidebarBody({ return (

-
+

Enrichments

- -
+ +
+ {children} +
+ ) +} + +interface TableSidebarHeaderActionProps extends ButtonHTMLAttributes { + 'aria-label': string +} + +export const TableSidebarHeaderAction = forwardRef< + HTMLButtonElement, + TableSidebarHeaderActionProps +>(({ className, ...props }, ref) => ( + + )}

- -
+ +
{/* Single-output mode renames this column directly. */} @@ -838,23 +832,9 @@ export function WorkflowSidebarBody({ )} {selectedWorkflowId && ( <> -
- - - -
+ setShowAdvanced((v) => !v)}> + {showAdvanced ? 'Hide additional fields' : 'Show additional fields'} + {showAdvanced && ( <> collaborativeBatchToggleLocked([currentBlockId!])} aria-label='Unlock block' > - + ) : (
@@ -499,7 +505,7 @@ export function Editor() { - -
+ + {displayAdvancedOptions + ? 'Hide additional fields' + : 'Show additional fields'} + )} {hasAdditionalFields && !canEditBlock && displayAdvancedOptions && (
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar.tsx index 09ede00b538..27f4ee1ed93 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar.tsx @@ -875,12 +875,7 @@ export const Toolbar = memo(

Toolbar

{!isSearchActive ? ( - ) : ( diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx index 5f894cfc5f3..5337e108a8d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx @@ -915,7 +915,7 @@ export const Panel = memo(function Panel() { diff --git a/packages/emcn/src/components/field-divider/field-divider.dom.test.tsx b/packages/emcn/src/components/field-divider/field-divider.dom.test.tsx new file mode 100644 index 00000000000..ed3a624673d --- /dev/null +++ b/packages/emcn/src/components/field-divider/field-divider.dom.test.tsx @@ -0,0 +1,64 @@ +/** + * @vitest-environment jsdom + */ +import { act } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { FieldDisclosure } from './field-divider' + +let root: Root | null = null +let container: HTMLDivElement | null = null + +afterEach(() => { + act(() => root?.unmount()) + container?.remove() + root = null + container = null +}) + +describe('FieldDisclosure', () => { + it('leaves expansion to the caller and does not submit its surrounding form', () => { + const onClick = vi.fn() + const onSubmit = vi.fn((event) => event.preventDefault()) + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) + const render = (expanded: boolean, disabled = false) => ( +
+ + {expanded ? 'Hide additional fields' : 'Show additional fields'} + + +
+ ) + act(() => root?.render(render(false))) + const button = container.querySelector('button')! + expect(button.getAttribute('aria-expanded')).toBe('false') + expect(button.getAttribute('aria-controls')).toBe('additional-fields') + act(() => { + button.focus() + button.click() + }) + expect(document.activeElement).toBe(button) + expect(onClick).toHaveBeenCalledTimes(1) + expect(onSubmit).not.toHaveBeenCalled() + expect(button.getAttribute('aria-expanded')).toBe('false') + + act(() => root?.render(render(true))) + expect(button.getAttribute('aria-expanded')).toBe('true') + expect(button.textContent).toBe('Hide additional fields') + expect(container.querySelector('#additional-fields')?.hidden).toBe(false) + + act(() => root?.render(render(true, true))) + act(() => button.click()) + expect(onClick).toHaveBeenCalledTimes(1) + expect(onSubmit).not.toHaveBeenCalled() + }) +}) diff --git a/packages/emcn/src/components/field-divider/field-divider.tsx b/packages/emcn/src/components/field-divider/field-divider.tsx index 7408a21360f..4b15d867f80 100644 --- a/packages/emcn/src/components/field-divider/field-divider.tsx +++ b/packages/emcn/src/components/field-divider/field-divider.tsx @@ -1,4 +1,6 @@ +import { ChevronDown } from '../../icons' import { cn } from '../../lib/cn' +import { Button, type ButtonProps } from '../button/button' const DASHED_DIVIDER_STYLE = { backgroundImage: @@ -54,4 +56,40 @@ function FieldDivider({ className, subblockMarker = false, ...props }: FieldDivi ) } -export { DashedDividerLine, FieldDivider } +export interface FieldDisclosureProps + extends Pick { + expanded: boolean +} + +/** + * Controlled disclosure between field groups. The caller owns the label, + * expanded state and fields; this supplies the divider lines and rotating chevron. + * + * @example + * + * {expanded ? 'Hide additional fields' : 'Show additional fields'} + * + */ +function FieldDisclosure({ expanded, children, ...props }: FieldDisclosureProps) { + return ( +
+ + + +
+ ) +} + +export { DashedDividerLine, FieldDisclosure, FieldDivider } diff --git a/packages/emcn/src/components/index.ts b/packages/emcn/src/components/index.ts index 6628b0a2d1b..29607eea0a6 100644 --- a/packages/emcn/src/components/index.ts +++ b/packages/emcn/src/components/index.ts @@ -153,7 +153,12 @@ export { dropdownMenuRowClass, } from './dropdown-menu/dropdown-menu' export { Expandable, ExpandableContent } from './expandable/expandable' -export { DashedDividerLine, FieldDivider } from './field-divider/field-divider' +export { + DashedDividerLine, + FieldDisclosure, + type FieldDisclosureProps, + FieldDivider, +} from './field-divider/field-divider' export { Info } from './info/info' export { InfoCard,