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
7 changes: 7 additions & 0 deletions .changeset/solid-floating-panel-lazy-mount.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@ark-ui/solid': patch
---

Fixed FloatingPanel `Content` and `Positioner` not reacting to presence changes. The panel never appeared when
`lazyMount` was used, and was never removed from the DOM when `unmountOnExit` was used. `Content` now also forwards the
presence ref so exit animations are tracked before unmounting.
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { mergeProps } from '@zag-js/solid'
import { Show } from 'solid-js'
import { composeRefs } from '../../utils/compose-refs.ts'
import { type HTMLProps, type PolymorphicProps, ark } from '../factory.tsx'
import { usePresenceContext } from '../presence/index.tsx'
import { useFloatingPanelContext } from './use-floating-panel-context.ts'
Expand All @@ -15,9 +17,9 @@ export const FloatingPanelContent = (props: FloatingPanelContentProps) => {
props,
)

if (presence().unmounted) {
return null
}

return <ark.div {...mergedProps} />
return (
<Show when={!presence().unmounted}>
<ark.div {...mergedProps} ref={composeRefs(presence().ref, props.ref)} />
</Show>
)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { mergeProps } from '@zag-js/solid'
import { Show } from 'solid-js'
import { type HTMLProps, type PolymorphicProps, ark } from '../factory.tsx'
import { usePresenceContext } from '../presence/index.tsx'
import { useFloatingPanelContext } from './use-floating-panel-context.ts'
Expand All @@ -11,9 +12,9 @@ export const FloatingPanelPositioner = (props: FloatingPanelPositionerProps) =>
const mergedProps = mergeProps(() => floatingPanel().getPositionerProps(), props)
const presence = usePresenceContext()

if (presence().unmounted) {
return null
}

return <ark.div {...mergedProps} />
return (
<Show when={!presence().unmounted}>
<ark.div {...mergedProps} />
</Show>
)
}
19 changes: 19 additions & 0 deletions packages/solid/src/components/floating-panel/tests/basic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { FloatingPanel } from '@ark-ui/solid/floating-panel'
import { Portal } from 'solid-js/web'

export const ComponentUnderTest = (props: FloatingPanel.RootProps) => (
<FloatingPanel.Root {...props}>
<FloatingPanel.Trigger>Open Panel</FloatingPanel.Trigger>
<Portal>
<FloatingPanel.Positioner data-testid="positioner">
<FloatingPanel.Content>
<FloatingPanel.Header>
<FloatingPanel.Title>Floating Panel</FloatingPanel.Title>
<FloatingPanel.CloseTrigger>Close</FloatingPanel.CloseTrigger>
</FloatingPanel.Header>
<FloatingPanel.Body>Panel Body</FloatingPanel.Body>
</FloatingPanel.Content>
</FloatingPanel.Positioner>
</Portal>
</FloatingPanel.Root>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { render, screen, waitFor } from '@solidjs/testing-library'
import user from '@testing-library/user-event'
import { ComponentUnderTest } from './basic.tsx'

describe('FloatingPanel', () => {
it('should show panel content when opened', async () => {
render(() => <ComponentUnderTest />)

await user.click(screen.getByText('Open Panel'))
expect(await screen.findByText('Panel Body')).toBeVisible()

await user.click(screen.getByText('Close'))
await waitFor(() => expect(screen.queryByText('Panel Body')).not.toBeVisible())
})

it('should invoke onOpenChange if panel is closed', async () => {
const onOpenChange = vi.fn()
render(() => <ComponentUnderTest open onOpenChange={onOpenChange} />)
await user.click(screen.getByText('Close'))

expect(onOpenChange).toHaveBeenCalledTimes(1)
})

it('should be able to lazy mount', async () => {
render(() => <ComponentUnderTest lazyMount />)

expect(screen.queryByTestId('positioner')).not.toBeInTheDocument()

await user.click(screen.getByRole('button', { name: 'Open Panel' }))
expect(screen.getByTestId('positioner')).toBeInTheDocument()

await user.click(screen.getByRole('button', { name: 'Close Window' }))
expect(screen.getByTestId('positioner')).toBeInTheDocument()
})

it('should not have aria-controls if lazy mounted', async () => {
render(() => <ComponentUnderTest lazyMount />)
expect(screen.getByRole('button', { name: 'Open Panel' })).not.toHaveAttribute('aria-controls')
})

it('should lazy mount and unmount on exit', async () => {
render(() => <ComponentUnderTest lazyMount unmountOnExit />)

expect(screen.queryByTestId('positioner')).not.toBeInTheDocument()

await user.click(screen.getByRole('button', { name: 'Open Panel' }))
expect(screen.getByTestId('positioner')).toBeInTheDocument()

await user.click(screen.getByRole('button', { name: 'Close Window' }))
await waitFor(() => expect(screen.queryByTestId('positioner')).not.toBeInTheDocument())
})
})