Skip to content

Conversation

RestartDK
Copy link
Contributor

@RestartDK RestartDK commented Oct 21, 2025

Description

This PR allows the user to:

  1. See guides for width and height when resizing on all sides
  2. "Snapping" effect when resizing to the same size on either width or height
  3. Includes resizing guidlines when resizing width and height simultaneously

Related Issues

closes #2909

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

No tests were made for this however I resized the frame using 2 - 4 frames at the same time, seeing if anything would conflict.

Screenshots (if applicable)

onlook-1.mp4

Additional Notes

For this feature I reused the current designs for the lines that are the same as the alignment ones. Please let me know if this needs to be changed to be more in line with the initial screenshot from #2909.


Important

Adds width and height resizing guides with snapping functionality to frames, enhancing user experience with visual feedback and alignment in resize-handles.tsx and snap/index.ts.

  • Behavior:
    • Adds width and height guides when resizing frames in resize-handles.tsx.
    • Implements snapping effect when resizing to match width or height of other frames.
    • Supports simultaneous width and height resizing with snapping.
  • SnapManager:
    • Adds calculateDimensionSnapTarget() and detectDimensionAlignment() to handle snapping logic in snap/index.ts.
    • Uses createSnapBounds() and getSnapFrames() to determine snapping candidates.
  • Misc:
    • Introduces isResizeActive to handle deadzone logic in resize-handles.tsx.
    • Updates snapping configuration checks in resize-handles.tsx.

This description was created by Ellipsis for ac587cb. You can customize this summary. It will automatically update as commits are pushed.

Summary by CodeRabbit

  • New Features
    • Dimension snapping during frame resize now snaps width/height independently, shows alignment guidelines, and respects modifier keys to bypass snapping.
  • Bug Fixes
    • Added a small movement deadzone to prevent accidental resizes.
    • Snap guidelines now appear only when relevant, are clamped to minimum sizes, refresh correctly during resize, and are hidden on resize stop.

@vercel
Copy link

vercel bot commented Oct 21, 2025

@RestartDK is attempting to deploy a commit to the Onlook Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

The PR adds a 5px deadzone before activating resize, computes per-mouse deltas, integrates dimension-based snapping into the resize flow (width/height), displays/hides snap lines appropriately, and ensures snap lines are hidden on resize stop.

Changes

Cohort / File(s) Summary
Resize handler deadzone & snapping integration
apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx
Adds a 5px movement deadzone (isResizeActive), tracks dx/dy per tick, uses the snap module to compute dimension snap targets when snapping is enabled and modifiers are not pressed, applies snapped or direct dimension updates (clamped to minimums), shows/hides snap lines and refreshes overlays, and hides snap lines on stopResize.
Dimension snap calculations & alignment helper
apps/web/client/src/components/store/editor/snap/index.ts
Adds private isAlignedWithFrame(currentPosition: RectPosition, otherFrame: SnapFrame) and calculateDimensionSnapTarget(frameId: string, dimension: RectDimension, position: RectPosition, resizingDimensions?: { width: boolean; height: boolean }) to compute width/height snap targets and corresponding snap lines (or return null). Uses existing helpers (createSnapBounds/createSnapLine) and config (threshold, enabled, showGuidelines).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ResizeHandler as resize-handles.tsx
    participant SnapModule as snap/index.ts
    participant Canvas

    User->>ResizeHandler: pointerdown -> start resize
    ResizeHandler->>ResizeHandler: track movement (dx,dy)
    alt movement > 5px deadzone
        ResizeHandler->>ResizeHandler: isResizeActive = true
        alt snapping enabled && no Ctrl/Meta
            ResizeHandler->>SnapModule: calculateDimensionSnapTarget(frameId, dim, pos, resizingDimensions)
            SnapModule-->>ResizeHandler: {dimension?, snapLines?} or null
            alt returned snap target
                ResizeHandler->>Canvas: apply snapped dimension (clamped)
                ResizeHandler->>Canvas: show snapLines / refresh overlays
            else no snap
                ResizeHandler->>Canvas: apply direct dimension update
                ResizeHandler->>Canvas: hide snapLines
            end
        else
            ResizeHandler->>Canvas: apply direct dimension update
            ResizeHandler->>Canvas: hide snapLines
        end
    else within deadzone
        ResizeHandler->>ResizeHandler: wait (do not resize)
    end
    User->>ResizeHandler: pointerup -> stopResize
    ResizeHandler->>Canvas: hide snapLines, finalize
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Kitenite

Poem

🐰
Five pixels wait before the stretch begins,
I hop and measure widths and heights like fins,
When edges match I draw a guiding line,
Snap, clamp, and finish — tidy and fine!

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Feat/width height guides" is related to the main changes in the pull request, which add width and height dimension guides and snapping behavior to the frame resizing functionality. The title accurately captures the core feature being added, though it uses formatting more typical of branch names rather than a conventional pull request title. The hyphenated "width height" phrasing and "Feat/" prefix suggest this was derived from the branch name; a more polished title might be "Add width and height snapping guides for frame resizing" but the current title is sufficiently clear and specific about the primary change.
Linked Issues Check ✅ Passed The changes directly implement all three coding requirements from issue #2909: the snap/index.ts additions provide the calculateDimensionSnapTarget() and isAlignedWithFrame() methods to detect when frames share width or height dimensions and compute snapping targets; the resize-handles.tsx modifications integrate this snapping logic into the resize flow, conditionally applying snapped dimensions and managing visual snap line display; and the snap line visibility handling ensures guides display along appropriate edges when dimension matches occur. The implementation appears complete and addresses all stated feature requirements.
Out of Scope Changes Check ✅ Passed The changes are focused on implementing the width and height guides feature outlined in issue #2909. Modifications to resize-handles.tsx include deadzone handling (isResizeActive), snapping integration, and snap line management, while snap/index.ts additions provide the underlying snapping calculation logic (calculateDimensionSnapTarget and isAlignedWithFrame). These changes are all directly scoped to delivering the dimension snapping and guide visualization requirements and do not introduce unrelated functionality or modifications outside the feature's scope.
Description Check ✅ Passed The pull request description follows the required template structure and includes all major sections: a clear description of the feature with numbered bullet points, a linked issue reference using GitHub keywords (closes #2909), the type of change marked as "New feature," testing information (though stating no automated tests were created), a screenshot for visual context, and additional notes about design reuse. The description clearly explains what users can now do and notes any outstanding design considerations, which provides good context for reviewers.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.


// Check deadzone - only start resizing after 5px movement
if (!isResizeActive) {
if (dx * dx + dy * dy <= 25) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider extracting the deadzone threshold (currently hardcoded as 25) into a named constant or configuration for easier future tuning.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
apps/web/client/src/components/store/editor/snap/index.ts (1)

255-278: Anchor dimension guides to the matched frame’s edge for consistency with alignment lines

Currently the guide position uses the drag frame’s edges; align to the matched frame’s edges for consistent visuals and to better reflect “where the match occurs.”

-                const widthLine = this.createSnapLine(
+                const widthLine = this.createSnapLine(
                   SnapLineType.EDGE_RIGHT,
                   'vertical',
-                  dragBounds.right,
+                  otherFrame.bounds.right,
                   otherFrame,
                   dragBounds,
                 );
...
-                const heightLine = this.createSnapLine(
+                const heightLine = this.createSnapLine(
                   SnapLineType.EDGE_BOTTOM,
                   'horizontal',
-                  dragBounds.bottom,
+                  otherFrame.bounds.bottom,
                   otherFrame,
                   dragBounds,
                 );
apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx (2)

6-6: Use native MouseEvent types for window listeners; drop unsafe casts

The handlers are registered on window, but typed as React.MouseEvent and then force‑cast to EventListener. Use ReactMouseEvent only for the initial onMouseDown; use global MouseEvent for window events.

-import type { MouseEvent } from 'react';
+import type { MouseEvent as ReactMouseEvent } from 'react';
...
-    const startResize = (e: MouseEvent, types: HandleType[]) => {
+    const startResize = (e: ReactMouseEvent, types: HandleType[]) => {
...
-        const resize = (e: MouseEvent) => {
+        const resize = (e: globalThis.MouseEvent) => {
...
-        const stopResize = (e: MouseEvent) => {
+        const stopResize = (e: globalThis.MouseEvent) => {
...
-            window.removeEventListener('mousemove', resize as unknown as EventListener);
-            window.removeEventListener('mouseup', stopResize as unknown as EventListener);
+            window.removeEventListener('mousemove', resize);
+            window.removeEventListener('mouseup', stopResize);
...
-        window.addEventListener('mousemove', resize as unknown as EventListener);
-        window.addEventListener('mouseup', stopResize as unknown as EventListener);
+        window.addEventListener('mousemove', resize);
+        window.addEventListener('mouseup', stopResize);

Also applies to: 20-21, 32-32, 115-122, 124-125


100-109: Consolidate hideSnapLines calls

hideSnapLines() is called in three branches; collapse to one call to reduce churn.

-                } else {
-                    editorEngine.snap.hideSnapLines();
-                }
-            } else {
-                editorEngine.snap.hideSnapLines();
-            }
-
-            // No snapping or snapping disabled 
-            editorEngine.snap.hideSnapLines();
+                } else {
+                    // fall through to non-snap path below
+                }
+            }
+            // No snapping or snapping disabled
+            editorEngine.snap.hideSnapLines();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59df63f and ac587cb.

📒 Files selected for processing (2)
  • apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx (2 hunks)
  • apps/web/client/src/components/store/editor/snap/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/components/store/editor/snap/index.ts
  • apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • apps/web/client/src/components/store/editor/snap/index.ts
  • apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • apps/web/client/src/components/store/editor/snap/index.ts
  • apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx
apps/web/client/src/app/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env instead

Avoid hardcoded user-facing text; use next-intl messages/hooks

Files:

  • apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx
apps/web/client/src/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'

Files:

  • apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx
🧬 Code graph analysis (1)
apps/web/client/src/components/store/editor/snap/index.ts (1)
apps/web/client/src/components/store/editor/snap/types.ts (1)
  • SnapLine (20-28)
🔇 Additional comments (4)
apps/web/client/src/components/store/editor/snap/index.ts (1)

284-375: Dimension snapping selection logic looks solid

Closest-by-dimension within threshold, optional per-axis gating, and line generation are coherent. No blocking issues from this block.

If multiple frames share identical closest differences, current tie-breaking is by iteration order. Confirm that’s acceptable or sort by a stable key to avoid UI flicker.

apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx (3)

33-43: Deadzone uses screen pixels; confirm UX intent across zoom levels

The 5px threshold is in device pixels (pre‑scale). If the desired feel is canvas‑space consistency, consider dividing by scale.


13-15: Observer component needs a client boundary

Per guidelines, observer components must be client components. Ensure a parent file in this feature sets 'use client', or add it here if this is the entry boundary.


119-119: Good: cleanup hides guides on stop

Snap lines are cleared on mouseup; this prevents stale guides.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx (3)

33-43: Extract the deadzone threshold to a named constant/config.

Hardcoded 5px makes tuning harder; hoist to a constant and reuse.

+const DEADZONE_PX = 5;
@@
-            // Check deadzone - only start resizing after 5px movement
+            // Check deadzone - only start resizing after DEADZONE_PX movement
             if (!isResizeActive) {
-                if (dx * dx + dy * dy <= 25) {
+                if (dx * dx + dy * dy <= DEADZONE_PX * DEADZONE_PX) {
                     return; // Still within deadzone
                 }
                 isResizeActive = true;
             }

80-116: Remove redundant hideSnapLines() call when no snap target.

You hide lines inside the else and again in the fallback path.

                 if (dimensionSnapTarget) {
                     // Apply snapped dimensions
@@
                     editorEngine.overlay.undebouncedRefresh();
                     return;
-                } else {
-                    editorEngine.snap.hideSnapLines();
                 }
             } 
@@
-            // No snapping or snapping disabled 
+            // No snapping or snapping disabled
             editorEngine.snap.hideSnapLines();
             editorEngine.frames.updateAndSaveToStorage(frame.id, {
                 dimension: { width: Math.round(newWidth), height: Math.round(newHeight) },
             });

92-108: Nice: snapped path clamps to min size and manages guides accordingly.

This resolves the prior min-size bypass.

🧹 Nitpick comments (3)
apps/web/client/src/components/store/editor/snap/index.ts (2)

25-36: Broaden alignment gating to axis overlap (not just top/left equality).

Current gating treats frames as “aligned” only when top/left edges are within threshold, which may miss legitimate width/height matches when frames are offset but overlap along an axis. Prefer overlap-based gating per axis.

Suggested change: pass current dimension and compute axis overlap.

-    private isAlignedWithFrame(currentPosition: RectPosition, otherFrame: SnapFrame): boolean {
-        // Check if frames are horizontally aligned for width snapping
-        const yDifference = Math.abs(currentPosition.y - otherFrame.bounds.top);
-        const isHorizontallyAligned = yDifference <= this.config.threshold;
-        
-        // Check if frames are vertically aligned for height snapping  
-        const xDifference = Math.abs(currentPosition.x - otherFrame.bounds.left);
-        const isVerticallyAligned = xDifference <= this.config.threshold;
-        
-        // Frame is aligned if it's either horizontally OR vertically aligned
-        return isHorizontallyAligned || isVerticallyAligned;
-    }
+    private isAlignedWithFrame(
+        currentPosition: RectPosition,
+        currentDimension: RectDimension,
+        otherFrame: SnapFrame,
+    ): boolean {
+        const currTop = currentPosition.y;
+        const currBottom = currentPosition.y + currentDimension.height;
+        const currLeft = currentPosition.x;
+        const currRight = currentPosition.x + currentDimension.width;
+
+        // Axis overlap with tolerance
+        const verticalOverlap =
+            currBottom >= otherFrame.bounds.top - this.config.threshold &&
+            currTop <= otherFrame.bounds.bottom + this.config.threshold;
+        const horizontalOverlap =
+            currRight >= otherFrame.bounds.left - this.config.threshold &&
+            currLeft <= otherFrame.bounds.right + this.config.threshold;
+
+        // Overlap on either axis is sufficient (width or height guides)
+        return verticalOverlap || horizontalOverlap;
+    }
@@
-        const alignedFrames = allFrames.filter(frame => this.isAlignedWithFrame(position, frame));
+        const alignedFrames = allFrames.filter(frame => this.isAlignedWithFrame(position, dimension, frame));

Also applies to: 266-271


228-236: Use stable snap line IDs to reduce overlay churn/flicker.

Date.now() changes every move; stable IDs improve keyed renders.

-        return {
-            id: `${type}-${otherFrame.id}-${Date.now()}`,
+        return {
+            id: `${type}-${otherFrame.id}-${orientation}-${Math.round(position)}`,
             type,
             orientation,
             position,
             start,
             end,
             frameIds: [otherFrame.id],
         };
apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx (1)

20-21: Type handlers for DOM events; avoid casting React MouseEvent to EventListener.

Use ReactMouseEvent only for onMouseDown; use global MouseEvent for window listeners to remove unsafe casts.

-import type { MouseEvent } from 'react';
+import type { MouseEvent as ReactMouseEvent } from 'react';
@@
-    const startResize = (e: MouseEvent, types: HandleType[]) => {
+    const startResize = (e: ReactMouseEvent, types: HandleType[]) => {
@@
-        const resize = (e: MouseEvent) => {
+        const resize = (e: globalThis.MouseEvent) => {
@@
-        const stopResize = (e: MouseEvent) => {
+        const stopResize = (e: globalThis.MouseEvent) => {
@@
-            window.removeEventListener('mousemove', resize as unknown as EventListener);
-            window.removeEventListener('mouseup', stopResize as unknown as EventListener);
+            window.removeEventListener('mousemove', resize);
+            window.removeEventListener('mouseup', stopResize);
@@
-        window.addEventListener('mousemove', resize as unknown as EventListener);
-        window.addEventListener('mouseup', stopResize as unknown as EventListener);
+        window.addEventListener('mousemove', resize);
+        window.addEventListener('mouseup', stopResize);

Also applies to: 32-43, 122-129, 131-133

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac587cb and 34a97ba.

📒 Files selected for processing (2)
  • apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx (2 hunks)
  • apps/web/client/src/components/store/editor/snap/index.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/client/src/app/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env instead

Avoid hardcoded user-facing text; use next-intl messages/hooks

Files:

  • apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx
  • apps/web/client/src/components/store/editor/snap/index.ts
apps/web/client/src/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'

Files:

  • apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx
  • apps/web/client/src/components/store/editor/snap/index.ts
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx
  • apps/web/client/src/components/store/editor/snap/index.ts
🧬 Code graph analysis (1)
apps/web/client/src/components/store/editor/snap/index.ts (1)
apps/web/client/src/components/store/editor/snap/types.ts (2)
  • SnapFrame (40-45)
  • SnapLine (20-28)
🔇 Additional comments (1)
apps/web/client/src/app/project/[id]/_components/canvas/frame/resize-handles.tsx (1)

1-1: Confirm client boundary for this observer component.

This component uses events and window APIs; ensure an ancestor declares 'use client'. If not, add it here.

+'use client';

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] Width / Height guides

1 participant