Skip to content

feat: add visibility toggle for components with outline and action bar controls - #1760

Open
fasenderos wants to merge 4 commits into
puckeditor:mainfrom
fasenderos:outline-components-visibility
Open

feat: add visibility toggle for components with outline and action bar controls#1760
fasenderos wants to merge 4 commits into
puckeditor:mainfrom
fasenderos:outline-components-visibility

Conversation

@fasenderos

@fasenderos fasenderos commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Closes #1750

Description

This PR adds a visibility toggle (Hide/Show) for components in Puck, allowing users to hide components from both the editor canvas and public render while keeping them in the DOM.

Hidden components persist through save/load via the hidden field on BaseData and are undoable through the reducer history.

Changes made

  • Added hidden?: boolean to BaseData in types/Data.tsx so visibility state persists in page data automatically.
  • Added SetVisibilityAction type to PuckAction union and setVisibility reducer action with history recording for undo support.
  • Added outline-item-show / outline-item-hide messages to the dictionary.
  • Added hidden field to LayerNode type and wired it through build-layer-tree.
  • Added Eye/EyeOff toggle button in LayerActions and Action Bar with permission gating (permissions.edit).
  • Editor canvas: DraggableComponent reads isHidden from store and applies display: none via a dedicated useEffect, keeping the component in the DOM.
  • Public render (SlotRender): hidden components render inside a <span style={{ display: "none" }}> wrapper instead of being removed from the DOM entirely.

Note

Tests and documentation will be added when you think the implementation is ready to be approved.

Note 2

The decision to keep hidden components in the DOM (via display: none) rather than removing them entirely, follows the same approach used by GrapesJS. However, this choice is open for discussion — removing hidden components from the DOM entirely (return null) would be a valid alternative if preferred.

Summary by CodeRabbit

  • New Features

    • Added visibility controls for components in the canvas and Outline.
    • Hidden components remain available in the Outline while being excluded from rendered output.
    • Added permission-aware show/hide actions with eye icons and localized labels.
    • Hidden layers are visually distinguished with reduced opacity and strikethrough text.
  • Bug Fixes

    • Corrected rich text editor component exports.

@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

@fasenderos is attempting to deploy a commit to the Puck Team on Vercel.

A member of the Team first needs to authorize it.

@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
puck-demo Ready Ready Preview Jul 30, 2026 7:08am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds persisted component visibility state, reducer support, outline and editor show/hide controls, hidden-layer styling, and suppression of hidden components during server rendering.

Changes

Component visibility controls

Layer / File(s) Summary
Visibility state and reducer
packages/core/types/Data.tsx, packages/core/reducer/actions.tsx, packages/core/reducer/actions/set-visibility.ts, packages/core/reducer/index.ts
Components can store an optional hidden flag. The setVisibility action updates the matching component immutably.
Outline visibility controls
packages/core/components/LayerTree/types.ts, packages/core/components/LayerTree/lib/build-layer-tree.ts, packages/core/components/LayerTree/components/layer-actions/index.tsx, packages/core/components/LayerTree/components/layer/index.tsx, packages/core/components/LayerTree/components/layer/styles.module.css, packages/core/lib/dictionary.ts
Outline nodes expose hidden state. Edit-enabled nodes render show and hide controls, and hidden nodes receive reduced opacity and line-through styling.
Editor visibility controls
packages/core/components/DraggableComponent/index.tsx
Draggable components track hidden state, synchronize the element display style, and render permission-gated show or hide actions.
Rendered output suppression
packages/core/components/SlotRender/server.tsx
Server-rendered slot items use display: none when their hidden state is true.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Editor
  participant Outline
  participant Reducer
  participant PageData
  participant RenderedSlot

  Editor->>Outline: display component hidden state
  Outline->>Reducer: dispatch setVisibility
  Reducer->>PageData: update component.hidden
  PageData->>Editor: synchronize component display
  PageData->>RenderedSlot: provide hidden item state
  RenderedSlot->>RenderedSlot: apply display:none
Loading

Suggested reviewers: federicobonel, chrisvxd

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The RichTextEditor barrel export change is unrelated to the visibility-toggle requirements in issue #1750. Remove the unrelated RichTextEditor export change or explain its dependency in the pull request description.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main visibility-toggle changes in the Outline and action bar.
Description check ✅ Passed The description includes the issue link, purpose, implementation details, and scope, but it omits a dedicated testing section.
Linked Issues check ✅ Passed The implementation provides Outline toggles, persistence, undo support, hidden rendering, DOM retention, and restoration as required by issue #1750.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/core/components/DraggableComponent/index.tsx

Oops! Something went wrong! :(

ESLint: 9.39.4

YAMLException: Cannot read config file: /packages/eslint-config-custom/index.mjs
Error: end of the stream or a document separator is expected (10:21)

7 | ...
8 |
9 | const newReactHooksRules = {
10 | "react-hooks/refs": "off",
--------------------------^
11 | "react-hooks/error-boundaries": "off",
12 | "react-hooks/immutability": "off",
Referenced from: /.eslintrc.js
at generateError (/node_modules/js-yaml/lib/loader.js:196:10)
at throwError (/node_modules/js-yaml/lib/loader.js:200:9)
at readDocument (/node_modules/js-yaml/lib/loader.js:1720:5)
at loadDocuments (/node_modules/js-yaml/lib/loader.js:1759:5)
at Object.load (/node_modules/js-yaml/lib/loader.js:1783:21)
at loadLegacyConfigFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2666:21)
at loadConfigFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2782:20)
at ConfigArrayFactory._loadConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3088:42)
at ConfigArrayFactory._loadExtendedShareableConfig (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3393:21)
at ConfigArrayFactory._loadExtends (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3261:25)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)

packages/core/components/LayerTree/components/layer-actions/index.tsx

Oops! Something went wrong! :(

ESLint: 9.39.4

YAMLException: Cannot read config file: /packages/eslint-config-custom/index.mjs
Error: end of the stream or a document separator is expected (10:21)

7 | ...
8 |
9 | const newReactHooksRules = {
10 | "react-hooks/refs": "off",
--------------------------^
11 | "react-hooks/error-boundaries": "off",
12 | "react-hooks/immutability": "off",
Referenced from: /.eslintrc.js
at generateError (/node_modules/js-yaml/lib/loader.js:196:10)
at throwError (/node_modules/js-yaml/lib/loader.js:200:9)
at readDocument (/node_modules/js-yaml/lib/loader.js:1720:5)
at loadDocuments (/node_modules/js-yaml/lib/loader.js:1759:5)
at Object.load (/node_modules/js-yaml/lib/loader.js:1783:21)
at loadLegacyConfigFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2666:21)
at loadConfigFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2782:20)
at ConfigArrayFactory._loadConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3088:42)
at ConfigArrayFactory._loadExtendedShareableConfig (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3393:21)
at ConfigArrayFactory._loadExtends (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3261:25)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)

packages/core/components/LayerTree/components/layer/index.tsx

Oops! Something went wrong! :(

ESLint: 9.39.4

YAMLException: Cannot read config file: /packages/eslint-config-custom/index.mjs
Error: end of the stream or a document separator is expected (10:21)

7 | ...
8 |
9 | const newReactHooksRules = {
10 | "react-hooks/refs": "off",
--------------------------^
11 | "react-hooks/error-boundaries": "off",
12 | "react-hooks/immutability": "off",
Referenced from: /.eslintrc.js
at generateError (/node_modules/js-yaml/lib/loader.js:196:10)
at throwError (/node_modules/js-yaml/lib/loader.js:200:9)
at readDocument (/node_modules/js-yaml/lib/loader.js:1720:5)
at loadDocuments (/node_modules/js-yaml/lib/loader.js:1759:5)
at Object.load (/node_modules/js-yaml/lib/loader.js:1783:21)
at loadLegacyConfigFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2666:21)
at loadConfigFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2782:20)
at ConfigArrayFactory._loadConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3088:42)
at ConfigArrayFactory._loadExtendedShareableConfig (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3393:21)
at ConfigArrayFactory._loadExtends (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3261:25)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)

  • 10 others

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/components/DraggableComponent/index.tsx`:
- Around line 520-526: Update the visibility effect in the DraggableComponent
around ref and isHidden so unhiding restores the element’s original display
behavior instead of assigning "block". Preserve the pre-hide inline display
value before setting it to "none", then restore it when isHidden becomes false,
or use a hidden attribute/class that avoids changing display styles.

In `@packages/core/components/SlotRender/server.tsx`:
- Around line 42-51: Update the rendering logic around Component.render so
visible items return Component.render directly without an inline span. Preserve
the existing props, richtextProps, and puck metadata, and only apply a hiding
wrapper when item.hidden is true.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0f3885f3-9146-4d30-b533-9bc78275362a

📥 Commits

Reviewing files that changed from the base of the PR and between b4204f0 and 239257d.

📒 Files selected for processing (13)
  • packages/core/components/DraggableComponent/index.tsx
  • packages/core/components/LayerTree/components/layer-actions/index.tsx
  • packages/core/components/LayerTree/components/layer/index.tsx
  • packages/core/components/LayerTree/components/layer/styles.module.css
  • packages/core/components/LayerTree/lib/build-layer-tree.ts
  • packages/core/components/LayerTree/types.ts
  • packages/core/components/RichTextEditor/index.ts
  • packages/core/components/SlotRender/server.tsx
  • packages/core/lib/dictionary.ts
  • packages/core/reducer/actions.tsx
  • packages/core/reducer/actions/set-visibility.ts
  • packages/core/reducer/index.ts
  • packages/core/types/Data.tsx

Comment on lines +520 to +526
useEffect(() => {
if (!ref.current) return;

const el = ref.current as HTMLElement;

el.style.display = isHidden ? "none" : "block";
}, [ref.current, isHidden]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore the original display value when the component becomes visible.

Line 525 forces every restored component to display: block. This changes layout for components that use inline, flex, grid, or an explicit inline display style.

Preserve the pre-hide inline display value, or toggle a dedicated hidden attribute or class instead of assigning "block".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/components/DraggableComponent/index.tsx` around lines 520 -
526, Update the visibility effect in the DraggableComponent around ref and
isHidden so unhiding restores the element’s original display behavior instead of
assigning "block". Preserve the pre-hide inline display value before setting it
to "none", then restore it when isHidden becomes false, or use a hidden
attribute/class that avoids changing display styles.

Comment on lines +42 to +51
<span style={item.hidden ? { display: "none" } : undefined}>
<Component.render
{...props}
{...richtextProps}
puck={{
...props.puck,
metadata: metadata || {},
}}
/>
</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not wrap visible component output in an inline span.

Line 42 adds a span around every component, including visible components. This changes flex and grid item layout and breaks selectors that expect the component output as the direct child.

Return Component.render directly when item.hidden is false. Apply a hiding wrapper only for hidden items.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/components/SlotRender/server.tsx` around lines 42 - 51, Update
the rendering logic around Component.render so visible items return
Component.render directly without an inline span. Preserve the existing props,
richtextProps, and puck metadata, and only apply a hiding wrapper when
item.hidden is true.

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.

Allow toggling component visibility from the Outline

1 participant