Skip to content

Conversation

@ghengeveld
Copy link
Member

@ghengeveld ghengeveld commented Dec 16, 2025

What I did

This is an almost complete rewrite of the Zoom tool. Notable changes:

  • Tool is moved from the left-hand side of the toolbar to the right-hand side.
  • The three toolbar items have been replaced with a single button, showing the current zoom percentage.
  • Zooming now uses a set of predefined levels rather than a zoom factor (multiplier), for more predictability and consistency with other software (e.g. DevTools).
  • Keyboard shortcuts ⌥+, ⌥- and ⌥0 have been added for quick access.
  • Custom zoom percentages are now supported, even outside of the usual 25-400% range.
  • The Viewports feature has been updated to play well with zooming, so that the canvas scales along with the zoom level.
Screen.Recording.2025-12-16.at.11.20.15.mov

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • New Features

    • Keyboard shortcut badges now display in a styled format throughout the UI.
    • Numeric input fields support arrow keys to increment/decrement values while preserving units.
    • Zoom tool enhanced with keyboard shortcuts, preset zoom levels, and improved controls.
  • Improvements

    • Improved viewport and iframe scaling behavior for consistent sizing.

✏️ Tip: You can customize this high-level summary in your review settings.

…ions

- Updated ZoomIFrame to correctly apply scaling transformations.
- Enhanced ZoomProvider to manage zoom levels with predefined constants.
- Refactored Zoom component to utilize ActionList for zoom controls and added keyboard shortcuts for zoom actions.
- Improved ZoomWrapper to dynamically adjust zoom levels based on user interactions.
@nx-cloud
Copy link

nx-cloud bot commented Dec 16, 2025

View your CI Pipeline Execution ↗ for commit 955f23b

Command Status Duration Result
nx run-many -t compile,check,knip,test,pretty-d... ❌ Failed 17m 45s View ↗

☁️ Nx Cloud last updated this comment at 2025-12-16 15:01:57 UTC

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 16, 2025

📝 Walkthrough

Walkthrough

This pull request refactors keyboard shortcut handling by extracting the Shortcut component from Menu.tsx into a dedicated module, replaces the SizeInput component with a new NumericInput implementation, introduces Zoom storybook stories, and reorganizes the Zoom tool with context-based state management and enhanced UI. Additionally, iframe scaling logic is adjusted in ZoomIFrame and Viewport, and various import paths are updated accordingly.

Changes

Cohort / File(s) Change Summary
Shortcut component extraction and reorganization
code/core/src/manager/components/Shortcut.tsx, code/core/src/manager/container/Menu.tsx, code/core/src/manager/container/Menu.stories.tsx, code/core/src/manager/container/Preview.tsx, code/core/src/manager/components/sidebar/ContextMenu.tsx, code/core/src/manager/components/preview/tools/share.tsx
New Shortcut component exported from dedicated module; previously defined in Menu.tsx. Import paths updated across ContextMenu, share tool, and Menu to use new location. No logic changes to the component itself.
NumericInput replacement for SizeInput
code/core/src/manager/components/preview/NumericInput.tsx, code/core/src/manager/components/preview/NumericInput.stories.tsx, code/core/src/manager/components/preview/SizeInput.tsx
New NumericInput component with forwardRef, ArrowUp/ArrowDown increment/decrement support, unit preservation, and selection control; replaces removed SizeInput with similar functionality. Storybook stories added for NumericInput with unit and baseUnit scenarios.
Zoom tool refactor with context and UI enhancements
code/core/src/manager/components/preview/tools/zoom.tsx, code/core/src/manager/components/preview/tools/zoom.stories.tsx
Zoom component reworked to accept controlled state (value, zoomIn, zoomOut, zoomTo) instead of imperative callbacks; new ZoomContext and ZoomWrapper introduced; keyboard shortcut integration via useStorybookApi; new ZoomButton and ZoomInput styled utilities; PopoverProvider-based UI replaces inline buttons. Storybook stories added with Default, ZoomIn, ZoomOut, Undo, MaxZoom, and MinZoom variants.
Viewport and iframe scaling adjustments
code/core/src/manager/components/preview/Viewport.tsx, code/core/src/manager/components/preview/ZoomIFrame.tsx, code/.storybook/bench/bench.stories.tsx
Viewport introduces scale-aware transform wrapper, frameStyles computation, and drag-to-resize with scale division; new ScrollEdge components and enhanced drag handlers with data-value attributes. ZoomIFrame adjusts width/height/transform calculations from scale * 100% to (1 / scale) * 100% and updates transform to use scale(scale) directly. Bench stories updates iframe to fixed positioning.
Toolbar tool reorganization
code/core/src/manager/container/Preview.tsx
zoomTool moved from defaultTools to defaultToolsExtra; defaultTools now contains menuTool and remountTool only.
Import path updates
code/core/src/components/components/ActionList/ActionList.stories.tsx
Shortcut import path updated from '../../../manager/container/Menu' to '../../../manager/components/Shortcut'.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–75 minutes

  • Viewport.tsx: Scale-aware transform wrapper, frameStyles computation, drag-to-resize with scale division, ScrollEdge components, and drag handler enhancements with scroll-into-view behavior require careful review of scale calculations and drag event flow.
  • zoom.tsx: Context-based state management, new ZoomWrapper structure, keyboard shortcut integration via useStorybookApi, and PopoverProvider-based UI need verification for proper state synchronization and callback wiring.
  • NumericInput.tsx: New imperative ref API (select method), input value synchronization with external value, ArrowUp/ArrowDown keyboard handling, and unit suffix preservation require thorough logic verification.
  • ZoomIFrame.tsx: Scale calculation inversions (scale * 100% → (1 / scale) * 100%) and transform adjustments need verification against expected layout behavior.

Possibly related PRs

✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link
Contributor

@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

🧹 Nitpick comments (5)
code/core/src/manager/components/preview/Viewport.tsx (1)

417-429: Minor: data-value initialization may show units for non-px dimensions.

The .replace('px', '') only strips px. For other units like % or vh, the unit would still appear in the tooltip. Consider using parseNumber(width)?.number for consistency.

         <DragHandle
           ref={dragRefX}
           isDefault={isDefault}
           data-side="right"
-          data-value={width.replace('px', '')}
+          data-value={parseNumber(width)?.number ?? width}
         />
         <DragHandle
           ref={dragRefY}
           isDefault={isDefault}
           data-side="bottom"
-          data-value={height.replace('px', '')}
+          data-value={parseNumber(height)?.number ?? height}
         />
code/core/src/manager/components/preview/tools/zoom.stories.tsx (1)

27-28: Consider using ZOOM_LEVELS for consistent behavior.

The story's zoomIn/zoomOut use a fixed 0.5 increment, while the actual implementation uses predefined ZOOM_LEVELS. This divergence may cause stories to behave differently than production. For more realistic testing, consider importing and using the same zoom level logic.

+import { Zoom, ZOOM_LEVELS } from './zoom';
-import { Zoom } from './zoom';
...
-          zoomIn: () => setValue(value + 0.5),
-          zoomOut: () => setValue(value - 0.5),
+          zoomIn: () => {
+            const higher = ZOOM_LEVELS.find((level) => level > value);
+            if (higher) setValue(higher);
+          },
+          zoomOut: () => {
+            const lower = [...ZOOM_LEVELS].reverse().find((level) => level < value);
+            if (lower) setValue(lower);
+          },

Note: This would require exporting ZOOM_LEVELS from zoom.tsx.

code/core/src/manager/components/preview/NumericInput.tsx (2)

71-71: Escape special regex characters in baseUnit.

If baseUnit contains regex metacharacters (e.g., %, ., +), the pattern may not match correctly. Consider escaping special characters.

-    const baseUnitRegex = useMemo(() => baseUnit && new RegExp(`${baseUnit}$`), [baseUnit]);
+    const baseUnitRegex = useMemo(
+      () => baseUnit && new RegExp(`${baseUnit.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`),
+      [baseUnit]
+    );

77-77: Type mismatch in useImperativeHandle.

The forwardRef declares { select: () => void } but exposes the full HTMLInputElement. While this works because HTMLInputElement has a select() method, it's semantically incorrect and exposes more than intended.

-    useImperativeHandle(forwardedRef, () => inputRef.current!);
+    useImperativeHandle(forwardedRef, () => ({
+      select: () => inputRef.current?.select(),
+    }));
code/core/src/manager/components/preview/tools/zoom.tsx (1)

182-201: Shortcuts re-registered on every zoom level change.

The useEffect dependencies include zoomIn, zoomOut, and zoomTo, which are recreated on each value change. This causes setAddonShortcut to be called repeatedly. Consider stabilizing the callbacks using refs or extracting state access to avoid re-registration on every zoom level change.

+  const valueRef = useRef(value);
+  valueRef.current = value;
+
+  const stableZoomIn = useCallback(() => {
+    const higherZoomLevel = ZOOM_LEVELS.find((level) => level > valueRef.current);
+    if (higherZoomLevel) set(higherZoomLevel);
+  }, [set]);
+
+  const stableZoomOut = useCallback(() => {
+    const lowerZoomLevel = ZOOM_LEVELS.findLast((level) => level < valueRef.current);
+    if (lowerZoomLevel) set(lowerZoomLevel);
+  }, [set]);
+
+  const stableZoomTo = useCallback((v: number) => set(v), [set]);
+
   useEffect(() => {
     api.setAddonShortcut('zoom', {
       label: 'Zoom to 100%',
       defaultShortcut: ['alt', '0'],
       actionName: 'zoomReset',
-      action: () => zoomTo(1),
+      action: () => stableZoomTo(1),
     });
     // ... similar for other shortcuts
-  }, [api, zoomIn, zoomOut, zoomTo]);
+  }, [api, stableZoomIn, stableZoomOut, stableZoomTo]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 879b3c2 and 955f23b.

📒 Files selected for processing (15)
  • code/.storybook/bench/bench.stories.tsx (1 hunks)
  • code/core/src/components/components/ActionList/ActionList.stories.tsx (1 hunks)
  • code/core/src/components/components/Zoom/ZoomIFrame.tsx (2 hunks)
  • code/core/src/manager/components/Shortcut.tsx (1 hunks)
  • code/core/src/manager/components/preview/NumericInput.stories.tsx (1 hunks)
  • code/core/src/manager/components/preview/NumericInput.tsx (1 hunks)
  • code/core/src/manager/components/preview/SizeInput.tsx (0 hunks)
  • code/core/src/manager/components/preview/Viewport.tsx (9 hunks)
  • code/core/src/manager/components/preview/tools/share.tsx (1 hunks)
  • code/core/src/manager/components/preview/tools/zoom.stories.tsx (1 hunks)
  • code/core/src/manager/components/preview/tools/zoom.tsx (2 hunks)
  • code/core/src/manager/components/sidebar/ContextMenu.tsx (1 hunks)
  • code/core/src/manager/container/Menu.stories.tsx (1 hunks)
  • code/core/src/manager/container/Menu.tsx (1 hunks)
  • code/core/src/manager/container/Preview.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • code/core/src/manager/components/preview/SizeInput.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ESLint and Prettier configurations that are enforced in the codebase

Files:

  • code/core/src/components/components/ActionList/ActionList.stories.tsx
  • code/core/src/components/components/Zoom/ZoomIFrame.tsx
  • code/core/src/manager/components/preview/NumericInput.tsx
  • code/core/src/manager/components/preview/NumericInput.stories.tsx
  • code/core/src/manager/components/preview/tools/share.tsx
  • code/core/src/manager/components/preview/tools/zoom.stories.tsx
  • code/core/src/manager/components/Shortcut.tsx
  • code/core/src/manager/container/Preview.tsx
  • code/core/src/manager/container/Menu.tsx
  • code/core/src/manager/components/sidebar/ContextMenu.tsx
  • code/core/src/manager/container/Menu.stories.tsx
  • code/core/src/manager/components/preview/tools/zoom.tsx
  • code/core/src/manager/components/preview/Viewport.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Enable TypeScript strict mode

Files:

  • code/core/src/components/components/ActionList/ActionList.stories.tsx
  • code/core/src/components/components/Zoom/ZoomIFrame.tsx
  • code/core/src/manager/components/preview/NumericInput.tsx
  • code/core/src/manager/components/preview/NumericInput.stories.tsx
  • code/core/src/manager/components/preview/tools/share.tsx
  • code/core/src/manager/components/preview/tools/zoom.stories.tsx
  • code/core/src/manager/components/Shortcut.tsx
  • code/core/src/manager/container/Preview.tsx
  • code/core/src/manager/container/Menu.tsx
  • code/core/src/manager/components/sidebar/ContextMenu.tsx
  • code/core/src/manager/container/Menu.stories.tsx
  • code/core/src/manager/components/preview/tools/zoom.tsx
  • code/core/src/manager/components/preview/Viewport.tsx
code/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/*.{ts,tsx,js,jsx,mjs}: Use server-side logger from 'storybook/internal/node-logger' for Node.js code
Use client-side logger from 'storybook/internal/client-logger' for browser code
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/components/components/ActionList/ActionList.stories.tsx
  • code/core/src/components/components/Zoom/ZoomIFrame.tsx
  • code/core/src/manager/components/preview/NumericInput.tsx
  • code/core/src/manager/components/preview/NumericInput.stories.tsx
  • code/core/src/manager/components/preview/tools/share.tsx
  • code/core/src/manager/components/preview/tools/zoom.stories.tsx
  • code/core/src/manager/components/Shortcut.tsx
  • code/core/src/manager/container/Preview.tsx
  • code/core/src/manager/container/Menu.tsx
  • code/core/src/manager/components/sidebar/ContextMenu.tsx
  • code/core/src/manager/container/Menu.stories.tsx
  • code/core/src/manager/components/preview/tools/zoom.tsx
  • code/core/src/manager/components/preview/Viewport.tsx
code/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Export functions that need to be tested from their modules

Files:

  • code/core/src/components/components/ActionList/ActionList.stories.tsx
  • code/core/src/components/components/Zoom/ZoomIFrame.tsx
  • code/core/src/manager/components/preview/NumericInput.tsx
  • code/core/src/manager/components/preview/NumericInput.stories.tsx
  • code/core/src/manager/components/preview/tools/share.tsx
  • code/core/src/manager/components/preview/tools/zoom.stories.tsx
  • code/core/src/manager/components/Shortcut.tsx
  • code/core/src/manager/container/Preview.tsx
  • code/core/src/manager/container/Menu.tsx
  • code/core/src/manager/components/sidebar/ContextMenu.tsx
  • code/core/src/manager/container/Menu.stories.tsx
  • code/core/src/manager/components/preview/tools/zoom.tsx
  • code/core/src/manager/components/preview/Viewport.tsx
code/**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier with --write flag to format code before committing
Run ESLint with yarn lint:js:cmd to check for linting issues and fix errors before committing

Files:

  • code/core/src/components/components/ActionList/ActionList.stories.tsx
  • code/core/src/components/components/Zoom/ZoomIFrame.tsx
  • code/core/src/manager/components/preview/NumericInput.tsx
  • code/core/src/manager/components/preview/NumericInput.stories.tsx
  • code/core/src/manager/components/preview/tools/share.tsx
  • code/core/src/manager/components/preview/tools/zoom.stories.tsx
  • code/core/src/manager/components/Shortcut.tsx
  • code/core/src/manager/container/Preview.tsx
  • code/core/src/manager/container/Menu.tsx
  • code/core/src/manager/components/sidebar/ContextMenu.tsx
  • code/core/src/manager/container/Menu.stories.tsx
  • code/core/src/manager/components/preview/tools/zoom.tsx
  • code/core/src/manager/components/preview/Viewport.tsx
🧠 Learnings (8)
📚 Learning: 2025-09-24T09:39:39.233Z
Learnt from: ndelangen
Repo: storybookjs/storybook PR: 32507
File: code/core/src/manager/globals/globals-module-info.ts:25-33
Timestamp: 2025-09-24T09:39:39.233Z
Learning: In Storybook, storybook/actions/decorator is a preview-only entrypoint and should not be included in manager globals configuration. The duplicatedKeys array in code/core/src/manager/globals/globals-module-info.ts is specifically for manager-side externalization, not preview entrypoints.

Applied to files:

  • code/core/src/components/components/ActionList/ActionList.stories.tsx
  • code/core/src/manager/components/preview/NumericInput.stories.tsx
  • code/core/src/manager/components/preview/tools/share.tsx
  • code/core/src/manager/components/preview/tools/zoom.stories.tsx
  • code/core/src/manager/container/Preview.tsx
  • code/core/src/manager/container/Menu.stories.tsx
📚 Learning: 2025-10-01T15:24:01.060Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32594
File: code/core/src/components/components/Popover/WithPopover.tsx:7-9
Timestamp: 2025-10-01T15:24:01.060Z
Learning: In the Storybook repository, "react-aria-components/patched-dist/*" (e.g., "react-aria-components/patched-dist/Dialog", "react-aria-components/patched-dist/Popover", "react-aria-components/patched-dist/Tooltip") are valid import paths created by a patch applied to the react-aria-components package. These imports should not be flagged as broken or invalid until a maintainer explicitly states they are no longer acceptable.

Applied to files:

  • code/core/src/components/components/ActionList/ActionList.stories.tsx
📚 Learning: 2025-11-28T14:50:24.889Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-28T14:50:24.889Z
Learning: Follow existing patterns and conventions in the Storybook codebase

Applied to files:

  • code/core/src/manager/components/preview/NumericInput.stories.tsx
  • code/core/src/manager/components/preview/tools/zoom.stories.tsx
  • code/core/src/manager/components/Shortcut.tsx
📚 Learning: 2025-11-24T17:49:59.279Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Document complex mock behaviors in Vitest tests

Applied to files:

  • code/core/src/manager/components/preview/NumericInput.stories.tsx
  • code/core/src/manager/components/preview/tools/zoom.stories.tsx
📚 Learning: 2025-11-05T09:37:25.920Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/tooltip/WithTooltip.tsx:54-96
Timestamp: 2025-11-05T09:37:25.920Z
Learning: Repo: storybookjs/storybook — In code/core/src/components/components/tooltip/WithTooltip.tsx, the legacy WithTooltip implementation is intentionally reintroduced for backward compatibility and is deprecated; maintainers (per Sidnioulz) do not want maintenance or improvements on it. Prefer WithTooltipNew/Popover; avoid suggesting changes to WithTooltip.* going forward.

Applied to files:

  • code/core/src/manager/components/preview/tools/share.tsx
  • code/core/src/manager/components/preview/tools/zoom.stories.tsx
  • code/core/src/manager/container/Menu.tsx
  • code/core/src/manager/components/sidebar/ContextMenu.tsx
  • code/core/src/manager/container/Menu.stories.tsx
  • code/core/src/manager/components/preview/tools/zoom.tsx
  • code/core/src/manager/components/preview/Viewport.tsx
📚 Learning: 2025-11-05T09:38:47.712Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Select/Select.tsx:200-204
Timestamp: 2025-11-05T09:38:47.712Z
Learning: Repo: storybookjs/storybook — Guidance: Until Storybook 11 is released, do not suggest using React.useId anywhere (e.g., in code/core/src/components/components/Select/Select.tsx) to maintain compatibility with React 17 runtimes. Prefer advising: accept a caller-provided props.id and, if needed, generate a client-only fallback id to minimize SSR hydration issues — but avoid useId. Resume prompting for useId after Storybook 11.

Applied to files:

  • code/core/src/manager/components/preview/tools/zoom.stories.tsx
  • code/core/src/manager/container/Menu.tsx
  • code/core/src/manager/components/preview/tools/zoom.tsx
  • code/core/src/manager/components/preview/Viewport.tsx
📚 Learning: 2025-10-03T07:55:42.639Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/manager/components/preview/Toolbar.tsx:102-105
Timestamp: 2025-10-03T07:55:42.639Z
Learning: In code/core/src/manager/components/preview/Toolbar.tsx, we intentionally do not add aria-label/aria-labelledby to StyledToolbar (AbstractToolbar) because the enclosing section is already labeled via an sr-only heading and the toolbar is the only content. Revisit only if real user testing indicates a need.

Applied to files:

  • code/core/src/manager/container/Preview.tsx
📚 Learning: 2025-11-25T11:09:33.798Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 33140
File: code/core/src/manager/components/sidebar/TagsFilter.tsx:247-259
Timestamp: 2025-11-25T11:09:33.798Z
Learning: In the storybookjs/storybook repository, PopoverProvider creates popovers with a dialog role, so using aria-haspopup="dialog" on buttons that trigger PopoverProvider is semantically correct.

Applied to files:

  • code/core/src/manager/components/preview/Viewport.tsx
🧬 Code graph analysis (4)
code/core/src/manager/components/preview/NumericInput.stories.tsx (1)
code/core/src/manager/components/preview/NumericInput.tsx (1)
  • NumericInput (66-152)
code/core/src/manager/components/preview/tools/zoom.stories.tsx (1)
code/core/src/manager/components/preview/tools/zoom.tsx (1)
  • Zoom (54-153)
code/core/src/manager/container/Preview.tsx (2)
code/core/src/manager/components/preview/tools/zoom.tsx (1)
  • zoomTool (206-212)
code/core/src/manager/components/preview/tools/share.tsx (1)
  • shareTool (152-184)
code/core/src/manager/components/preview/Viewport.tsx (2)
code/core/src/manager/components/preview/NumericInput.tsx (1)
  • NumericInput (66-152)
code/core/src/manager/components/preview/Iframe.tsx (1)
  • IFrame (29-47)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: normal
  • GitHub Check: nx
  • GitHub Check: Core Unit Tests, windows-latest
  • GitHub Check: nx
  • GitHub Check: nx
🔇 Additional comments (25)
code/.storybook/bench/bench.stories.tsx (1)

72-72: LGTM! Verify fixed positioning works with zoom tool.

The change to position: 'fixed' and width: '100vw' ensures the bundle analyzer iframe properly fills the viewport regardless of zoom level, which aligns with the PR's viewport scaling updates.

Please verify that the fixed positioning works correctly with the new zoom tool in fullscreen layout and doesn't cause any layering or overflow issues with other Storybook UI elements.

code/core/src/manager/components/preview/Viewport.tsx (9)

1-16: LGTM! Imports updated correctly.

The additions of useMemo and NumericInput align with the new scale-aware frame styling and dimension controls.


127-183: Clean refactor of DragHandle with conditional rendering.

The isDefault prop now drives visibility at the styled-component level, simplifying usage in JSX. The data-value attribute pattern for displaying dimensions during drag is a nice approach.


185-202: Invisible scroll anchors for drag auto-scrolling.

The ScrollEdge components serve as scroll targets for scrollIntoView during drag operations—good UX for large viewports that extend beyond the visible area.


204-208: LGTM!

Simple styled wrapper for consistent input sizing.


250-267: Good addition of real-time dimension feedback and auto-scrolling.

The data-value updates provide immediate visual feedback during drag, and the scrollIntoView calls ensure the drag handle remains visible when resizing large viewports.


270-278: Scale-aware dimension calculation looks correct.

Reading scale from data-scale and dividing the measured dimensions ensures the resize call receives the logical (unscaled) viewport size. The || 1 fallback handles missing/invalid scale gracefully.


297-300: LGTM!

The parseNumber helper correctly extracts numeric values and units from dimension strings, with proper handling for decimals and various CSS units.


302-309: Verify scaling behavior for percentage-based dimensions.

The calculation multiplies the numeric portion by scale, which works intuitively for px units. For percentage units (e.g., 50%), this would produce 100% at 2x scale—confirm this is the intended behavior for percentage-based viewports.


400-416: Scale transformation approach is correct.

The wrapper-based scaling (inverse size + transform) maintains proper DOM dimensions for the iframe while achieving visual zoom. The scale={1} prop on IFrame correctly delegates scaling to the wrapper.

code/core/src/manager/components/preview/tools/share.tsx (1)

20-20: LGTM!

Import path correctly updated to reference the new shared Shortcut component location.

code/core/src/manager/components/sidebar/ContextMenu.tsx (1)

22-22: LGTM!

Import path correctly updated to the new shared Shortcut component.

code/core/src/manager/container/Menu.stories.tsx (1)

10-10: LGTM!

Import path correctly updated to reference the extracted Shortcut component.

code/core/src/components/components/ActionList/ActionList.stories.tsx (1)

5-5: Consider alternative approaches for keyboard shortcut display in ActionList story.

This story imports Shortcut directly from the manager package, which maintains architectural separation in the codebase. All other manager-internal stories use relative imports within manager, and the components package does not export manager utilities via its public API. Either use an alternative pattern for demonstrating shortcuts in this story, or verify this cross-package dependency is acceptable for documentation purposes.

code/core/src/manager/container/Preview.tsx (1)

25-26: LGTM!

The tool grouping reorganization correctly moves zoomTool to defaultToolsExtra, positioning it on the right side of the toolbar as intended by the PR objectives.

code/core/src/components/components/Zoom/ZoomIFrame.tsx (2)

38-50: LGTM!

The updated scaling logic correctly implements the zoom behavior: the iframe dimensions are set to (1/scale) * 100% to compensate for the CSS scale(scale) transform, ensuring the content fills the available space at the desired zoom level.


52-59: LGTM!

The fallback setIframeZoom method correctly mirrors the inner zoom logic for cases where cross-origin restrictions prevent modifying the iframe's content document.

code/core/src/manager/container/Menu.tsx (1)

20-21: LGTM!

Clean refactor extracting the Shortcut component to a shared module. The import path and usage pattern are consistent throughout the file.

code/core/src/manager/components/Shortcut.tsx (1)

30-36: LGTM!

Well-structured centralized Shortcut component with appropriate theming and styling. The use of key={key} is acceptable since keyboard shortcuts typically don't contain duplicate keys.

code/core/src/manager/components/preview/NumericInput.stories.tsx (2)

16-30: LGTM!

Good interaction test coverage for NumericInput. The play function correctly tests ArrowUp/ArrowDown keyboard behavior and verifies both the displayed value and the setValue callback invocations.


65-80: LGTM!

The WithBaseUnit story correctly verifies that the baseUnit is stripped from the display value ('11') while still being included in the setValue calls ('11em').

code/core/src/manager/components/preview/NumericInput.tsx (1)

112-127: Arrow key handler doesn't support negative values.

The condition update >= 0 on line 120 prevents decrementing below zero. If negative values are intentionally unsupported, this is fine; otherwise, consider allowing negative numbers or making this configurable.

Is the restriction to non-negative values intentional for all NumericInput use cases (e.g., zoom percentages, dimensions)?

code/core/src/manager/components/preview/tools/zoom.tsx (3)

15-16: LGTM!

Good use of as const for ZOOM_LEVELS to enable precise array method types (e.g., .at(-1) returning 4 not number | undefined).


109-111: Shortcut display mismatch with registered key.

The UI shows ['alt', '+'] but the shortcut is registered as ['alt', '='] (line 191). While these are the same physical key on US keyboards, this could cause confusion. Consider aligning the display with the registered shortcut or ensuring shortcutToHumanString normalizes this.

Verify that shortcutToHumanString(['alt', '+']) and shortcutToHumanString(['alt', '=']) display consistently to users across different keyboard layouts.


54-153: LGTM!

The Zoom component is well-structured with a clean popover-based UI. The memoization, accessibility labels, and disabled states for boundary conditions are properly implemented.

@storybook-app-bot
Copy link

Package Benchmarks

Commit: 955f23b, ran on 16 December 2025 at 14:55:10 UTC

The following packages have significant changes to their size or dependencies:

@storybook/addon-a11y

Before After Difference
Dependency count 0 2 🚨 +2 🚨
Self size 0 B 181 KB 🚨 +181 KB 🚨
Dependency size 0 B 2.97 MB 🚨 +2.97 MB 🚨
Bundle Size Analyzer Link Link

@storybook/addon-docs

Before After Difference
Dependency count 0 18 🚨 +18 🚨
Self size 0 B 1.79 MB 🚨 +1.79 MB 🚨
Dependency size 0 B 9.25 MB 🚨 +9.25 MB 🚨
Bundle Size Analyzer Link Link

@storybook/addon-links

Before After Difference
Dependency count 0 1 🚨 +1 🚨
Self size 0 B 14 KB 🚨 +14 KB 🚨
Dependency size 0 B 5 KB 🚨 +5 KB 🚨
Bundle Size Analyzer Link Link

@storybook/addon-onboarding

Before After Difference
Dependency count 0 0 0
Self size 0 B 331 KB 🚨 +331 KB 🚨
Dependency size 0 B 670 B 🚨 +670 B 🚨
Bundle Size Analyzer Link Link

storybook-addon-pseudo-states

Before After Difference
Dependency count 0 0 0
Self size 0 B 21 KB 🚨 +21 KB 🚨
Dependency size 0 B 689 B 🚨 +689 B 🚨
Bundle Size Analyzer Link Link

@storybook/addon-themes

Before After Difference
Dependency count 0 1 🚨 +1 🚨
Self size 0 B 18 KB 🚨 +18 KB 🚨
Dependency size 0 B 28 KB 🚨 +28 KB 🚨
Bundle Size Analyzer Link Link

@storybook/addon-vitest

Before After Difference
Dependency count 0 2 🚨 +2 🚨
Self size 0 B 376 KB 🚨 +376 KB 🚨
Dependency size 0 B 338 KB 🚨 +338 KB 🚨
Bundle Size Analyzer Link Link

@storybook/builder-vite

Before After Difference
Dependency count 0 17 🚨 +17 🚨
Self size 0 B 325 KB 🚨 +325 KB 🚨
Dependency size 0 B 2.00 MB 🚨 +2.00 MB 🚨
Bundle Size Analyzer Link Link

@storybook/builder-webpack5

Before After Difference
Dependency count 0 192 🚨 +192 🚨
Self size 0 B 75 KB 🚨 +75 KB 🚨
Dependency size 0 B 32.16 MB 🚨 +32.16 MB 🚨
Bundle Size Analyzer Link Link

storybook

Before After Difference
Dependency count 0 39 🚨 +39 🚨
Self size 0 B 20.56 MB 🚨 +20.56 MB 🚨
Dependency size 0 B 16.41 MB 🚨 +16.41 MB 🚨
Bundle Size Analyzer Link Link

@storybook/angular

Before After Difference
Dependency count 0 191 🚨 +191 🚨
Self size 0 B 118 KB 🚨 +118 KB 🚨
Dependency size 0 B 30.14 MB 🚨 +30.14 MB 🚨
Bundle Size Analyzer Link Link

@storybook/ember

Before After Difference
Dependency count 0 196 🚨 +196 🚨
Self size 0 B 15 KB 🚨 +15 KB 🚨
Dependency size 0 B 28.88 MB 🚨 +28.88 MB 🚨
Bundle Size Analyzer Link Link

@storybook/html-vite

Before After Difference
Dependency count 0 20 🚨 +20 🚨
Self size 0 B 22 KB 🚨 +22 KB 🚨
Dependency size 0 B 2.36 MB 🚨 +2.36 MB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs

Before After Difference
Dependency count 0 538 🚨 +538 🚨
Self size 0 B 645 KB 🚨 +645 KB 🚨
Dependency size 0 B 59.09 MB 🚨 +59.09 MB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs-vite

Before After Difference
Dependency count 0 128 🚨 +128 🚨
Self size 0 B 1.12 MB 🚨 +1.12 MB 🚨
Dependency size 0 B 21.96 MB 🚨 +21.96 MB 🚨
Bundle Size Analyzer Link Link

@storybook/preact-vite

Before After Difference
Dependency count 0 20 🚨 +20 🚨
Self size 0 B 13 KB 🚨 +13 KB 🚨
Dependency size 0 B 2.35 MB 🚨 +2.35 MB 🚨
Bundle Size Analyzer Link Link

@storybook/react-native-web-vite

Before After Difference
Dependency count 0 160 🚨 +160 🚨
Self size 0 B 30 KB 🚨 +30 KB 🚨
Dependency size 0 B 23.14 MB 🚨 +23.14 MB 🚨
Bundle Size Analyzer Link Link

@storybook/react-vite

Before After Difference
Dependency count 0 118 🚨 +118 🚨
Self size 0 B 35 KB 🚨 +35 KB 🚨
Dependency size 0 B 19.75 MB 🚨 +19.75 MB 🚨
Bundle Size Analyzer Link Link

@storybook/react-webpack5

Before After Difference
Dependency count 0 278 🚨 +278 🚨
Self size 0 B 24 KB 🚨 +24 KB 🚨
Dependency size 0 B 44.04 MB 🚨 +44.04 MB 🚨
Bundle Size Analyzer Link Link

@storybook/server-webpack5

Before After Difference
Dependency count 0 204 🚨 +204 🚨
Self size 0 B 16 KB 🚨 +16 KB 🚨
Dependency size 0 B 33.41 MB 🚨 +33.41 MB 🚨
Bundle Size Analyzer Link Link

@storybook/svelte-vite

Before After Difference
Dependency count 0 24 🚨 +24 🚨
Self size 0 B 55 KB 🚨 +55 KB 🚨
Dependency size 0 B 27.02 MB 🚨 +27.02 MB 🚨
Bundle Size Analyzer Link Link

@storybook/sveltekit

Before After Difference
Dependency count 0 25 🚨 +25 🚨
Self size 0 B 56 KB 🚨 +56 KB 🚨
Dependency size 0 B 27.08 MB 🚨 +27.08 MB 🚨
Bundle Size Analyzer Link Link

@storybook/vue3-vite

Before After Difference
Dependency count 0 114 🚨 +114 🚨
Self size 0 B 35 KB 🚨 +35 KB 🚨
Dependency size 0 B 44.17 MB 🚨 +44.17 MB 🚨
Bundle Size Analyzer Link Link

@storybook/web-components-vite

Before After Difference
Dependency count 0 21 🚨 +21 🚨
Self size 0 B 19 KB 🚨 +19 KB 🚨
Dependency size 0 B 2.39 MB 🚨 +2.39 MB 🚨
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 0 173 🚨 +173 🚨
Self size 0 B 775 KB 🚨 +775 KB 🚨
Dependency size 0 B 67.49 MB 🚨 +67.49 MB 🚨
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 0 166 🚨 +166 🚨
Self size 0 B 30 KB 🚨 +30 KB 🚨
Dependency size 0 B 66.06 MB 🚨 +66.06 MB 🚨
Bundle Size Analyzer Link Link

@storybook/core-webpack

Before After Difference
Dependency count 0 1 🚨 +1 🚨
Self size 0 B 11 KB 🚨 +11 KB 🚨
Dependency size 0 B 28 KB 🚨 +28 KB 🚨
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 0 40 🚨 +40 🚨
Self size 0 B 1000 KB 🚨 +1000 KB 🚨
Dependency size 0 B 36.97 MB 🚨 +36.97 MB 🚨
Bundle Size Analyzer node node

@storybook/csf-plugin

Before After Difference
Dependency count 0 9 🚨 +9 🚨
Self size 0 B 7 KB 🚨 +7 KB 🚨
Dependency size 0 B 1.26 MB 🚨 +1.26 MB 🚨
Bundle Size Analyzer Link Link

eslint-plugin-storybook

Before After Difference
Dependency count 0 20 🚨 +20 🚨
Self size 0 B 131 KB 🚨 +131 KB 🚨
Dependency size 0 B 2.81 MB 🚨 +2.81 MB 🚨
Bundle Size Analyzer Link Link

@storybook/react-dom-shim

Before After Difference
Dependency count 0 0 0
Self size 0 B 18 KB 🚨 +18 KB 🚨
Dependency size 0 B 788 B 🚨 +788 B 🚨
Bundle Size Analyzer Link Link

@storybook/preset-create-react-app

Before After Difference
Dependency count 0 68 🚨 +68 🚨
Self size 0 B 32 KB 🚨 +32 KB 🚨
Dependency size 0 B 5.98 MB 🚨 +5.98 MB 🚨
Bundle Size Analyzer Link Link

@storybook/preset-react-webpack

Before After Difference
Dependency count 0 170 🚨 +170 🚨
Self size 0 B 18 KB 🚨 +18 KB 🚨
Dependency size 0 B 31.19 MB 🚨 +31.19 MB 🚨
Bundle Size Analyzer Link Link

@storybook/preset-server-webpack

Before After Difference
Dependency count 0 10 🚨 +10 🚨
Self size 0 B 7 KB 🚨 +7 KB 🚨
Dependency size 0 B 1.20 MB 🚨 +1.20 MB 🚨
Bundle Size Analyzer Link Link

@storybook/html

Before After Difference
Dependency count 0 2 🚨 +2 🚨
Self size 0 B 29 KB 🚨 +29 KB 🚨
Dependency size 0 B 32 KB 🚨 +32 KB 🚨
Bundle Size Analyzer Link Link

@storybook/preact

Before After Difference
Dependency count 0 2 🚨 +2 🚨
Self size 0 B 16 KB 🚨 +16 KB 🚨
Dependency size 0 B 32 KB 🚨 +32 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react

Before After Difference
Dependency count 0 57 🚨 +57 🚨
Self size 0 B 716 KB 🚨 +716 KB 🚨
Dependency size 0 B 12.91 MB 🚨 +12.91 MB 🚨
Bundle Size Analyzer Link Link

@storybook/server

Before After Difference
Dependency count 0 3 🚨 +3 🚨
Self size 0 B 8 KB 🚨 +8 KB 🚨
Dependency size 0 B 716 KB 🚨 +716 KB 🚨
Bundle Size Analyzer Link Link

@storybook/svelte

Before After Difference
Dependency count 0 2 🚨 +2 🚨
Self size 0 B 45 KB 🚨 +45 KB 🚨
Dependency size 0 B 230 KB 🚨 +230 KB 🚨
Bundle Size Analyzer Link Link

@storybook/vue3

Before After Difference
Dependency count 0 3 🚨 +3 🚨
Self size 0 B 55 KB 🚨 +55 KB 🚨
Dependency size 0 B 211 KB 🚨 +211 KB 🚨
Bundle Size Analyzer Link Link

@storybook/web-components

Before After Difference
Dependency count 0 3 🚨 +3 🚨
Self size 0 B 41 KB 🚨 +41 KB 🚨
Dependency size 0 B 47 KB 🚨 +47 KB 🚨
Bundle Size Analyzer Link Link

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants