-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
ref(slideoverpanel) refactor using core primitives #107127
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
base: master
Are you sure you want to change the base?
Conversation
|
@cursoragent review |
|
Now let me provide a comprehensive code review of this PR. Code Review: SlideOverPanel Refactor using Core PrimitivesOverviewThis PR successfully refactors Strengths ✅
Concerns & Questions 🤔
const BOTTOM_SIDE_PANEL_HEIGHT = '50vh';const COLLAPSED_STYLES = {
bottom: {
transform: `translateX(0) translateY(${BOTTOM_SIDE_PANEL_HEIGHT})`,
opacity: 0,
},
right: {transform: `translateX(${RIGHT_SIDE_PANEL_WIDTH}) translateY(0)`, opacity: 0},
left: {transform: `translateX(-${LEFT_SIDE_PANEL_WIDTH}) translateY(0)`, opacity: 0},
};Issue: The Recommendation: Either:
{...(props as any)}Issue: The Recommendation: Consider a more precise type assertion or explicitly type the conflicting props.
style={{zIndex: theme.zIndex.drawer}}Issue: This uses inline Recommendation: Consider adding
import SlideOverDocumentation from '!!type-loader!@sentry/scraps/slideOverPanel';The examples look correct, but verify that all references are updated. Minor Suggestions 💡
width:
typeof props.width === 'string'
? {
'2xs': `calc(100vw - ${theme.space.lg})`,
sm: props.width ?? RIGHT_SIDE_PANEL_WIDTH,
}
: props.width,Consider documenting what non-string width values are expected (responsive objects?).
import {Surface, type ContainerProps} from '@sentry/scraps/layout';Verify this is properly exported from the package. Testing Recommendations 🧪
Overall Assessment 🎯This is a solid refactor that improves code quality and maintainability. The main concerns are:
Recommendation: Address the breaking changes and type safety concerns before merging. The architectural direction is excellent. |
|
@sentry review |
| aria-hidden={false} | ||
| aria-label={ariaLabel ?? 'slide out drawer'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1c6e041 to
e50ff26
Compare
| transition={theme.motion.framer.spring.moderate} | ||
| // Polymorphism is not very well supported by emotion and in this case conflicts with the | ||
| // HTMLMotionProps type on the onAnimationStart and onDrag members. | ||
| {...(props as any)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to find a better solution to this, but framer-motion types do not appear to play well with polymorphism
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the big problem with an object spread of as any is that it silences all type errors on the whole “object”, as it widens the complete type to any, and in case of a react component, the affected “object” is all props passed to it.
That means we will never get reported any type-errors of other props passed to MotionSurface here. For example, if I change overflow="auto" to overflow="foobar”, there is no error. This makes future refactorings really hard as we rely on the type checker.
The better workaround is to do {...(props as Record<string, unknown>)}. It silences the props you’re spreading, but not all other props.
I have a blogpost on this too: https://tkdodo.eu/blog/beware-the-leaking-any#when-any-is-leaking
- Add missing ariaLabel parameter to waitForDrawerToHide calls - Remove unused within imports from test files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| return { | ||
| position: {'2xs': 'fixed', sm: 'relative'}, | ||
| width: isResponsive(props.width) | ||
| ? (props.width ?? LEFT_SIDE_PANEL_WIDTH) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unreachable fallback code in left placement width logic
Low Severity
The fallback ?? LEFT_SIDE_PANEL_WIDTH in the responsive branch is unreachable. The isResponsive function returns true only when props.width is an object that is not null. Since the nullish coalescing operator (??) only triggers for null or undefined, and we've already confirmed props.width is a non-null object in this branch, LEFT_SIDE_PANEL_WIDTH can never be used. The right case correctly handles this by just using props.width directly without the redundant fallback.
The SlideOverPanel refactor removed the ariaLabel prop, causing tests
that queried drawers using getByLabelText() or getByRole('complementary')
to fail. Updated all affected tests to use data-test-id attributes instead.
Changes:
- Added test IDs to drawer components (schema hints, automations, monitors)
- Updated all failing test files to query by test ID instead of aria label
- Follows the pattern established in globalDrawer/index.spec.tsx
| export function isResponsive( | ||
| prop: unknown | ||
| ): prop is Partial<Record<BreakpointSize, any>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this function should do an isResponsive check, I guess the input needs to be tightened, as unknown an accept anything really. Otherwise, this is just an isObject check with no guarantees that we actually get a Record<BreakpointSize, any> back.
| transition={theme.motion.framer.spring.moderate} | ||
| // Polymorphism is not very well supported by emotion and in this case conflicts with the | ||
| // HTMLMotionProps type on the onAnimationStart and onDrag members. | ||
| {...(props as any)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the big problem with an object spread of as any is that it silences all type errors on the whole “object”, as it widens the complete type to any, and in case of a react component, the affected “object” is all props passed to it.
That means we will never get reported any type-errors of other props passed to MotionSurface here. For example, if I change overflow="auto" to overflow="foobar”, there is no error. This makes future refactorings really hard as we rely on the type checker.
The better workaround is to do {...(props as Record<string, unknown>)}. It silences the props you’re spreading, but not all other props.
I have a blogpost on this too: https://tkdodo.eu/blog/beware-the-leaking-any#when-any-is-leaking
| await screen.findByRole('textbox', {name: 'Search All Breadcrumbs'}); | ||
|
|
||
| // Get all complementary elements and find the one with breadcrumb content | ||
| const drawers = screen.getAllByRole('complementary'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we not label the complementary by some header content? Like for example, the BreadCrumbsDrawer has a header with a text:
| <Header>{t('Breadcrumbs')}</Header> |
so I’d expect to find this drawer with screen.getByRole('complementary', {name: 'Breadcrumbs'}); which also makes the most sense from a user perspective, as that's how they'd see it.
If we don’t to change the API towards <Drawer header={t('Breadcrumbs')}>, we could expose a Breadcrumbs.Header component that does the association
|
|
||
| return ( | ||
| <DrawerBody> | ||
| <DrawerBody data-test-id="create-automation-drawer"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is used to identify the drawer but why not just use the complementary role that we already have?
| const drawer = await screen.findByRole('complementary', { | ||
| name: 'Connect Monitors', | ||
| }); | ||
| const drawer = await screen.findByTestId('connect-monitors-drawer'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this definitely reads like a step backwards. I agree that we shouldn’t use aria-label, but using aria-labelledby pointing to the Header seems doable and worth it.
gggritso
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very cool! I only took a high-level glance (I can look closer if you'd like) and I like the API changes a lot. There's a UI regression in Dashboards I pointed to, though. If there's anything I can help with there, let me know!
| <SlideOverPanel | ||
| position="left" | ||
| data-test-id="widget-slideout" | ||
| transitionProps={animationTransitionSettings} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, this causes a UI regression. You can see for yourself:
- Open any dashboard
- Click "Add Widget"
- Click "Create Custom Widget"
You'll see two UI issues:
- The widget preview and the Widget Builder fly in at different speeds, which is jarring
- The Widget Builder animation is awkward because it over bounces and temporarily shows a black bar on its left side as it flies in and reveals the backdrop underneath
I ran out of time to fix this a few months ago, but I think the panel needs some extra margin to prevent the second UI issue. The first UI issue could be fixed by changing the animation of the preview, but it's also a bit jarring for two huge elements to fly in that fast. I don't have a strong opinion on that, though.
|
@TkDodo @gggritso thanks for the great feedback, it's a good test to try and refactor some of these components in our codebase and find where the rough edges are. Motion integration is a sharp edge, that I'm going to try and resolve in a better way, similar thing with the animation, maybe bounce animation is not the right one in this instance, or we need to make the other part of the UI use the same animation then. I appreciate both of your feedbacks though, it's a good test to see what breaks :) |
|
the drawers on the issue details pages appear on the wrong side of the page. not sure if the other feedback already mentioned that. |
Rewrite SlideOverPanel using design system primitives and remove unused bottom sizing.
It is a tad unfortunate that left size configs use relative positioning vs fixed, but I assume this has something to do with our sidebar (nevertheless, we need a more robust solution)
I have likewise removed the transition prop from this component as the escape hatch to the entire transition config requires proper encapsulation