Repository navigation
feat: port accordion + tabs block families - #568
ViewFromTheBox merged 2 commits into
Conversation
Adds the artisanpack/accordions, accordion, accordion-title, accordion-body, tabs, and tab-section blocks plus three-renderer parity (Blade + React + Vue) and renderer-parity tests. Accordion family: - accordions parent owns the wrapper; accordion child owns the panelId + panelIcon, auto-generated from clientId on mount. - accordion-title / accordion-body are content-only — the accordion renderer walks its children and re-wraps them with the matching id / aria-controls / aria-labelledby / hidden wiring, so authors only ever set the panel id once. Tabs family: - tab-section owns its own label + tabId (both auto-fill on mount). - tabs renderer walks innerBlocks to build the tablist AND re-wraps each section's grandchildren with the resolved tabId, so trigger aria-controls always lines up with panel id. Adds positional fallback for missing tabIds and dedup for duplicates. Front-end: - Per-family CSS + a vanilla interactivity script ship from visual-editor-renderer-blade under public/vendor/.../frontend/, wired into the new <x-ve-blocks-styles /> emission. The interactivity script handles accordion toggle (click + Enter / Space) and tab switching (click + arrow keys + Home / End). - Editor canvas iframe also picks up the styles via canvas-styles with editor-only overrides that keep every panel / section expanded for editing. Renderer infrastructure: - BlockRenderer.php now passes the raw innerBlocks array to Blade partials (alongside the pre-rendered innerBlocksHtml) so partials can walk children when they need to derive parent state from them. Closes #497
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (5)
📝 WalkthroughWalkthroughThis PR adds artisanpack accordion and tabs block families across editor manifests and entrypoints, editor authoring components (auto-fill/slugging and inspector controls), Blade/React/Vue renderers with ID sanitization and deduplication, frontend accordion/tabs CSS and an interactivity JS runtime, registration/parity updates, and tests covering rendering and ARIA/id behavior. ChangesAccordion & Tabs Block Families
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/visual-editor-renderer-blade/src/BlockRenderer.php (1)
200-217:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winConsider passing
$innerBlocksthrough the dynamic block fallback path.When a dynamic block lookup fails (line 205) or throws an exception (line 216), the fallback to
renderStaticdoesn't receive the structured$innerBlocksarray because it's out of scope. Partials that depend on$innerBlocks(like the newaccordion.blade.php) will receive an empty array in these fallback scenarios, breaking features that iterate the structured block tree.To fix, pass
$innerBlockstorenderDynamicand forward it to the fallback calls:-protected function renderDynamic( string $name, array $attributes, string $innerBlocksHtml ): string +protected function renderDynamic( string $name, array $attributes, string $innerBlocksHtml, array $innerBlocks = [] ): string { $block = $this->dynamicBlocks->get( $name ); if ( null === $block ) { - return $this->renderStatic( $name, $attributes, $innerBlocksHtml ); + return $this->renderStatic( $name, $attributes, $innerBlocksHtml, $innerBlocks ); } try { $validated = $block->validateAttrs( $attributes ); $result = $block->render( $validated ); return $this->coerceToString( $result ); } catch ( Throwable $e ) { report( $e ); - return $this->renderStatic( $name, $attributes, $innerBlocksHtml ); + return $this->renderStatic( $name, $attributes, $innerBlocksHtml, $innerBlocks ); } }And update the call site at line 113:
-return $this->renderDynamic( $name, $attributes, $innerBlocksHtml ); +return $this->renderDynamic( $name, $attributes, $innerBlocksHtml, $innerBlocks );🤖 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/visual-editor-renderer-blade/src/BlockRenderer.php` around lines 200 - 217, The renderDynamic method currently lacks the structured $innerBlocks parameter, so when a dynamic block is missing or throws (in renderDynamic) the fallback renderStatic receives no $innerBlocks; update the renderDynamic signature to accept array $innerBlocks, pass that $innerBlocks through to both renderStatic fallback calls, and update the place that invokes renderDynamic (the call site that previously passed name, attributes, innerBlocksHtml) to supply the structured $innerBlocks argument in addition to existing args so partials like accordion.blade.php receive the correct structured block tree.
🧹 Nitpick comments (8)
packages/visual-editor-renderer-vue/src/blocks/artisanpack/accordion.ts (1)
116-116: 💤 Low valueConsider using boolean
truefor thehiddenattribute to align with Vue 3 best practices and React parity.The Vue renderer uses
hidden: ''while the React renderer useshidden: true(line 98 inaccordion.tsx). Both render correctly in their respective frameworks, but Vue 3.3+ recommends using actual boolean values for boolean attributes rather than empty strings (which is legacy Vue 2 style). For cross-renderer consistency and clarity, consider changing tohidden: true.♻️ Suggested alignment with React and Vue 3 best practices
const bodyAttrs: Record<string, unknown> = { class: 'ap-accordion__body', role: 'region', - hidden: '', + hidden: true, };🤖 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/visual-editor-renderer-vue/src/blocks/artisanpack/accordion.ts` at line 116, The accordion block sets hidden: '' using legacy string syntax; update the property to hidden: true to use a boolean (aligning with Vue 3+ and the React renderer) by locating the accordion block definition in packages/visual-editor-renderer-vue/src/blocks/artisanpack/accordion.ts and replacing the hidden property value from an empty string to the boolean true.resources/js/visual-editor/blocks/accordion/edit.tsx (1)
46-51: ⚡ Quick winConsider extracting the
slugifyfunction to a shared utility.The
slugifyfunction here is duplicated intab-section/edit.tsx(lines 33-38). Extracting it to a shared utility module would eliminate duplication and ensure consistent ID normalization across both block families.🤖 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 `@resources/js/visual-editor/blocks/accordion/edit.tsx` around lines 46 - 51, The local slugify implementation is duplicated; extract the slugify function into a shared utility module (e.g., export function slugify in a common utils file) and replace the local slugify implementations in both edit.tsx files to import and use that shared slugify; ensure you export the function with the same signature (slugify(value: string): string), update imports in the components that used the old local function, and run/type-check to confirm no breaking type issues.resources/js/visual-editor/blocks/tab-section/index.ts (1)
12-12: 💤 Low valueShared CSS import pattern noted.
Both
tabs/index.tsandtab-section/index.tsimporttabs.css. This is intentional co-location since the stylesheet defines styles for both the parent (.ap-tabs) and child (.ap-tab-section) blocks. The bundler (Vite) should deduplicate this import automatically.This is working as designed - no action required unless the team prefers to centralize CSS imports only at the parent block level.
🤖 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 `@resources/js/visual-editor/blocks/tab-section/index.ts` at line 12, The import of '../tabs/tabs.css' in tab-section/index.ts duplicates the same import present in tabs/index.ts; since Vite will deduplicate CSS this is acceptable, so no change is required, but if you prefer centralizing imports remove the import from tab-section/index.ts and ensure tabs/index.ts remains the single importer of 'tabs.css' so styles for .ap-tabs and .ap-tab-section are still bundled by Vite.resources/js/visual-editor/blocks/tabs/tabs.css (1)
83-83: ⚡ Quick winFix
currentColorcasing to satisfy stylelint rule.The stylelint
value-keyword-caserule requires lowercase keywords. WhilecurrentColoris valid CSS, the project's linting configuration expectscurrentcolor.🎨 Proposed fix
.ap-tabs__list a[aria-selected="true"], .ap-tabs__list button[aria-selected="true"] { font-weight: 600; - border-bottom: 2px solid currentColor; + border-bottom: 2px solid currentcolor; } .ap-tabs.align-tabs-vertical .ap-tabs__list a[aria-selected="true"], .ap-tabs.align-tabs-vertical .ap-tabs__list button[aria-selected="true"] { border-bottom: 0; - border-right: 2px solid currentColor; + border-right: 2px solid currentcolor; }Also applies to: 89-89
🤖 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 `@resources/js/visual-editor/blocks/tabs/tabs.css` at line 83, The CSS uses the keyword "currentColor" with incorrect casing for our stylelint rule; update the occurrences in tabs.css (e.g., the border-bottom declaration in the tabs block and the other occurrence noted around line 89) to use lowercase "currentcolor" so the value-keyword-case lint rule passes, ensuring you change every instance of currentColor to currentcolor (search for "currentColor" to catch all spots).Source: Linters/SAST tools
packages/visual-editor-renderer-blade/resources/assets/frontend/tabs.css (1)
74-74: ⚡ Quick winUse lowercase
currentcolorfor consistency.Stylelint reports that
currentColorshould becurrentcolor(lines 74, 79). While CSS color keywords are case-insensitive, lowercase is the conventional style.♻️ Apply the linter's suggested casing
.ap-tabs__list a[aria-selected="true"] { font-weight: 600; - border-bottom: 2px solid currentColor; + border-bottom: 2px solid currentcolor; } .ap-tabs.align-tabs-vertical .ap-tabs__list a[aria-selected="true"] { border-bottom: 0; - border-right: 2px solid currentColor; + border-right: 2px solid currentcolor; }Also applies to: 79-79
🤖 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/visual-editor-renderer-blade/resources/assets/frontend/tabs.css` at line 74, Replace the CSS color keyword casing for the two occurrences of "currentColor" used in the border rules (the lines with "border-bottom: 2px solid currentColor;") to the lowercase form "currentcolor" for consistency with the project's style/linter; update both occurrences (the one at the shown diff and the other reported) so the rule reads "border-bottom: 2px solid currentcolor;".packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/tab-section.blade.php (1)
4-7: ⚡ Quick winSanitize
tabIdfor HTML id validity (consistency with parent).Lines 4–7 cast
tabIdto string and applyhtmlspecialcharsfor attribute safety, but do not sanitize for HTMLidvalidity (e.g., removing spaces, non-alphanumeric characters). The parenttabs.blade.php(lines 27–32) uses asanitizeTabIdhelper that lowercases, replaces non-alphanumeric with hyphens, and trims. This inconsistency means a tab-section rendered outside a tabs parent could emit an invalididiftabIdcontains spaces or special characters.♻️ Proposed fix to align sanitization with parent
Extract a shared sanitization helper or inline the same logic here:
`@php` use ArtisanPackUI\VisualEditorRendererBlade\Support\BlockSupports; - $tabId = isset( $attributes['tabId'] ) ? (string) $attributes['tabId'] : ''; - // Escape so quotes / brackets in the persisted slug can't break the - // surrounding attribute. - $tabIdSafe = htmlspecialchars( $tabId, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); + $rawTabId = isset( $attributes['tabId'] ) ? (string) $attributes['tabId'] : ''; + + // Sanitize tabId for HTML id validity (mirror tabs.blade.php logic). + $slug = strtolower( $rawTabId ); + $slug = preg_replace( '/[^a-z0-9-]+/', '-', $slug ) ?? ''; + $slug = trim( $slug, '-' ); + $tabId = '' === $slug ? '' : $slug; + + $tabIdSafe = htmlspecialchars( $tabId, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); $idAttr = '' === $tabId ? '' : ' id="tabs-panel-' . $tabIdSafe . '"'; $labelledByAttr = '' === $tabId ? '' : ' aria-labelledby="tabs-tab-' . $tabIdSafe . '"'; `@endphp`🤖 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/visual-editor-renderer-blade/resources/views/blocks/artisanpack/tab-section.blade.php` around lines 4 - 7, The tab-section currently casts $tabId to string and only calls htmlspecialchars into $tabIdSafe, but needs the same ID normalization as the parent: apply the sanitizeTabId logic (lowercase, replace non-alphanumeric chars with hyphens, collapse/trim hyphens) before HTML-escaping so the produced id is valid; either call the existing sanitizeTabId helper used by tabs.blade.php or inline that normalization for $tabId then pass the result through htmlspecialchars to set $tabIdSafe.packages/visual-editor-renderer-blade/resources/assets/frontend/accordion.css (2)
40-40: ⚡ Quick winUse lowercase
currentcolorper CSS spec.Line 40 uses
currentColor, but CSS keyword values should be lowercase (currentcolor) per the CSS specification and common linting rules.🎨 Proposed fix
.ap-accordion__title-content:focus-visible { - outline: 2px solid currentColor; + outline: 2px solid currentcolor; outline-offset: -2px; }🤖 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/visual-editor-renderer-blade/resources/assets/frontend/accordion.css` at line 40, The CSS declaration "outline: 2px solid currentColor;" uses the wrong casing for the keyword; update the property value to use the lowercase CSS keyword "currentcolor" (i.e., replace currentColor with currentcolor) wherever that declaration appears (e.g., in the selector containing the outline rule) to comply with the CSS spec and linters.Source: Linters/SAST tools
58-58: ⚡ Quick winUse lowercase
currentcolorper CSS spec.Line 58 uses
currentColor, but CSS keyword values should be lowercase (currentcolor) per the CSS specification and common linting rules.🎨 Proposed fix
.ap-accordion__icon::before, .ap-accordion__icon::after { content: ""; position: absolute; - background: currentColor; + background: currentcolor; transition: transform 0.15s ease-out; }🤖 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/visual-editor-renderer-blade/resources/assets/frontend/accordion.css` at line 58, Replace the CSS keyword usage "currentColor" with the lowercase form "currentcolor" in the background declaration; locate the declaration background: currentColor; in the accordion.css (e.g., the rule that sets background: currentColor;) and change it to background: currentcolor; (and search for any other occurrences of "currentColor" to normalize them to lowercase).Source: Linters/SAST tools
🤖 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/visual-editor-renderer-blade/resources/views/blocks/artisanpack/tab-section.blade.php`:
- Around line 4-12: The template risks generating invalid or duplicate
id/aria-labelledby attributes: sanitize $tabId into a valid HTML id (e.g.,
normalize to lowercase, replace invalid chars with hyphens, ensure it starts
with a letter, and fallback to empty), then build $tabIdSafe from that
deterministic slug (not just htmlspecialchars) and only emit $idAttr and
$labelledByAttr when no id was already produced by BlockSupports::wrapperAttrs —
detect this by checking $attributes['id'] (or equivalent input key) before
adding id="tabs-panel-…" and aria-labelledby="tabs-tab-…"; update the code
around $tabId/$tabIdSafe and the logic that sets $idAttr/$labelledByAttr
accordingly.
In `@resources/js/visual-editor/blocks/accordion/accordion.css`:
- Line 39: The CSS uses the keyword "currentColor" with uppercase C which
triggers stylelint's value-keyword-case rule; update all occurrences (e.g., the
outline property in accordion.css where you see "outline: 2px solid
currentColor;") to the lowercase form "currentcolor" so the keyword is fully
lowercase and satisfies the linter.
---
Outside diff comments:
In `@packages/visual-editor-renderer-blade/src/BlockRenderer.php`:
- Around line 200-217: The renderDynamic method currently lacks the structured
$innerBlocks parameter, so when a dynamic block is missing or throws (in
renderDynamic) the fallback renderStatic receives no $innerBlocks; update the
renderDynamic signature to accept array $innerBlocks, pass that $innerBlocks
through to both renderStatic fallback calls, and update the place that invokes
renderDynamic (the call site that previously passed name, attributes,
innerBlocksHtml) to supply the structured $innerBlocks argument in addition to
existing args so partials like accordion.blade.php receive the correct
structured block tree.
---
Nitpick comments:
In
`@packages/visual-editor-renderer-blade/resources/assets/frontend/accordion.css`:
- Line 40: The CSS declaration "outline: 2px solid currentColor;" uses the wrong
casing for the keyword; update the property value to use the lowercase CSS
keyword "currentcolor" (i.e., replace currentColor with currentcolor) wherever
that declaration appears (e.g., in the selector containing the outline rule) to
comply with the CSS spec and linters.
- Line 58: Replace the CSS keyword usage "currentColor" with the lowercase form
"currentcolor" in the background declaration; locate the declaration background:
currentColor; in the accordion.css (e.g., the rule that sets background:
currentColor;) and change it to background: currentcolor; (and search for any
other occurrences of "currentColor" to normalize them to lowercase).
In `@packages/visual-editor-renderer-blade/resources/assets/frontend/tabs.css`:
- Line 74: Replace the CSS color keyword casing for the two occurrences of
"currentColor" used in the border rules (the lines with "border-bottom: 2px
solid currentColor;") to the lowercase form "currentcolor" for consistency with
the project's style/linter; update both occurrences (the one at the shown diff
and the other reported) so the rule reads "border-bottom: 2px solid
currentcolor;".
In
`@packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/tab-section.blade.php`:
- Around line 4-7: The tab-section currently casts $tabId to string and only
calls htmlspecialchars into $tabIdSafe, but needs the same ID normalization as
the parent: apply the sanitizeTabId logic (lowercase, replace non-alphanumeric
chars with hyphens, collapse/trim hyphens) before HTML-escaping so the produced
id is valid; either call the existing sanitizeTabId helper used by
tabs.blade.php or inline that normalization for $tabId then pass the result
through htmlspecialchars to set $tabIdSafe.
In `@packages/visual-editor-renderer-vue/src/blocks/artisanpack/accordion.ts`:
- Line 116: The accordion block sets hidden: '' using legacy string syntax;
update the property to hidden: true to use a boolean (aligning with Vue 3+ and
the React renderer) by locating the accordion block definition in
packages/visual-editor-renderer-vue/src/blocks/artisanpack/accordion.ts and
replacing the hidden property value from an empty string to the boolean true.
In `@resources/js/visual-editor/blocks/accordion/edit.tsx`:
- Around line 46-51: The local slugify implementation is duplicated; extract the
slugify function into a shared utility module (e.g., export function slugify in
a common utils file) and replace the local slugify implementations in both
edit.tsx files to import and use that shared slugify; ensure you export the
function with the same signature (slugify(value: string): string), update
imports in the components that used the old local function, and run/type-check
to confirm no breaking type issues.
In `@resources/js/visual-editor/blocks/tab-section/index.ts`:
- Line 12: The import of '../tabs/tabs.css' in tab-section/index.ts duplicates
the same import present in tabs/index.ts; since Vite will deduplicate CSS this
is acceptable, so no change is required, but if you prefer centralizing imports
remove the import from tab-section/index.ts and ensure tabs/index.ts remains the
single importer of 'tabs.css' so styles for .ap-tabs and .ap-tab-section are
still bundled by Vite.
In `@resources/js/visual-editor/blocks/tabs/tabs.css`:
- Line 83: The CSS uses the keyword "currentColor" with incorrect casing for our
stylelint rule; update the occurrences in tabs.css (e.g., the border-bottom
declaration in the tabs block and the other occurrence noted around line 89) to
use lowercase "currentcolor" so the value-keyword-case lint rule passes,
ensuring you change every instance of currentColor to currentcolor (search for
"currentColor" to catch all spots).
🪄 Autofix (Beta)
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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 733fb8ee-70aa-4ea6-bb48-1f178fe5045f
📒 Files selected for processing (51)
packages/renderer-parity.jsonpackages/visual-editor-renderer-blade/resources/assets/frontend/accordion.csspackages/visual-editor-renderer-blade/resources/assets/frontend/interactivity.jspackages/visual-editor-renderer-blade/resources/assets/frontend/tabs.csspackages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/accordion-body.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/accordion-title.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/accordion.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/accordions.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/tab-section.blade.phppackages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/tabs.blade.phppackages/visual-editor-renderer-blade/resources/views/components/blocks-styles.blade.phppackages/visual-editor-renderer-blade/src/BlockRenderer.phppackages/visual-editor-renderer-blade/src/View/Components/BlocksStylesComponent.phppackages/visual-editor-renderer-blade/src/VisualEditorRendererBladeServiceProvider.phppackages/visual-editor-renderer-blade/tests/Unit/BlockRendererTest.phppackages/visual-editor-renderer-react/src/blocks/artisanpack/accordion.tsxpackages/visual-editor-renderer-react/src/blocks/artisanpack/tabs.tsxpackages/visual-editor-renderer-react/src/blocks/registerCoreBlocks.tspackages/visual-editor-renderer-react/tests/BlockTree.test.tsxpackages/visual-editor-renderer-vue/src/blocks/artisanpack/accordion.tspackages/visual-editor-renderer-vue/src/blocks/artisanpack/tabs.tspackages/visual-editor-renderer-vue/src/blocks/registerCoreBlocks.tspackages/visual-editor-renderer-vue/tests/BlockTree.test.tsresources/js/visual-editor/blocks/accordion-body/block.jsonresources/js/visual-editor/blocks/accordion-body/edit.tsxresources/js/visual-editor/blocks/accordion-body/index.tsresources/js/visual-editor/blocks/accordion-body/save.tsxresources/js/visual-editor/blocks/accordion-title/block.jsonresources/js/visual-editor/blocks/accordion-title/edit.tsxresources/js/visual-editor/blocks/accordion-title/index.tsresources/js/visual-editor/blocks/accordion-title/save.tsxresources/js/visual-editor/blocks/accordion/accordion.cssresources/js/visual-editor/blocks/accordion/block.jsonresources/js/visual-editor/blocks/accordion/edit.tsxresources/js/visual-editor/blocks/accordion/index.tsresources/js/visual-editor/blocks/accordion/save.tsxresources/js/visual-editor/blocks/accordions/block.jsonresources/js/visual-editor/blocks/accordions/edit.tsxresources/js/visual-editor/blocks/accordions/index.tsresources/js/visual-editor/blocks/accordions/save.tsxresources/js/visual-editor/blocks/tab-section/block.jsonresources/js/visual-editor/blocks/tab-section/edit.tsxresources/js/visual-editor/blocks/tab-section/index.tsresources/js/visual-editor/blocks/tab-section/save.tsxresources/js/visual-editor/blocks/tabs/block.jsonresources/js/visual-editor/blocks/tabs/edit.tsxresources/js/visual-editor/blocks/tabs/index.tsresources/js/visual-editor/blocks/tabs/save.tsxresources/js/visual-editor/blocks/tabs/tabs.cssresources/js/visual-editor/editor/__tests__/canvas-styles.test.tsresources/js/visual-editor/editor/canvas-styles.ts
- Lowercase the `currentcolor` keyword in accordion + tabs CSS to satisfy stylelint's value-keyword-case rule. - Slugify $tabId in the standalone tab-section partial so emitted id / aria-labelledby attributes are always valid HTML ids even when the persisted slug carries characters outside [a-z0-9-].
3d71d66
into
feature/495-port-crosswinds-blocks
Description
Adds six new
artisanpack/*blocks covering two interactive families — accordion + tabs — with three-renderer parity (Blade + React + Vue), front-end CSS, and a vanilla interactivity script.Closes: #497
Type of Change
Related Issue
Issue: #497
Motivation and Context
Phase CW1 of the broader port to first-party ArtisanPack UI blocks (umbrella #495). These two families exercise the parent / child / grandchild inner-block pattern (
accordions→accordion→accordion-title+accordion-body) and the parent / child pattern (tabs→tab-section), plus JS-driven interactivity. Both families ship as first-party blocks underartisanpack/*— the upstream attribution lives in the umbrella issue, not the block code itself.Changes Made
Accordion family
accordionsparent owns the wrapper;accordionchild owns thepanelId+panelIcon, auto-generated from the clientId on mount.accordion-titleandaccordion-bodyare content-only — theaccordionrenderer walks its children and re-wraps them with matchingid/aria-controls/aria-labelledby/hiddenwiring, so authors only ever set the panel id once.Tabs family
tab-sectionowns its ownlabel+tabId(both auto-fill on mount: label asTab N, tabId astab-<clientId-suffix>).tabsrenderer walksinnerBlocksto build the tablist AND re-wraps each section's grandchildren with the resolved tabId, so triggeraria-controlsalways matches panelid. Includes positional fallback for missing tabIds and dedup for duplicate slugs.Renderer infrastructure
BlockRenderer.phpnow passes the rawinnerBlocksarray to Blade partials alongside the pre-renderedinnerBlocksHtmlso partials can walk children when they need to derive parent state.Front-end + editor canvas
visual-editor-renderer-bladeunderpublic/vendor/.../frontend/, wired into the new<x-ve-blocks-styles />emission.aria-selected. Re-binds onwindow.dispatchEvent(new Event('ap:rebind'))for SPA navigation.canvas-styleswith editor-only overrides that keep every panel / section expanded for editing.How Has This Been Tested?
Testing Environment:
feature/495-port-crosswinds-blocksHEADTests Performed:
useSelectAccessibility Tests Run
Details: Accordion title is
role="button"witharia-expandedtoggling on Enter / Space; body isrole="region"witharia-labelledbypointing at the title. Tabs userole="tablist"/role="tab"/role="tabpanel"with full keyboard support (Tab, arrow keys, Home, End). Color contrast and screen-reader testing left for a dedicated accessibility audit pass.Tests Added
Test details: 6 new Blade tests, 7 new React tests, 7 new Vue tests covering happy-path rendering, safe-default fallbacks for invalid enums, empty-id edge cases, positional auto-fill, and tabId dedup. All 1031 Pest tests + 1915 vitest tests + renderer-parity check green.
Documentation
Documentation details: Each new block, renderer, and helper carries a file-level docblock explaining intent + parent/child relationship. Editor-side comments call out non-obvious patterns (e.g., why the accordion renderer reaches into pre-rendered children to re-wrap them).
Screenshots
Editor + front-end screenshots captured during local manual testing; see the issue thread for a record.
Pre-Submission Checklist
Summary by CodeRabbit