Skip to content

Commit 63b223a

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
improvement(ui): share additional-field disclosures
1 parent 970d992 commit 63b223a

5 files changed

Lines changed: 130 additions & 39 deletions

File tree

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

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ChipInput,
88
type ComboboxOptionGroup,
99
cn,
10-
DashedDividerLine,
10+
FieldDisclosure,
1111
FieldDivider,
1212
Label,
1313
Loader,
@@ -16,7 +16,7 @@ import {
1616
Tooltip,
1717
toast,
1818
} from '@sim/emcn'
19-
import { ArrowLeft, ChevronDown, SquareArrowUpRight, X } from '@sim/emcn/icons'
19+
import { ArrowLeft, SquareArrowUpRight, X } from '@sim/emcn/icons'
2020
import { toError } from '@sim/utils/errors'
2121
import { generateId } from '@sim/utils/id'
2222
import { findValidationIssue, isValidationError } from '@/lib/api/client/errors'
@@ -838,23 +838,9 @@ export function WorkflowSidebarBody({
838838
)}
839839
{selectedWorkflowId && (
840840
<>
841-
<div className='flex items-center gap-2.5 px-0.5 pt-3.5 pb-3'>
842-
<DashedDividerLine className='flex-1' />
843-
<button
844-
type='button'
845-
onClick={() => setShowAdvanced((v) => !v)}
846-
className='flex items-center gap-1.5 whitespace-nowrap text-[var(--text-secondary)] text-small hover-hover:text-[var(--text-primary)]'
847-
>
848-
{showAdvanced ? 'Hide additional fields' : 'Show additional fields'}
849-
<ChevronDown
850-
className={cn(
851-
'size-[14px] transition-transform duration-200',
852-
showAdvanced && 'rotate-180'
853-
)}
854-
/>
855-
</button>
856-
<DashedDividerLine className='flex-1' />
857-
</div>
841+
<FieldDisclosure expanded={showAdvanced} onClick={() => setShowAdvanced((v) => !v)}>
842+
{showAdvanced ? 'Hide additional fields' : 'Show additional fields'}
843+
</FieldDisclosure>
858844
{showAdvanced && (
859845
<>
860846
<InputMappingSection

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx‎

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
'use client'
22

33
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
4-
import { Button, DashedDividerLine, FieldDivider, Loader, Tooltip } from '@sim/emcn'
4+
import {
5+
Button,
6+
DashedDividerLine,
7+
FieldDisclosure,
8+
FieldDivider,
9+
Loader,
10+
Tooltip,
11+
} from '@sim/emcn'
512
import {
613
BookOpen,
714
Check,
8-
ChevronDown,
915
ChevronUp,
1016
Lock,
1117
Pencil,
@@ -743,22 +749,14 @@ export function Editor() {
743749
})}
744750

745751
{hasAdditionalFields && canEditBlock && (
746-
<div className='flex items-center gap-2.5 px-0.5 pt-3.5 pb-3'>
747-
<DashedDividerLine className='flex-1' />
748-
<button
749-
type='button'
750-
onClick={handleToggleAdditionalFields}
751-
className='flex items-center gap-1.5 whitespace-nowrap text-[var(--text-secondary)] text-small hover-hover:text-[var(--text-primary)]'
752-
>
753-
{displayAdvancedOptions
754-
? 'Hide additional fields'
755-
: 'Show additional fields'}
756-
<ChevronDown
757-
className={`size-[14px] transition-transform duration-200 ${displayAdvancedOptions ? 'rotate-180' : ''}`}
758-
/>
759-
</button>
760-
<DashedDividerLine className='flex-1' />
761-
</div>
752+
<FieldDisclosure
753+
expanded={displayAdvancedOptions}
754+
onClick={handleToggleAdditionalFields}
755+
>
756+
{displayAdvancedOptions
757+
? 'Hide additional fields'
758+
: 'Show additional fields'}
759+
</FieldDisclosure>
762760
)}
763761
{hasAdditionalFields && !canEditBlock && displayAdvancedOptions && (
764762
<div className='flex items-center gap-2.5 px-0.5 pt-3.5 pb-3'>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { act } from 'react'
5+
import { createRoot, type Root } from 'react-dom/client'
6+
import { afterEach, describe, expect, it, vi } from 'vitest'
7+
import { FieldDisclosure } from './field-divider'
8+
9+
let root: Root | null = null
10+
let container: HTMLDivElement | null = null
11+
12+
afterEach(() => {
13+
act(() => root?.unmount())
14+
container?.remove()
15+
root = null
16+
container = null
17+
})
18+
19+
describe('FieldDisclosure', () => {
20+
it('leaves expansion to the caller and does not submit its surrounding form', () => {
21+
const onClick = vi.fn()
22+
const onSubmit = vi.fn((event) => event.preventDefault())
23+
container = document.createElement('div')
24+
document.body.appendChild(container)
25+
root = createRoot(container)
26+
const render = (expanded: boolean, disabled = false) => (
27+
<form onSubmit={onSubmit}>
28+
<FieldDisclosure
29+
expanded={expanded}
30+
disabled={disabled}
31+
onClick={onClick}
32+
aria-controls='additional-fields'
33+
>
34+
{expanded ? 'Hide additional fields' : 'Show additional fields'}
35+
</FieldDisclosure>
36+
<div id='additional-fields' hidden={!expanded}>
37+
Additional fields
38+
</div>
39+
</form>
40+
)
41+
act(() => root?.render(render(false)))
42+
const button = container.querySelector('button')!
43+
expect(button.getAttribute('aria-expanded')).toBe('false')
44+
expect(button.getAttribute('aria-controls')).toBe('additional-fields')
45+
act(() => {
46+
button.focus()
47+
button.click()
48+
})
49+
expect(document.activeElement).toBe(button)
50+
expect(onClick).toHaveBeenCalledTimes(1)
51+
expect(onSubmit).not.toHaveBeenCalled()
52+
expect(button.getAttribute('aria-expanded')).toBe('false')
53+
54+
act(() => root?.render(render(true)))
55+
expect(button.getAttribute('aria-expanded')).toBe('true')
56+
expect(button.textContent).toBe('Hide additional fields')
57+
expect(container.querySelector<HTMLElement>('#additional-fields')?.hidden).toBe(false)
58+
59+
act(() => root?.render(render(true, true)))
60+
act(() => button.click())
61+
expect(onClick).toHaveBeenCalledTimes(1)
62+
expect(onSubmit).not.toHaveBeenCalled()
63+
})
64+
})

‎packages/emcn/src/components/field-divider/field-divider.tsx‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { ChevronDown } from '../../icons'
12
import { cn } from '../../lib/cn'
3+
import { Button, type ButtonProps } from '../button/button'
24

35
const DASHED_DIVIDER_STYLE = {
46
backgroundImage:
@@ -54,4 +56,40 @@ function FieldDivider({ className, subblockMarker = false, ...props }: FieldDivi
5456
)
5557
}
5658

57-
export { DashedDividerLine, FieldDivider }
59+
export interface FieldDisclosureProps
60+
extends Pick<ButtonProps, 'onClick' | 'disabled' | 'aria-controls' | 'children'> {
61+
expanded: boolean
62+
}
63+
64+
/**
65+
* Controlled disclosure between field groups. The caller owns the label,
66+
* expanded state and fields; this supplies the divider lines and rotating chevron.
67+
*
68+
* @example
69+
* <FieldDisclosure expanded={expanded} onClick={onToggle}>
70+
* {expanded ? 'Hide additional fields' : 'Show additional fields'}
71+
* </FieldDisclosure>
72+
*/
73+
function FieldDisclosure({ expanded, children, ...props }: FieldDisclosureProps) {
74+
return (
75+
<div className='flex items-center gap-2.5 px-0.5 pt-3.5 pb-3'>
76+
<DashedDividerLine className='flex-1' />
77+
<Button
78+
{...props}
79+
type='button'
80+
variant='ghost'
81+
size={null}
82+
aria-expanded={expanded}
83+
className='gap-1.5 whitespace-nowrap p-0 text-small'
84+
>
85+
{children}
86+
<ChevronDown
87+
className={cn('size-[14px] transition-transform duration-200', expanded && 'rotate-180')}
88+
/>
89+
</Button>
90+
<DashedDividerLine className='flex-1' />
91+
</div>
92+
)
93+
}
94+
95+
export { DashedDividerLine, FieldDisclosure, FieldDivider }

‎packages/emcn/src/components/index.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,12 @@ export {
153153
dropdownMenuRowClass,
154154
} from './dropdown-menu/dropdown-menu'
155155
export { Expandable, ExpandableContent } from './expandable/expandable'
156-
export { DashedDividerLine, FieldDivider } from './field-divider/field-divider'
156+
export {
157+
DashedDividerLine,
158+
FieldDisclosure,
159+
type FieldDisclosureProps,
160+
FieldDivider,
161+
} from './field-divider/field-divider'
157162
export { Info } from './info/info'
158163
export {
159164
InfoCard,

0 commit comments

Comments
 (0)