diff --git a/packages/core/components/DraggableComponent/index.tsx b/packages/core/components/DraggableComponent/index.tsx index 879d5e9f92..d66d20e8ce 100644 --- a/packages/core/components/DraggableComponent/index.tsx +++ b/packages/core/components/DraggableComponent/index.tsx @@ -5,6 +5,7 @@ import getClassNameFactory from "../../lib/get-class-name-factory"; import { Copy, Trash } from "lucide-react"; import { useModifierHeld } from "../../lib/use-modifier-held"; import { ClipLoader } from "react-spinners"; +import { useAppContext } from "../Puck/context"; const getClassName = getClassNameFactory("DraggableComponent", styles); @@ -51,6 +52,7 @@ export const DraggableComponent = ({ indicativeHover?: boolean; style?: CSSProperties; }) => { + const { state } = useAppContext(); const isModifierHeld = useModifierHeld("Alt"); useEffect(onMount, []); @@ -92,7 +94,10 @@ export const DraggableComponent = ({ )} -
+
{label && (
{label}
diff --git a/packages/core/components/DropZone/context.tsx b/packages/core/components/DropZone/context.tsx index b3fcc620df..d4761b5580 100644 --- a/packages/core/components/DropZone/context.tsx +++ b/packages/core/components/DropZone/context.tsx @@ -41,7 +41,6 @@ export type DropZoneContext< pathData?: PathData; registerPath?: (selector: ItemSelector) => void; mode?: "edit" | "render"; - disableZoom?: boolean; } | null; export const dropZoneContext = createContext(null); diff --git a/packages/core/components/DropZone/index.tsx b/packages/core/components/DropZone/index.tsx index 5e054c08e8..3eff734d68 100644 --- a/packages/core/components/DropZone/index.tsx +++ b/packages/core/components/DropZone/index.tsx @@ -38,7 +38,6 @@ function DropZoneEdit({ zone, allow, disallow, style }: DropZoneProps) { registerZoneArea, areasWithZones, hoveringComponent, - disableZoom = false, } = ctx! || {}; let content = data.content || []; @@ -172,7 +171,6 @@ function DropZoneEdit({ zone, allow, disallow, style }: DropZoneProps) { isDisabled: !isEnabled, isAreaSelected, hasChildren: content.length > 0, - zoomEnabled: !disableZoom, })} > { + const { dispatch, state, overrides } = useAppContext(); + const { ui } = state; + const frameRef = useRef(null); + + const [rootHeight, setRootHeight] = useState(0); + const [autoZoom, setAutoZoom] = useState(1); + + const defaultRender = useMemo< + React.FunctionComponent<{ children?: ReactNode }> + >(() => { + const PuckDefault = ({ children }: { children?: ReactNode }) => ( + <>{children} + ); + + return PuckDefault; + }, []); + + const CustomPreview = useMemo( + () => overrides.preview || defaultRender, + [overrides] + ); + + // TODO deal with window resize + const resetAutoZoom = useCallback(() => { + if (frameRef.current) { + const frame = frameRef.current; + + const box = getBox(frame); + const frameWidth = box.contentBox.width; + const frameHeight = box.contentBox.height; + + const viewportHeight = + ui.viewport.height === "auto" ? frameHeight : ui.viewport.height; + + if (ui.viewport.width > frameWidth || viewportHeight > frameHeight) { + const widthZoom = Math.min(frameWidth / ui.viewport.width, 1); + const heightZoom = Math.min(frameHeight / viewportHeight, 1); + + let zoom = widthZoom; + + if (widthZoom < heightZoom) { + setRootHeight(viewportHeight / zoom); + } else { + setRootHeight(viewportHeight); + zoom = heightZoom; + } + + setAutoZoom(zoom); + + dispatch({ + type: "setUi", + ui: { + ...ui, + viewport: { + ...ui.viewport, + zoom, + }, + }, + }); + } else { + setAutoZoom(1); + + if (ui.viewport.zoom === autoZoom) { + setRootHeight(viewportHeight); + + dispatch({ + type: "setUi", + ui: { + ...ui, + viewport: { + ...ui.viewport, + zoom: 1, + }, + }, + }); + } else { + setRootHeight(viewportHeight / ui.viewport.zoom); + } + } + } + }, [frameRef, ui.leftSideBarVisible, ui, autoZoom]); + + // Auto zoom + useEffect(() => { + resetAutoZoom(); + }, [ + frameRef, + ui.leftSideBarVisible, + ui.rightSideBarVisible, + ui.viewport.width, + ]); + + return ( +
+ dispatch({ + type: "setUi", + ui: { itemSelector: null }, + recordHistory: true, + }) + } + > +
+ +
+
+
+ + + +
+
+
+ ); +}; diff --git a/packages/core/components/Puck/components/Preview/index.tsx b/packages/core/components/Puck/components/Preview/index.tsx index 50ee2cfceb..4b2f93b176 100644 --- a/packages/core/components/Puck/components/Preview/index.tsx +++ b/packages/core/components/Puck/components/Preview/index.tsx @@ -1,6 +1,6 @@ -import { DropZone, dropZoneContext } from "../../../DropZone"; +import { DropZone } from "../../../DropZone"; import { rootDroppableId } from "../../../../lib/root-droppable-id"; -import { useCallback, useContext, useRef } from "react"; +import { useCallback, useRef } from "react"; import { useAppContext } from "../../context"; import AutoFrame from "@measured/auto-frame-component"; import styles from "./styles.module.css"; @@ -22,8 +22,6 @@ export const Preview = ({ id = "puck-preview" }: { id?: string }) => { // DEPRECATED const rootProps = state.data.root.props || state.data.root; - const { disableZoom = false } = useContext(dropZoneContext) || {}; - const ref = useRef(null); return ( @@ -40,11 +38,9 @@ export const Preview = ({ id = "puck-preview" }: { id?: string }) => { data-rfd-iframe ref={ref} > -
- - - -
+ + +
); diff --git a/packages/core/components/Puck/context.tsx b/packages/core/components/Puck/context.tsx index f5303f52c4..7a967a45de 100644 --- a/packages/core/components/Puck/context.tsx +++ b/packages/core/components/Puck/context.tsx @@ -17,7 +17,8 @@ export const defaultAppState: AppState = { componentList: {}, isDragging: false, viewport: { - width: viewports.desktop, + width: viewports.large.width, + height: viewports.large.height, zoom: 1, }, }, diff --git a/packages/core/components/Puck/index.tsx b/packages/core/components/Puck/index.tsx index 6f71def8aa..4e6ac11b07 100644 --- a/packages/core/components/Puck/index.tsx +++ b/packages/core/components/Puck/index.tsx @@ -5,6 +5,7 @@ import { useEffect, useMemo, useReducer, + useRef, useState, } from "react"; import { DragDropContext, DragStart, DragUpdate } from "@measured/dnd"; @@ -42,7 +43,7 @@ import { Overrides } from "../../types/Overrides"; import { loadOverrides } from "../../lib/load-overrides"; import { usePuckHistory } from "../../lib/use-puck-history"; import { useHistoryStore } from "../../lib/use-history-store"; -import { ViewportControls } from "../ViewportControls"; +import { Canvas } from "./components/Canvas"; const getClassName = getClassNameFactory("Puck", styles); @@ -281,10 +282,6 @@ export function Puck< [loadedOverrides] ); - const CustomPreview = useMemo( - () => loadedOverrides.preview || defaultRender, - [loadedOverrides] - ); const CustomHeader = useMemo( () => loadedOverrides.header || defaultHeaderRender, [loadedOverrides] @@ -294,8 +291,6 @@ export function Puck< [loadedOverrides] ); - const disableZoom = children || loadedOverrides.puck ? true : false; - return (
@@ -397,7 +391,6 @@ export function Puck< leftSideBarVisible, menuOpen, rightSideBarVisible, - disableZoom, })} >
-
setItemSelector(null)} - > -
- -
-
-
- - - -
-
-
+
= { + // small: { width: 390, height: 844 }, // iPhone + small: { width: 360, height: "auto" }, + medium: { width: 768, height: "auto" }, + large: { width: 1440, height: "auto" }, }; const ViewportButton = ({ Icon, + height, title, width, }: { Icon: LucideIcon; + height: number | "auto"; title: string; - width: number | string; + width: number; }) => { const { state, setUi } = useAppContext(); const viewport = state.ui.viewport; @@ -28,7 +45,7 @@ const ViewportButton = ({ disabled={isActive} onClick={(e) => { e.stopPropagation(); - setUi({ viewport: { ...viewport, width } }); + setUi({ viewport: { ...viewport, height, width } }); }} > { +// Based on Chrome dev tools +const defaultZoomOptions = [ + { label: "25%", value: 0.25 }, + { label: "50%", value: 0.5 }, + { label: "75%", value: 0.75 }, + { label: "100%", value: 1 }, + { label: "125%", value: 1.25 }, + { label: "150%", value: 1.5 }, + { label: "200%", value: 2 }, +]; + +export const ViewportControls = ({ autoZoom }: { autoZoom: number }) => { const { state, setUi } = useAppContext(); const viewport = state.ui.viewport; + const defaultsContainAutoZoom = defaultZoomOptions.find( + (option) => option.value === autoZoom + ); + + const zoomOptions = useMemo( + () => + [ + ...defaultZoomOptions, + ...(defaultsContainAutoZoom + ? [] + : [ + { + value: autoZoom, + label: `${(autoZoom * 100).toFixed(0)}% (Auto)`, + }, + ]), + ].sort((a, b) => (a.value > b.value ? 1 : -1)), + [autoZoom] + ); + return ( <> +
+
+
+ { + e.stopPropagation(); + setUi({ + viewport: { + ...viewport, + zoom: zoomOptions[ + Math.max( + zoomOptions.findIndex( + (option) => option.value === viewport.zoom + ) - 1, + 0 + ) + ].value, + }, + }); + }} + > + + + { + e.stopPropagation(); + + setUi({ + viewport: { + ...viewport, + zoom: zoomOptions[ + Math.min( + zoomOptions.findIndex( + (option) => option.value === viewport.zoom + ) + 1, + zoomOptions.length - 1 + ) + ].value, + }, + }); + }} + > + + +
+
+
+ ); }; diff --git a/packages/core/package.json b/packages/core/package.json index 20020f252f..5d0a51722d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -46,6 +46,7 @@ "@types/jest": "^29.5.4", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", + "css-box-model": "^1.2.1", "eslint": "^7.32.0", "eslint-config-custom": "*", "jest": "^29.6.4", diff --git a/packages/core/types/Config.tsx b/packages/core/types/Config.tsx index 9245fd4ad2..badb4e42df 100644 --- a/packages/core/types/Config.tsx +++ b/packages/core/types/Config.tsx @@ -272,7 +272,8 @@ export type UiState = { >; isDragging: boolean; viewport: { - width: string | number; + width: number; + height: number | "auto"; zoom: number; }; };