[6.x] Consolidate Pane and craft-pane into a single web component - #19398
Conversation
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>
📚 Storybook previews@craftcms/ui — open Storybook Changed components: resources/js — open Storybook No changed components detected in this Storybook. |
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
|
This PR had conflicts with Claude's response
|
Description
The CP had two pane implementations: a thin
craft-paneLit element with only a default slot, and a richer VuePane.vuewith header/footer regions, variants and a padding scale. This folds both into a singlecraft-paneweb component, migrates all 48<Pane>consumers, and deletesPane.vue.The unified API keeps the Vue prop names, which carried the richer semantics and the larger consumer base:
appearanceraised|outline|plain|sunkenraisedvariantplain|error|codeplainpaddingsm/md/lg/xl,0, a number, or any CSS lengthlglabel<h1>in the headerappearanceandvariantstay orthogonal, so a code pane can also be outlined.sizeis replaced bypadding, andasis dropped. Header and footer regions render only when their slots are filled, following thecraft-cardpattern.Two changes worth calling out for review:
titlebecamelabel. A pane is a large surface, so a nativetitleattribute would fire a browser tooltip anywhere inside it and be announced over the visible heading. This also matchescraft-card.appearancenow take theraiseddefault, 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-heightused to sit on the same element that consumers puttabindex="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-wiretabindex.ModalFormforwarded slots dynamically withv-for (_, slotName) in $slots, which native slots can't express; each pane region is now forwarded explicitly through adisplay: contentswrapper, rendered only when the parent fills that slot.Install.vueusedas="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