mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-09 14:03:33 +09:00
Merge pull request #55 from webstudio-is/30-remove-bouncing-back-animation-when-dropping-a-new-instance
Fixes bouncing back animation from native thumbnail on drop
This commit is contained in:
7 files changed
+113
-63
No files matched your search
@@ -1,4 +1 @@
|
||||
export { SidebarLeft } from "./sidebar-left";
|
||||
export { CustomDragLayer } from "./custom-drag-layer";
|
||||
import type { CustomDragLayerProps } from "./custom-drag-layer";
|
||||
export type OnDrag = CustomDragLayerProps["onDrag"];
|
||||
@@ -0,0 +1,50 @@
|
||||
import { type Instance } from "@webstudio-is/sdk";
|
||||
import { forwardRef, type ElementRef, type ComponentProps } from "react";
|
||||
import { primitives } from "~/shared/component";
|
||||
import { Flex, Text, styled } from "~/shared/design-system";
|
||||
|
||||
const Thumb = styled(Flex, {
|
||||
px: 5,
|
||||
width: 75,
|
||||
height: 75,
|
||||
border: "1px solid $slate6",
|
||||
userSelect: "none",
|
||||
color: "$hiContrast",
|
||||
cursor: "grab",
|
||||
"&:hover": {
|
||||
background: "$slate3",
|
||||
},
|
||||
variants: {
|
||||
state: {
|
||||
dragging: {
|
||||
background: "$slate3",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
type ComponentThumbProps = {
|
||||
component: Instance["component"];
|
||||
} & ComponentProps<typeof Thumb>;
|
||||
|
||||
export const ComponentThumb = forwardRef<
|
||||
ElementRef<typeof Thumb>,
|
||||
ComponentThumbProps
|
||||
>(({ component, ...rest }, ref) => {
|
||||
const { Icon, label } = primitives[component];
|
||||
return (
|
||||
<Thumb
|
||||
direction="column"
|
||||
align="center"
|
||||
justify="center"
|
||||
gap="3"
|
||||
ref={ref}
|
||||
{...rest}
|
||||
>
|
||||
<Icon width={30} height={30} />
|
||||
<Text size="1">{label}</Text>
|
||||
</Thumb>
|
||||
);
|
||||
});
|
||||
|
||||
ComponentThumb.displayName = "ComponentThumb";
|
||||
+30
-44
@@ -1,30 +1,23 @@
|
||||
import { useEffect, useRef, type MouseEventHandler, useCallback } from "react";
|
||||
import { useDrag } from "react-dnd";
|
||||
import { getEmptyImage } from "react-dnd-html5-backend";
|
||||
import { type Instance, type Publish } from "@webstudio-is/sdk";
|
||||
import { Flex, Text } from "~/shared/design-system";
|
||||
import { Flex } from "~/shared/design-system";
|
||||
import { PlusIcon } from "~/shared/icons";
|
||||
import { primitives, type DragData } from "~/shared/component";
|
||||
import { createInstance } from "~/shared/tree-utils";
|
||||
import { CustomDragLayer } from "../custom-drag-layer";
|
||||
import type { TabName } from "../types";
|
||||
import type { TabName } from "../../types";
|
||||
import { CustomDragLayer } from "./custom-drag-layer";
|
||||
import { ComponentThumb } from "./component-thumb";
|
||||
|
||||
type ComponentProps = {
|
||||
Icon: typeof PlusIcon;
|
||||
type UseDraggableProps = {
|
||||
component: Instance["component"];
|
||||
onDragChange: (isDragging: boolean) => void;
|
||||
onClick: MouseEventHandler<HTMLDivElement>;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const Component = ({
|
||||
Icon,
|
||||
component,
|
||||
label,
|
||||
onDragChange,
|
||||
onClick,
|
||||
}: ComponentProps) => {
|
||||
const useDraggable = ({ component, onDragChange }: UseDraggableProps) => {
|
||||
const lastIsDragging = useRef<boolean | void>();
|
||||
const [{ isDragging }, dragRef] = useDrag(
|
||||
const [{ isDragging }, dragRef, preview] = useDrag(
|
||||
() => ({
|
||||
type: component,
|
||||
collect: (monitor) => ({
|
||||
@@ -39,35 +32,30 @@ const Component = ({
|
||||
lastIsDragging.current = isDragging;
|
||||
}, [isDragging, onDragChange]);
|
||||
|
||||
useEffect(() => {
|
||||
preview(getEmptyImage(), { captureDraggingState: true });
|
||||
}, [preview]);
|
||||
|
||||
return dragRef;
|
||||
};
|
||||
|
||||
type DraggableThumbProps = {
|
||||
onClick: MouseEventHandler<HTMLDivElement>;
|
||||
} & UseDraggableProps;
|
||||
|
||||
const DraggableThumb = ({
|
||||
component,
|
||||
onDragChange,
|
||||
onClick,
|
||||
}: DraggableThumbProps) => {
|
||||
const dragRef = useDraggable({ component, onDragChange });
|
||||
return (
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center"
|
||||
justify="center"
|
||||
gap="3"
|
||||
css={{
|
||||
px: 5,
|
||||
width: 75,
|
||||
height: 75,
|
||||
border: "1px solid $slate6",
|
||||
userSelect: "none",
|
||||
color: "$hiContrast",
|
||||
cursor: "grab",
|
||||
"&:hover": {
|
||||
background: "$slate3",
|
||||
},
|
||||
}}
|
||||
ref={dragRef}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Icon width={30} height={30} />
|
||||
<Text size="1">{label}</Text>
|
||||
</Flex>
|
||||
<ComponentThumb component={component} ref={dragRef} onClick={onClick} />
|
||||
);
|
||||
};
|
||||
|
||||
type ComponentsProps = {
|
||||
onDragChange: ComponentProps["onDragChange"];
|
||||
type TabContentProps = {
|
||||
onDragChange: UseDraggableProps["onDragChange"];
|
||||
onSetActiveTab: (tabName: TabName) => void;
|
||||
publish: Publish;
|
||||
};
|
||||
@@ -76,7 +64,7 @@ export const TabContent = ({
|
||||
onDragChange,
|
||||
publish,
|
||||
onSetActiveTab,
|
||||
}: ComponentsProps) => {
|
||||
}: TabContentProps) => {
|
||||
const components = (
|
||||
Object.keys(primitives) as Array<Instance["component"]>
|
||||
).filter((component) => primitives[component].isInlineOnly === false);
|
||||
@@ -94,11 +82,9 @@ export const TabContent = ({
|
||||
return (
|
||||
<Flex gap="1" wrap="wrap" css={{ padding: "$1" }}>
|
||||
{components.map((component: Instance["component"]) => (
|
||||
<Component
|
||||
<DraggableThumb
|
||||
key={component}
|
||||
Icon={primitives[component].Icon}
|
||||
component={component}
|
||||
label={primitives[component].label}
|
||||
onClick={() => {
|
||||
onSetActiveTab("none");
|
||||
publish<"insertInstance", { instance: Instance }>({
|
||||
+22
-15
@@ -3,6 +3,7 @@ import { createPortal } from "react-dom";
|
||||
import { useDragLayer, type XYCoord } from "react-dnd";
|
||||
import { type Instance } from "@webstudio-is/sdk";
|
||||
import { useCanvasRect, useZoom } from "~/designer/shared/nano-values";
|
||||
import { ComponentThumb } from "./component-thumb";
|
||||
|
||||
const layerStyles = {
|
||||
position: "absolute",
|
||||
@@ -22,24 +23,19 @@ export type CustomDragLayerProps = {
|
||||
};
|
||||
|
||||
export const CustomDragLayer = ({ onDrag }: CustomDragLayerProps) => {
|
||||
const {
|
||||
itemType: component,
|
||||
isDragging,
|
||||
initialOffset,
|
||||
clientOffset,
|
||||
} = useDragLayer((monitor) => ({
|
||||
itemType: monitor.getItemType() as Instance["component"],
|
||||
initialOffset: monitor.getInitialSourceClientOffset(),
|
||||
isDragging: monitor.isDragging(),
|
||||
clientOffset: monitor.getClientOffset(),
|
||||
}));
|
||||
const { component, isDragging, clientOffset, sourceClientOffset } =
|
||||
useDragLayer((monitor) => ({
|
||||
component: monitor.getItemType() as Instance["component"],
|
||||
isDragging: monitor.isDragging(),
|
||||
clientOffset: monitor.getClientOffset(),
|
||||
sourceClientOffset: monitor.getSourceClientOffset(),
|
||||
}));
|
||||
const [canvasRect] = useCanvasRect();
|
||||
const [zoom] = useZoom();
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
clientOffset === null ||
|
||||
initialOffset === null ||
|
||||
component === null ||
|
||||
canvasRect === undefined
|
||||
) {
|
||||
@@ -56,9 +52,20 @@ export const CustomDragLayer = ({ onDrag }: CustomDragLayerProps) => {
|
||||
currentOffset,
|
||||
component,
|
||||
});
|
||||
}, [clientOffset, initialOffset, component, onDrag, zoom, canvasRect]);
|
||||
}, [clientOffset, component, onDrag, zoom, canvasRect]);
|
||||
|
||||
if (isDragging === false) return null;
|
||||
if (isDragging === false || sourceClientOffset === null) return null;
|
||||
|
||||
return createPortal(<div style={layerStyles} />, document.body);
|
||||
return createPortal(
|
||||
<div style={layerStyles}>
|
||||
<ComponentThumb
|
||||
component={component}
|
||||
style={{
|
||||
transform: `translate3d(${sourceClientOffset.x}px, ${sourceClientOffset.y}px, 0)`,
|
||||
}}
|
||||
state="dragging"
|
||||
/>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./components";
|
||||
@@ -6,3 +6,4 @@ export * from "./undo";
|
||||
export * from "./redo";
|
||||
export * from "./devices";
|
||||
export * from "./tablet";
|
||||
export * from "./types";
|
||||
@@ -1,6 +1,14 @@
|
||||
import type { SVGAttributes } from "react";
|
||||
import type {
|
||||
SVGAttributes,
|
||||
RefAttributes,
|
||||
ForwardRefExoticComponent,
|
||||
} from "react";
|
||||
|
||||
export interface IconProps extends SVGAttributes<SVGElement> {
|
||||
children?: never;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export type IconComponent = ForwardRefExoticComponent<
|
||||
IconProps & RefAttributes<SVGSVGElement>
|
||||
>;
|
||||
Reference in new issue
Block a user