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(widget-builder): Make widget builder preview draggable #82038

Closed
Closed
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
6 changes: 3 additions & 3 deletions static/app/components/slideOverPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ const _SlideOverPanel = styled(motion.div, {
}>`
position: fixed;

top: ${space(2)};
right: 0;
top: ${p => (p.slidePosition === 'left' ? '54px' : space(2))};
Copy link
Member

Choose a reason for hiding this comment

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

Is there some kind of constant we could use in place of the 54px? Just trying to see if there's something a bit more semantic

right: ${p => (p.slidePosition === 'left' ? space(2) : 0)};
bottom: ${space(2)};
left: ${space(2)};
left: ${p => (p.slidePosition === 'left' ? 0 : space(2))};

overflow: auto;
pointer-events: auto;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Fragment, useEffect} from 'react';
import {css} from '@emotion/react';
import {Fragment, useEffect, useState} from 'react';
import {DndContext, type Translate, useDraggable} from '@dnd-kit/core';
import {css, useTheme} from '@emotion/react';
import styled from '@emotion/styled';
import {AnimatePresence, motion} from 'framer-motion';

Expand Down Expand Up @@ -36,6 +37,8 @@ type WidgetBuilderV2Props = {
onSave: ({index, widget}: {index: number; widget: Widget}) => void;
};

const WIDGET_PREVIEW_DRAG_ID = 'widget-preview-draggable';

function WidgetBuilderV2({
isOpen,
onClose,
Expand All @@ -47,6 +50,14 @@ function WidgetBuilderV2({
const organization = useOrganization();
const {selection} = usePageFilters();

const [{translate}, setTranslate] = useState<{
initialTranslate: Translate;
translate: Translate;
}>({
initialTranslate: {x: 0, y: 0},
translate: {x: 0, y: 0},
});

useEffect(() => {
if (escapeKeyPressed) {
if (isOpen) {
Expand All @@ -55,6 +66,25 @@ function WidgetBuilderV2({
}
}, [escapeKeyPressed, isOpen, onClose]);

const handleDragEnd = () => {
setTranslate(({translate: newTranslate}) => {
return {
translate: newTranslate,
initialTranslate: newTranslate,
};
});
};

const handleDragMove = ({delta}) => {
setTranslate(({initialTranslate}) => ({
initialTranslate,
translate: {
x: initialTranslate.x + delta.x,
y: initialTranslate.y + delta.y,
},
}));
};

return (
<Fragment>
{isOpen && <Backdrop style={{opacity: 0.5, pointerEvents: 'auto'}} />}
Expand All @@ -70,13 +100,22 @@ function WidgetBuilderV2({
<WidgetBuilderContainer>
<WidgetBuilderSlideout
isOpen={isOpen}
onClose={onClose}
onClose={() => {
onClose();
setTranslate({
initialTranslate: {x: 0, y: 0},
translate: {x: 0, y: 0},
});
}}
onSave={onSave}
/>
<WidgetPreviewContainer
dashboardFilters={dashboardFilters}
dashboard={dashboard}
/>
<DndContext onDragEnd={handleDragEnd} onDragMove={handleDragMove}>
<WidgetPreviewContainer
dashboardFilters={dashboardFilters}
dashboard={dashboard}
translate={translate}
/>
</DndContext>
</WidgetBuilderContainer>
</ContainerWithoutSidebar>
</SpanTagsProvider>
Expand All @@ -93,13 +132,25 @@ export default WidgetBuilderV2;
function WidgetPreviewContainer({
dashboardFilters,
dashboard,
translate,
}: {
dashboard: DashboardDetails;
dashboardFilters: DashboardFilters;
translate: Translate;
}) {
const {state} = useWidgetBuilderContext();
const organization = useOrganization();
const location = useLocation();
const theme = useTheme();

const isDragEnabled =
window.innerWidth < parseInt(theme.breakpoints.small.replace('px', ''), 10);

const {attributes, listeners, setNodeRef, isDragging} = useDraggable({
id: WIDGET_PREVIEW_DRAG_ID,
disabled: !isDragEnabled,
// May need to add 'handle' prop if we want to drag the preview by a specific area
});

return (
<DashboardsMEPProvider>
Expand All @@ -115,22 +166,35 @@ function WidgetPreviewContainer({
location={location}
forceTransactions={metricsDataSide.forceTransactionsOnly}
>
<SampleWidgetCard
initial={{opacity: 0, x: '50%', y: 0}}
animate={{opacity: 1, x: 0, y: 0}}
exit={{opacity: 0, x: '50%', y: 0}}
transition={{
type: 'spring',
stiffness: 500,
damping: 50,
<DraggableWidgetContainer
ref={setNodeRef}
id={WIDGET_PREVIEW_DRAG_ID}
style={{
transform: isDragEnabled
Copy link
Member

Choose a reason for hiding this comment

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

We might be able to eek out some small perf gains by telling the browser these values will change.
https://developer.mozilla.org/en-US/docs/Learn/Performance/CSS#optimizing_element_changes_with_will-change

i.e. you can probably add willChange: 'opacity, transform'

? `translate3d(${translate?.x ?? 0}px, ${translate?.y ?? 0}px, 0)`
: undefined,
opacity: isDragging ? 0.5 : 1,
}}
isTable={state.displayType === DisplayType.TABLE}
{...attributes}
{...listeners}
>
<WidgetPreview
dashboardFilters={dashboardFilters}
dashboard={dashboard}
/>
</SampleWidgetCard>
<SampleWidgetCard
initial={{opacity: 0, x: '50%', y: 0}}
animate={{opacity: 1, x: 0, y: 0}}
exit={{opacity: 0, x: '50%', y: 0}}
transition={{
type: 'spring',
stiffness: 500,
damping: 50,
}}
isTable={state.displayType === DisplayType.TABLE}
>
<WidgetPreview
dashboardFilters={dashboardFilters}
dashboard={dashboard}
/>
</SampleWidgetCard>
</DraggableWidgetContainer>
</MEPSettingProvider>
)}
</MetricsDataSwitcher>
Expand Down Expand Up @@ -164,10 +228,31 @@ const SampleWidgetCard = styled(motion.div)<{isTable: boolean}>`
border: 2px dashed ${p => p.theme.border};
border-radius: ${p => p.theme.borderRadius};
background-color: ${p => p.theme.background};
z-index: ${p => p.theme.zIndex.modal};
position: relative;
`;

const DraggableWidgetContainer = styled(`div`)`
align-content: center;
z-index: ${p => p.theme.zIndex.modal};
position: relative;
margin: auto;

touch-action: none; /* Prevents touch scrolling while dragging */
cursor: grab;

&:active {
cursor: grabbing;
}

@media (min-width: ${p => p.theme.breakpoints.small}) {
transform: none;
cursor: auto;

&:active {
cursor: auto;
}
}
`;

const ContainerWithoutSidebar = styled('div')`
Expand Down
Loading