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
Expand Up @@ -15,6 +15,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
Duplicate,
OverlayActionButton,
Search as SearchIcon,
Tooltip,
useCopyToClipboard,
Expand Down Expand Up @@ -504,39 +505,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 @@ -27,6 +27,7 @@ import {
Duplicate,
Eye,
handleKeyboardActivation,
OverlayActionButton,
Redo,
Search as SearchIcon,
Tooltip,
Expand Down Expand Up @@ -168,39 +169,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 @@ -2,7 +2,6 @@

import { useId, useState } from 'react'
import {
Button,
ChipButtonGroup,
ChipButtonGroupItem,
ChipConfirmModal,
Expand All @@ -12,6 +11,7 @@ import {
cn,
Expand,
Label,
OverlayActionButton,
Skeleton,
Tooltip,
} from '@sim/emcn'
Expand Down Expand Up @@ -241,15 +241,15 @@ export function GeneralDeploy({
</div>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button
<OverlayActionButton
aria-label='See preview'
type='button'
variant='default'
size='md'
onClick={() => setShowExpandedPreview(true)}
className='absolute right-[8px] bottom-2 z-10 size-[28px] cursor-pointer bg-transparent p-0 backdrop-blur-xs hover-hover:bg-[var(--surface-3)]'
className='absolute right-[8px] bottom-2 z-10'
>
<Expand className='size-[14px]' />
</Button>
</OverlayActionButton>
</Tooltip.Trigger>
<Tooltip.Content side='top'>See preview</Tooltip.Content>
</Tooltip.Root>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
Input,
Label,
OverflowText,
OverlayActionButton,
Tooltip,
} from '@sim/emcn'
import {
Expand Down Expand Up @@ -1235,41 +1236,37 @@ function PreviewEditorContent({
<div className='absolute top-[7px] right-[6px] z-10 flex gap-1'>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button
<OverlayActionButton
aria-label={copiedSection === 'input' ? 'Copied' : 'Copy'}
type='button'
variant='ghost'
onClick={(e) => {
e.stopPropagation()
handleCopySection(formatValueAsJson(executionData.input), 'input')
}}
className='size-[20px] cursor-pointer border border-[var(--border-1)] bg-transparent p-0 backdrop-blur-xs hover-hover:bg-[var(--surface-4)]'
>
{copiedSection === 'input' ? (
<Check className='size-[10px] text-[var(--text-success)]' />
) : (
<Clipboard className='size-[10px]' />
)}
</Button>
</OverlayActionButton>
</Tooltip.Trigger>
<Tooltip.Content side='top'>
{copiedSection === 'input' ? 'Copied' : 'Copy'}
</Tooltip.Content>
</Tooltip.Root>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button
<OverlayActionButton
aria-label='Search'
type='button'
variant='ghost'
onClick={(e) => {
e.stopPropagation()
activateSearch()
}}
className='size-[20px] cursor-pointer border border-[var(--border-1)] bg-transparent p-0 backdrop-blur-xs hover-hover:bg-[var(--surface-4)]'
>
<Search className='size-[10px]' />
</Button>
</OverlayActionButton>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Search</Tooltip.Content>
</Tooltip.Root>
Expand Down Expand Up @@ -1309,41 +1306,37 @@ function PreviewEditorContent({
<div className='absolute top-[7px] right-[6px] z-10 flex gap-1'>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button
<OverlayActionButton
aria-label={copiedSection === 'output' ? 'Copied' : 'Copy'}
type='button'
variant='ghost'
onClick={(e) => {
e.stopPropagation()
handleCopySection(formatValueAsJson(executionData.output), 'output')
}}
className='size-[20px] cursor-pointer border border-[var(--border-1)] bg-transparent p-0 backdrop-blur-xs hover-hover:bg-[var(--surface-4)]'
>
{copiedSection === 'output' ? (
<Check className='size-[10px] text-[var(--text-success)]' />
) : (
<Clipboard className='size-[10px]' />
)}
</Button>
</OverlayActionButton>
</Tooltip.Trigger>
<Tooltip.Content side='top'>
{copiedSection === 'output' ? 'Copied' : 'Copy'}
</Tooltip.Content>
</Tooltip.Root>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button
<OverlayActionButton
aria-label='Search'
type='button'
variant='ghost'
onClick={(e) => {
e.stopPropagation()
activateSearch()
}}
className='size-[20px] cursor-pointer border border-[var(--border-1)] bg-transparent p-0 backdrop-blur-xs hover-hover:bg-[var(--surface-4)]'
>
<Search className='size-[10px]' />
</Button>
</OverlayActionButton>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Search</Tooltip.Content>
</Tooltip.Root>
Expand Down
5 changes: 5 additions & 0 deletions packages/emcn/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,11 @@ export {
overflowTextClipClass,
overflowTextFadeClass,
} from './overflow-text/overflow-text'
export {
OverlayActionButton,
type OverlayActionButtonProps,
overlayActionButtonVariants,
} from './overlay-action-button/overlay-action-button'
export {
Popover,
PopoverAnchor,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/** @vitest-environment jsdom */
import { act, createRef, type ReactNode } from 'react'
import { Button, OverlayActionButton, Tooltip } from '@sim/emcn'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, describe, expect, it, vi } from 'vitest'

let root: Root | null = null
let container: HTMLDivElement | null = null

function mount(children: ReactNode) {
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
container = document.createElement('div')
document.body.appendChild(container)
root = createRoot(container)
act(() => root?.render(children))
return container
}

afterEach(() => {
if (root) act(() => root?.unmount())
container?.remove()
root = null
container = null
vi.useRealTimers()
})

/** Pre-migration recipes from log details and the deployment preview. */
const PREVIOUS = [
{
name: 'default 20px adaptive action',
props: {},
variant: 'default',
className:
'size-[20px] cursor-pointer border-[var(--border-1)] bg-transparent p-0 backdrop-blur-xs hover-hover:bg-[var(--surface-3)]',
},
{
name: '28px adaptive action',
props: { size: 'md' },
variant: 'default',
className:
'size-[28px] cursor-pointer bg-transparent p-0 backdrop-blur-xs hover-hover:bg-[var(--surface-3)]',
},
] as const

describe('OverlayActionButton', () => {
it.each(PREVIOUS)('preserves the previous $name markup', ({ props, variant, className }) => {
const view = mount(
<>
<Button variant={variant} aria-label='Copy' className={`${className} shrink-0`}>
<svg className='size-[10px]' aria-hidden='true' />
</Button>
<OverlayActionButton {...props} aria-label='Copy' className='shrink-0'>
<svg className='size-[10px]' aria-hidden='true' />
</OverlayActionButton>
</>
)
const [previous, current] = view.querySelectorAll('button')
/** The old border-1 token aliases border; class order changes when recipes are composed. */
for (const button of [previous, current]) {
button.className = button.className
.replaceAll('--border-1', '--border')
.split(/\s+/)
.sort()
.join(' ')
}
expect(current.outerHTML).toBe(previous.outerHTML)
})

it('forwards refs and native props through a tooltip and suppresses disabled clicks', () => {
vi.useFakeTimers()
const ref = createRef<HTMLButtonElement>()
const onClick = vi.fn()
const onKeyDown = vi.fn()
const action = (disabled: boolean) => (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<OverlayActionButton
ref={ref}
type='button'
aria-label='Copy'
data-action='copy'
disabled={disabled}
onClick={onClick}
onKeyDown={onKeyDown}
/>
</Tooltip.Trigger>
<Tooltip.Content>Copy output</Tooltip.Content>
</Tooltip.Root>
)
const view = mount(action(false))
const button = view.querySelector('button')
if (!button) throw new Error('Button did not render')
expect(view.querySelectorAll('button')).toHaveLength(1)
expect(ref.current).toBe(button)
expect(button.type).toBe('button')
expect(button.dataset.action).toBe('copy')
expect(button.getAttribute('aria-label')).toBe('Copy')
act(() =>
button.dispatchEvent(
new MouseEvent('pointerover', { bubbles: true, clientX: 200, clientY: 200 })
)
)
expect(document.querySelector('[role="tooltip"]')?.textContent).toBe('Copy output')
act(() => button.focus())
expect(document.activeElement).toBe(button)
const keyEvent = new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })
act(() => button.dispatchEvent(keyEvent))
expect(onKeyDown).toHaveBeenCalledTimes(1)
expect(onKeyDown.mock.calls[0][0].nativeEvent).toBe(keyEvent)
act(() => button.click())
expect(onClick).toHaveBeenCalledTimes(1)
act(() => root?.render(action(true)))
expect(button.disabled).toBe(true)
act(() => button.click())
expect(onClick).toHaveBeenCalledTimes(1)
})
})
Loading
Loading