diff --git a/app/designer/features/sidebar-left/index.tsx b/app/designer/features/sidebar-left/index.tsx index 6a574a943e..e403e34164 100644 --- a/app/designer/features/sidebar-left/index.tsx +++ b/app/designer/features/sidebar-left/index.tsx @@ -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"]; diff --git a/app/designer/features/sidebar-left/panels/components/component-thumb.tsx b/app/designer/features/sidebar-left/panels/components/component-thumb.tsx new file mode 100644 index 0000000000..b846a415e6 --- /dev/null +++ b/app/designer/features/sidebar-left/panels/components/component-thumb.tsx @@ -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; + +export const ComponentThumb = forwardRef< + ElementRef, + ComponentThumbProps +>(({ component, ...rest }, ref) => { + const { Icon, label } = primitives[component]; + return ( + + + {label} + + ); +}); + +ComponentThumb.displayName = "ComponentThumb"; diff --git a/app/designer/features/sidebar-left/panels/components.tsx b/app/designer/features/sidebar-left/panels/components/components.tsx similarity index 70% rename from app/designer/features/sidebar-left/panels/components.tsx rename to app/designer/features/sidebar-left/panels/components/components.tsx index 34615cbf38..a0c56a23c7 100644 --- a/app/designer/features/sidebar-left/panels/components.tsx +++ b/app/designer/features/sidebar-left/panels/components/components.tsx @@ -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; - label: string; }; -const Component = ({ - Icon, - component, - label, - onDragChange, - onClick, -}: ComponentProps) => { +const useDraggable = ({ component, onDragChange }: UseDraggableProps) => { const lastIsDragging = useRef(); - 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; +} & UseDraggableProps; + +const DraggableThumb = ({ + component, + onDragChange, + onClick, +}: DraggableThumbProps) => { + const dragRef = useDraggable({ component, onDragChange }); return ( - - - {label} - + ); }; -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 ).filter((component) => primitives[component].isInlineOnly === false); @@ -94,11 +82,9 @@ export const TabContent = ({ return ( {components.map((component: Instance["component"]) => ( - { onSetActiveTab("none"); publish<"insertInstance", { instance: Instance }>({ diff --git a/app/designer/features/sidebar-left/custom-drag-layer.tsx b/app/designer/features/sidebar-left/panels/components/custom-drag-layer.tsx similarity index 57% rename from app/designer/features/sidebar-left/custom-drag-layer.tsx rename to app/designer/features/sidebar-left/panels/components/custom-drag-layer.tsx index 0987689c0a..d84676d5c0 100644 --- a/app/designer/features/sidebar-left/custom-drag-layer.tsx +++ b/app/designer/features/sidebar-left/panels/components/custom-drag-layer.tsx @@ -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(
, document.body); + return createPortal( +
+ +
, + document.body + ); }; diff --git a/app/designer/features/sidebar-left/panels/components/index.ts b/app/designer/features/sidebar-left/panels/components/index.ts new file mode 100644 index 0000000000..40b494c5f8 --- /dev/null +++ b/app/designer/features/sidebar-left/panels/components/index.ts @@ -0,0 +1 @@ +export * from "./components"; diff --git a/app/shared/icons/index.ts b/app/shared/icons/index.ts index 4e98751c66..b6af274862 100644 --- a/app/shared/icons/index.ts +++ b/app/shared/icons/index.ts @@ -6,3 +6,4 @@ export * from "./undo"; export * from "./redo"; export * from "./devices"; export * from "./tablet"; +export * from "./types"; diff --git a/app/shared/icons/types.ts b/app/shared/icons/types.ts index db754f22cf..2268f89f79 100644 --- a/app/shared/icons/types.ts +++ b/app/shared/icons/types.ts @@ -1,6 +1,14 @@ -import type { SVGAttributes } from "react"; +import type { + SVGAttributes, + RefAttributes, + ForwardRefExoticComponent, +} from "react"; export interface IconProps extends SVGAttributes { children?: never; color?: string; } + +export type IconComponent = ForwardRefExoticComponent< + IconProps & RefAttributes +>;