Skip to content

[6.x] Consolidate Pane and craft-pane into a single web component - #19398

Merged
brandonkelly merged 7 commits into
6.xfrom
feature/craft-pane
Aug 12, 2026
Merged

[6.x] Consolidate Pane and craft-pane into a single web component#19398
brandonkelly merged 7 commits into
6.xfrom
feature/craft-pane

Conversation

@brianjhanson

Copy link
Copy Markdown
Contributor

Description

The CP had two pane implementations: a thin craft-pane Lit element with only a default slot, and a richer Vue Pane.vue with header/footer regions, variants and a padding scale. This folds both into a single craft-pane web component, migrates all 48 <Pane> consumers, and deletes Pane.vue.

The unified API keeps the Vue prop names, which carried the richer semantics and the larger consumer base:

Attribute Values Default
appearance raised | outline | plain | sunken raised
variant plain | error | code plain
padding sm/md/lg/xl, 0, a number, or any CSS length lg
label renders an <h1> in the header

appearance and variant stay orthogonal, so a code pane can also be outlined. size is replaced by padding, and as is dropped. Header and footer regions render only when their slots are filled, following the craft-card pattern.

Two changes worth calling out for review:

  • title became label. A pane is a large surface, so a native title attribute would fire a browser tooltip anywhere inside it and be announced over the visible heading. This also matches craft-card.
  • Panes that didn't set appearance now take the raised default, so they gain a border and shadow. This affects the login/set-password/verify-email screens, the installer, the icon picker, the stack trace and project config.

Moving the pane's surface into shadow DOM broke keyboard scrolling on the variant="code" panes: overflow/max-height used to sit on the same element that consumers put tabindex="0" on, so the focused element was the scroll container. The component now owns this — it makes its own scroll container the tab stop (tabindex, role="region", aria-label) while the content overflows, and consumers no longer hand-wire tabindex.

ModalForm forwarded slots dynamically with v-for (_, slotName) in $slots, which native slots can't express; each pane region is now forwarded explicitly through a display: contents wrapper, rendered only when the parent fills that slot. Install.vue used as="form", so its surrounding wrapper <div> becomes the <form>.

Adds Storybook stories and tests covering the variants, the padding scale, the slot regions and the keyboard-scroll behavior.

🤖 Generated with Claude Code

brianjhanson and others added 3 commits August 11, 2026 12:47
The CP had two pane implementations: a thin `craft-pane` Lit element with
only a default slot, and a richer Vue `Pane.vue` with header/footer
regions, variants and a padding scale. Fold both into `craft-pane`.

The unified API keeps the Vue prop names, which carried the richer
semantics and the larger consumer base:

    appearance  raised | outline | plain | sunken  (default: raised)
    variant     plain | error | code               (default: plain)
    padding     sm|md|lg|xl | 0 | number | length  (default: lg)
    label       renders an <h1> in the header

`appearance` and `variant` stay orthogonal, so a code pane can also be
outlined. `size` is replaced by `padding`, and `as` is dropped.

Header and footer regions render only when their slots are filled,
following the `craft-card` pattern: @State plus slotchange plus a
light-DOM MutationObserver, since slotchange alone does not fire for
conditionally rendered slots.

`title` became `label`. A pane is a large surface, so a native `title`
attribute would fire a browser tooltip anywhere inside it and be
announced over the visible heading. `label` also matches `craft-card`.

A scrollable `variant="code"` pane makes its own scroll container the tab
stop (tabindex, role=region, aria-label), and only while the content
actually overflows. The scroller lives in the shadow root, so a tabindex
on the host would leave the focused element and the scroll container as
different nodes and keyboard scrolling would fall through to the page.

Adds Storybook stories and tests covering the variants, the padding
scale, the slot regions and the keyboard-scroll behavior.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Swaps every `<Pane>` consumer over to `<craft-pane>` and deletes
`Pane.vue`. `craft-pane` is registered globally by the `@craftcms/ui`
side-effect import in cp.ts, so no per-file import is needed.

Most of this is mechanical: `:padding="0"` becomes `padding="0"`,
`:title` becomes `:label`, and `<template #x>` slots become `slot="x"` on
a real direct child, since a native slot cannot be filled through a Vue
`<template>` wrapper.

Two consumers needed more than a tag swap:

  - ModalForm forwarded slots dynamically with `v-for (_, slotName) in
    $slots`, which native slots cannot express. Each pane region is now
    forwarded explicitly through a `display: contents` wrapper, rendered
    only when the parent fills that slot so the pane's child-presence
    check does not render empty header/footer chrome. Its four callers
    are unchanged.

  - Install.vue used `as="form"`. Since `craft-pane` cannot render as a
    `<form>`, the surrounding wrapper `<div>` becomes the `<form>`. The
    submit button remains a light-DOM descendant, so submission and
    `currentTarget.action` behave as before.

Panes that did not set `appearance` now take the `raised` default,
gaining a border and a shadow. The two `variant="code"` panes drop their
`tabindex="0"`, which the component now manages itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

📚 Storybook previews

@craftcms/uiopen Storybook

Changed components:

resources/jsopen Storybook

No changed components detected in this Storybook.

brianjhanson and others added 2 commits August 11, 2026 16:06
Adds CraftCms\Cms\Cp\Components\Pane, the server-side counterpart to the
<craft-pane> web component, registered as `pane` so it's reachable from the
ui() PHP helper and the `ui` Twig function.

The appearance (raised/outline/plain/sunken) and variant (plain/error/code)
value sets get their own PaneAppearance/PaneVariant enums rather than reusing
the shared Appearance/Variant enums: neither shared set contains a pane's
values, and widening them would make `appearance="raised"` silently acceptable
on every other component. `padding` is free-form (scale keyword, 0, a unitless
number, or any CSS length) so it is passed through unvalidated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	resources/js/pages/settings/assets/transforms/Index.vue
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

This PR had conflicts with 6.x. Claude attempted an automatic resolution and pushed a merge commit. Please review the merge commit carefully before approving/merging.

Claude's response

Merge completed and pushed.

Summary

One conflicted file: resources/js/pages/settings/assets/transforms/Index.vue.

Both sides touched the same template line for different reasons:

  • Ours (64d8d14) swapped <Pane appearance="raised" :padding="0"> for the <craft-pane appearance="raised" padding="0"> web component and dropped the now-unused Pane.vue import.
  • Theirs (7acf7c7) deleted a leftover hey hney debug string that sat immediately inside that same element.

These are independent and non-exclusive, so I composed them: kept the craft-pane migration and kept the debug-text removal. The closing </craft-pane> and the import removal had already auto-merged cleanly.

No ambiguity here — nothing needed a CONFLICT-REVIEW note. Verified no conflict markers remain anywhere in the repo and no unmerged paths are left.

@brandonkelly
brandonkelly merged commit e3a5712 into 6.x Aug 12, 2026
@brandonkelly
brandonkelly deleted the feature/craft-pane branch August 12, 2026 15:45
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.

2 participants