Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Popover UI primitive #1849

Merged
merged 1 commit into from
Jan 23, 2025
Merged

feat: Add Popover UI primitive #1849

merged 1 commit into from
Jan 23, 2025

Conversation

cpcramer
Copy link
Contributor

@cpcramer cpcramer commented Jan 22, 2025

What changed? Why?
Add Popover UI Primitive.

This primitive handles positioning relative to an anchor / trigger element, focus management, click outside and escape key dismissal, portal rendering, and proper ARIA attributes.

Notes to reviewers

How has it been tested?
Tested with SwapSettings component - PR implementation to follow.

Tested updates to DismissableLayer, WalletModal still works as expected.

Copy link

vercel bot commented Jan 22, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
onchainkit-coverage ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 22, 2025 7:57pm
onchainkit-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 22, 2025 7:57pm
onchainkit-routes ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 22, 2025 7:57pm

/**
* Calculates the initial position of the popover based on the position prop.
*/
function getInitialPosition(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see these functions being reused so i've left them in the Popover file

// 2. This dismissal logic to fire (forcing close)
// This would create a race condition where the popover rapidly closes and reopens
const isTriggerClick = (event.target as HTMLElement).closest(
'[aria-label="Toggle swap settings"]',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meant to be hardcoded?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it will be passed as a param

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2nd PR -> #1856

Comment on lines +71 to +93
switch (align) {
case 'start':
if (isVerticalPosition) {
left = triggerRect.left;
} else {
top = triggerRect.top;
}
break;
case 'center':
if (isVerticalPosition) {
left = triggerRect.left + (triggerRect.width - contentRect.width) / 2;
} else {
top = triggerRect.top + (triggerRect.height - contentRect.height) / 2;
}
break;
case 'end':
if (isVerticalPosition) {
left = triggerRect.right - contentRect.width;
} else {
top = triggerRect.bottom - contentRect.height;
}
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably fine but if it gets any more complex I'd probably do this in some mapping like this since it's more declarative

const alignmentMap = {
  start: {
    vertical: () => ({ left: triggerRect.left }),
    horizontal: () => ({ top: triggerRect.top }),
  },
  center: {
    vertical: () => ({ left: triggerRect.left + (triggerRect.width - contentRect.width) / 2 }),
    horizontal: () => ({ top: triggerRect.top + (triggerRect.height - contentRect.height) / 2 }),
  },
  end: {
    vertical: () => ({ left: triggerRect.right - contentRect.width }),
    horizontal: () => ({ top: triggerRect.bottom - contentRect.height }),
  },
};

const { top, left } = {
  ...initialPosition,
  ...alignmentMap[align][isVerticalPosition ? 'vertical' : 'horizontal'](),
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it, will follow up with some cleanup here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm implementing this primitive into the SwapSettings component - I'm expecting that this alignment might change a little

const isTriggerClick = (event.target as HTMLElement).closest(
'[aria-label="Toggle swap settings"]',
);
if (isTriggerClick) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i won't block on this, but I find the name trigger click confusing. what is a trigger?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trigger is the button that opens the popup or dialog

Comment on lines +90 to +91
expect(popover.style.top).toBeDefined();
expect(popover.style.left).toBeDefined();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: seems like these should test to see if the correct values are passed in


fireEvent(window, new Event('resize'));

expect(screen.getByTestId('ockPopover')).toBeInTheDocument();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, this doesn't seem like it's testing if position was updated


fireEvent.scroll(window);

expect(screen.getByTestId('ockPopover')).toBeInTheDocument();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment here

@brendan-defi
Copy link
Contributor

@cpcramer approved with some comments on the tests

@cpcramer cpcramer merged commit 31f1cc2 into main Jan 23, 2025
16 checks passed
@cpcramer cpcramer deleted the paul/add-popover-primitive branch January 23, 2025 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

3 participants