Skip to content
Open
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
@@ -0,0 +1,73 @@
/**
* @vitest-environment jsdom
*/
import { act, useState } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { ForkDisclosureTrigger } from '@/ee/workspace-forking/components/fork-disclosure-trigger'

let container: HTMLDivElement
let root: Root

function Disclosures() {
const [resourcesOpen, setResourcesOpen] = useState(false)
const [mappingOpen, setMappingOpen] = useState(true)

return (
<>
<ForkDisclosureTrigger
label='Workflows (2)'
expanded={resourcesOpen}
onToggle={() => setResourcesOpen((value) => !value)}
className='min-w-0 flex-1 gap-1'
labelClassName='flex-1'
/>
<ForkDisclosureTrigger
label='Credentials'
expanded={mappingOpen}
onToggle={() => setMappingOpen((value) => !value)}
className='w-full gap-2'
trailing={<span>Needs review</span>}
/>
</>
)
}

describe('ForkDisclosureTrigger', () => {
beforeEach(() => {
container = document.createElement('div')
document.body.appendChild(container)
root = createRoot(container)
act(() => root.render(<Disclosures />))
})

afterEach(() => {
act(() => root.unmount())
container.remove()
})

it('keeps each native disclosure button independent and announces its current state', () => {
const [resources, mapping] = Array.from(container.querySelectorAll('button'))
expect(resources?.getAttribute('aria-expanded')).toBe('false')
expect(mapping?.getAttribute('aria-expanded')).toBe('true')
expect(resources?.textContent).toContain('Workflows (2)')
expect(mapping?.textContent).toContain('CredentialsNeeds review')

act(() => resources?.click())
expect(resources?.getAttribute('aria-expanded')).toBe('true')
expect(mapping?.getAttribute('aria-expanded')).toBe('true')

Comment thread
BillLeoutsakosvl346 marked this conversation as resolved.
act(() => mapping?.click())
expect(resources?.getAttribute('aria-expanded')).toBe('true')
expect(mapping?.getAttribute('aria-expanded')).toBe('false')
})

it('keeps the disclosure trigger keyboard focusable with its visible focus treatment', () => {
const trigger = container.querySelector<HTMLButtonElement>('button')
act(() => trigger?.focus())

expect(document.activeElement).toBe(trigger)
expect(trigger?.classList.contains('focus-visible:outline-2')).toBe(true)
expect(trigger?.classList.contains('focus-visible:outline-[var(--selection)]')).toBe(true)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use client'

import type { ReactNode } from 'react'
import { ChevronDown, cn, disclosureChevronClass, OverflowText } from '@sim/emcn'

interface ForkDisclosureTriggerProps {
label: string
expanded: boolean
onToggle: () => void
className: string
labelClassName?: string
children?: ReactNode
trailing?: ReactNode
}

/** The common label and chevron trigger used by fork picker disclosures. */
export function ForkDisclosureTrigger({
label,
expanded,
onToggle,
className,
labelClassName,
children,
trailing,
}: ForkDisclosureTriggerProps) {
return (
<button
type='button'
aria-expanded={expanded}
className={cn(
'flex items-center text-left hover:text-[var(--text-primary)] focus-visible:rounded-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--selection)] focus-visible:outline-offset-2',
className
)}
onClick={onToggle}
>
<OverflowText label={label} className={labelClassName}>
{children}
</OverflowText>
{trailing}
<ChevronDown
className={cn(disclosureChevronClass, 'transition-transform', expanded && 'rotate-180')}
/>
</button>
)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
'use client'

import { useId, useMemo, useState } from 'react'
import { Checkbox, ChevronDown, cn, OverflowText, toast } from '@sim/emcn'
import { Checkbox, cn, OverflowText, toast } from '@sim/emcn'
import { getErrorMessage } from '@sim/utils/errors'
import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
import { ForkDisclosureTrigger } from '@/ee/workspace-forking/components/fork-disclosure-trigger'
import { useUpdateForkExcludedWorkflows } from '@/ee/workspace-forking/hooks/workspace-fork'
import { useFolders } from '@/hooks/queries/folders'
import { useWorkflows } from '@/hooks/queries/workflows'
Expand Down Expand Up @@ -200,26 +201,17 @@ function ExcludedFolderRow({
onCheckedChange={() => onToggle(folder.descendantWorkflowIds, headerState !== true)}
disabled={disabled}
/>
<button
type='button'
className='flex min-w-0 items-center gap-1.5 text-left hover:text-[var(--text-primary)]'
onClick={() => setExpanded((value) => !value)}
<ForkDisclosureTrigger
label={`${folder.name} (${selectedCount > 0 ? `${selectedCount}/${total}` : total})`}
expanded={expanded}
onToggle={() => setExpanded((value) => !value)}
className='min-w-0 gap-1.5'
>
<OverflowText
label={`${folder.name} (${selectedCount > 0 ? `${selectedCount}/${total}` : total})`}
>
{folder.name}{' '}
<span className='text-[var(--text-muted)]'>
({selectedCount > 0 ? `${selectedCount}/${total}` : total})
</span>
</OverflowText>
<ChevronDown
className={cn(
'size-[14px] shrink-0 text-[var(--text-icon)] transition-transform',
expanded && 'rotate-180'
)}
/>
</button>
{folder.name}{' '}
<span className='text-[var(--text-muted)]'>
({selectedCount > 0 ? `${selectedCount}/${total}` : total})
</span>
</ForkDisclosureTrigger>
</div>
{expanded ? (
<div className='relative'>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use client'

import { useId, useState } from 'react'
import { Checkbox, ChevronDown, cn, OverflowText } from '@sim/emcn'
import { Checkbox, cn, OverflowText } from '@sim/emcn'
import { ForkDisclosureTrigger } from '@/ee/workspace-forking/components/fork-disclosure-trigger'

export interface ForkFileTreeItem {
id: string
Expand Down Expand Up @@ -132,22 +133,13 @@ function ForkFileFolderRow({
onCheckedChange={() => onToggleMany(fileIds, headerState !== true)}
disabled={disabled}
/>
<button
type='button'
className='flex min-w-0 flex-1 items-center gap-1 text-left hover:text-[var(--text-primary)]'
onClick={() => setExpanded((value) => !value)}
>
<OverflowText
label={`${folder.name} (${selectedCount > 0 ? `${selectedCount}/${total}` : total})`}
className='flex-1'
/>
<ChevronDown
className={cn(
'size-[14px] shrink-0 text-[var(--text-icon)] transition-transform',
expanded && 'rotate-180'
)}
/>
</button>
<ForkDisclosureTrigger
label={`${folder.name} (${selectedCount > 0 ? `${selectedCount}/${total}` : total})`}
expanded={expanded}
onToggle={() => setExpanded((value) => !value)}
className='min-w-0 flex-1 gap-1'
labelClassName='flex-1'
/>
</div>
{expanded ? (
<div className='ml-6 flex flex-col gap-0.5'>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use client'

import { useId, useMemo, useState } from 'react'
import { Checkbox, ChevronDown, cn, OverflowText } from '@sim/emcn'
import { Checkbox, cn, OverflowText } from '@sim/emcn'
import { ForkDisclosureTrigger } from '@/ee/workspace-forking/components/fork-disclosure-trigger'
import {
ForkFileTree,
type ForkFlatFile,
Expand Down Expand Up @@ -64,22 +65,13 @@ export function ResourceKindRow({
}
disabled={disabled}
/>
<button
type='button'
className='flex min-w-0 flex-1 items-center gap-1 text-left hover:text-[var(--text-primary)]'
onClick={() => setExpanded((value) => !value)}
>
<OverflowText
label={`${label} (${selectedCount > 0 ? `${selectedCount}/${total}` : total})`}
className='flex-1'
/>
<ChevronDown
className={cn(
'size-[14px] shrink-0 text-[var(--text-icon)] transition-transform',
expanded && 'rotate-180'
)}
/>
</button>
<ForkDisclosureTrigger
label={`${label} (${selectedCount > 0 ? `${selectedCount}/${total}` : total})`}
expanded={expanded}
onToggle={() => setExpanded((value) => !value)}
className='min-w-0 flex-1 gap-1'
labelClassName='flex-1'
/>
</div>

{expanded ? (
Expand Down Expand Up @@ -160,22 +152,13 @@ export function FileKindRow({
onCheckedChange={() => onToggleAll(headerState !== true)}
disabled={disabled}
/>
<button
type='button'
className='flex min-w-0 flex-1 items-center gap-1 text-left hover:text-[var(--text-primary)]'
onClick={() => setExpanded((value) => !value)}
>
<OverflowText
label={`${label} (${selectedCount > 0 ? `${selectedCount}/${total}` : total})`}
className='flex-1'
/>
<ChevronDown
className={cn(
'size-[14px] shrink-0 text-[var(--text-icon)] transition-transform',
expanded && 'rotate-180'
)}
/>
</button>
<ForkDisclosureTrigger
label={`${label} (${selectedCount > 0 ? `${selectedCount}/${total}` : total})`}
expanded={expanded}
onToggle={() => setExpanded((value) => !value)}
className='min-w-0 flex-1 gap-1'
labelClassName='flex-1'
/>
</div>

{expanded ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
import { type Dispatch, Fragment, type SetStateAction, useMemo, useState } from 'react'
import {
Badge,
ChevronDown,
Chip,
ChipCombobox,
ChipInput,
ChipSwitch,
CollapsibleCard,
cn,
FieldDivider,
Label,
OverflowText,
Expand All @@ -26,6 +24,7 @@ import type {
import type { SelectorKey } from '@/lib/selectors/manifest'
import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section'
import { ForkDisclosureTrigger } from '@/ee/workspace-forking/components/fork-disclosure-trigger'
import {
FileKindRow,
ResourceKindRow,
Expand Down Expand Up @@ -610,22 +609,18 @@ function MappingKindRow({ controller, group, summary }: MappingKindRowProps) {
const badge = kindStatusBadge(summary)
return (
<div className='flex flex-col'>
<button
type='button'
onClick={() => setOpen((value) => !value)}
className='flex w-full items-center gap-2 text-left text-[var(--text-body)] text-sm transition-colors hover:text-[var(--text-primary)]'
>
<OverflowText label={group.label} className='flex-1' />
<Badge variant={badge.variant} size='sm' dot>
{badge.label}
</Badge>
<ChevronDown
className={cn(
'size-[14px] shrink-0 text-[var(--text-icon)] transition-transform',
open && 'rotate-180'
)}
/>
</button>
<ForkDisclosureTrigger
label={group.label}
expanded={open}
onToggle={() => setOpen((value) => !value)}
className='w-full gap-2 text-[var(--text-body)] text-sm transition-colors'
labelClassName='flex-1'
trailing={
<Badge variant={badge.variant} size='sm' dot>
{badge.label}
</Badge>
}
/>
{open ? (
<div className='flex flex-col pt-3 pb-1'>
{group.items.map((entry, index) => (
Expand Down
Loading