FEATURE introducing the tab and tab-group component

This commit is contained in:
Antoine M 2026-03-20 17:10:34 +01:00
parent b387694f07
commit 67ccfdee1f
15 changed files with 466 additions and 0 deletions

View File

@ -0,0 +1,25 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "carhop-blocks/tab-group",
"version": "0.1.0",
"title": "Tab Group",
"category": "carhop-blocks",
"icon": "smiley",
"description": "Tab Group pour la mise en forme supérieure d'éléments de contenu",
"example": {},
"supports": {
"html": false,
"color": {
"text": true,
"background": false,
"link": false
}
},
"textdomain": "tab-group",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"viewScript": "file:./view.js",
"render": "file:./render.php"
}

View File

@ -0,0 +1,54 @@
import { __ } from "@wordpress/i18n";
import { useBlockProps, InnerBlocks } from "@wordpress/block-editor";
import { useSelect } from "@wordpress/data";
import "./editor.scss";
export default function Edit({ attributes, setAttributes, clientId }) {
const blockProps = useBlockProps({
className: "tab-group",
});
const tabs = useSelect(
(select) => {
const block = select("core/block-editor").getBlock(clientId);
if (!block?.innerBlocks) return [];
return block.innerBlocks.map((innerBlock, index) => ({
id: `tab-${index + 1}`,
panelId: `tabpanel-${index + 1}`,
title: innerBlock.attributes?.title || __("Sans titre", "tab-group"),
iconUrl: innerBlock.attributes?.iconUrl || "",
}));
},
[clientId],
);
return (
<section {...blockProps}>
<div className="tab-group__toolbar">
<div role="tablist" aria-labelledby="tablist-1" className="tablist">
{tabs.map((tab, index) => (
<button
key={tab.id}
id={tab.id}
type="button"
role="tab"
aria-selected={index === 0}
aria-controls={tab.panelId}
tabIndex={index === 0 ? 0 : -1}
data-tab={index}
>
{tab.iconUrl && (
<img src={tab.iconUrl} alt="" className="tab__icon" aria-hidden />
)}
<span>{tab.title}</span>
</button>
))}
</div>
</div>
<InnerBlocks
allowedBlocks={["carhop-blocks/tab"]}
template={[["carhop-blocks/tab"]]}
/>
</section>
);
}

View File

@ -0,0 +1,9 @@
/**
* The following styles get applied inside the editor only.
*
* Replace them with your own styles or remove the file completely.
*/
.wp-block-create-block-chapo {
border: 1px dotted #f00;
}

View File

@ -0,0 +1,26 @@
import { registerBlockType } from "@wordpress/blocks";
import "./style.scss";
import Edit from "./edit";
import save from "./save";
import metadata from "./block.json";
registerBlockType(metadata.name, {
icon: {
src: (
<svg
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
viewBox="0 0 64 64"
>
<path
d="M46,10H8c-2.1,0-4.2.8-5.7,2.3s-2.3,3.5-2.3,5.7v38c0,2.1.8,4.2,2.3,5.7s3.5,2.3,5.7,2.3h38c2.1,0,4.2-.8,5.7-2.3s2.3-3.5,2.3-5.7V18c0-2.1-.8-4.2-2.3-5.7s-3.5-2.3-5.7-2.3ZM25.8,18.3c.5-.5,1.3-.8,2-.8h0c1.1,0,2.2.7,2.6,1.8.4,1.1.2,2.3-.6,3.1s-2,1.1-3.1.6c-1.1-.4-1.7-1.5-1.8-2.6,0-.8.3-1.5.8-2ZM19.5,17.5c1.1,0,2.2.7,2.6,1.8.4,1.1.2,2.3-.6,3.1s-2,1.1-3.1.6c-1.1-.4-1.7-1.5-1.8-2.6,0-1.6,1.3-2.8,2.8-2.8ZM11.2,17.5c1.6,0,2.8,1.3,2.8,2.8s-1.3,2.8-2.8,2.8-2.8-1.3-2.8-2.8,1.3-2.8,2.8-2.8ZM50,56c0,2.2-1.8,4-4,4H8c-2.2,0-4-1.8-4-4v-25.3h46v25.3ZM61.7,2.3C60.2.8,58.1,0,56,0H18C15.9,0,13.8.8,12.3,2.3s-2.3,3.5-2.3,5.7h36c5.5,0,10,4.5,10,10v36c2.1,0,4.2-.8,5.7-2.3s2.3-3.5,2.3-5.7V8c0-2.1-.8-4.2-2.3-5.7Z"
fill="#146E63"
/>
</svg>
),
},
edit: Edit,
save,
});

View File

@ -0,0 +1,48 @@
<?php
$wrapper_attributes = get_block_wrapper_attributes(['class' => 'tab-group']);
$inner_blocks = $block->parsed_block['innerBlocks'] ?? [];
// Extraire les titres et icônes des blocs tab pour les boutons
$tabs = array_map(function ($inner_block) {
return [
'title' => $inner_block['attrs']['title'] ?? __('Sans titre', 'tab-group'),
'iconUrl' => $inner_block['attrs']['iconUrl'] ?? '',
];
}, array_filter($inner_blocks, fn($b) => ($b['blockName'] ?? '') === 'carhop-blocks/tab'));
?>
<section <?php echo $wrapper_attributes; ?>>
<div class="tab-group__toolbar">
<div role="tablist" aria-label="<?php esc_attr_e('Onglets', 'tab-group'); ?>" class="tablist">
<?php foreach ($tabs as $index => $tab) : ?>
<button
type="button"
role="tab"
aria-selected="<?php echo $index === 0 ? 'true' : 'false'; ?>"
aria-controls="tabpanel-<?php echo esc_attr($index + 1); ?>"
tabindex="<?php echo $index === 0 ? '0' : '-1'; ?>"
data-tab="<?php echo esc_attr($index); ?>">
<?php if (!empty($tab['iconUrl'])) : ?>
<img src="<?php echo esc_url($tab['iconUrl']); ?>" alt="" class="tab__icon" aria-hidden />
<?php endif; ?>
<span><?php echo esc_html($tab['title']); ?></span>
</button>
<?php endforeach; ?>
</div>
</div>
<div class="tab-group__innerblocks">
<?php foreach ($inner_blocks as $index => $inner_block) : ?>
<div class="wp-block-carhop-blocks-tab tab" id="tabpanel-<?php echo esc_attr($index + 1); ?>" role="tabpanel" data-active="<?php echo $index === 0 ? 'true' : 'false'; ?>">
<?php echo render_block($inner_block); ?>
</div>
<?php endforeach; ?>
</div>
</section>

View File

@ -0,0 +1,6 @@
import { useBlockProps } from "@wordpress/block-editor";
import { InnerBlocks } from "@wordpress/block-editor";
export default function save() {
return <InnerBlocks.Content />;
}

View File

@ -0,0 +1,36 @@
.tab__title {
margin-bottom: 2rem;
}
.tablist {
button {
gap: 10px !important;
}
button[aria-selected="false"] {
img {
filter: grayscale(100%);
}
}
}
.tab-group__toolbar {
margin-bottom: 2rem;
}
.tablist .tab__icon {
--iconSize: 1.5rem;
width: var(--iconSize);
height: var(--iconSize);
// background: blue;
object-fit: contain;
object-position: center;
vertical-align: middle;
}
.wp-block-carhop-blocks-tab[data-active="false"] {
display: none;
}
.wp-block-carhop-blocks-tab[data-active="true"] {
display: block !important;
}

View File

@ -0,0 +1,34 @@
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".tab-group").forEach((TabGroup) => {
const toolbar = TabGroup.querySelector(".tab-group__toolbar");
const tabs = toolbar.querySelectorAll("button");
function setActiveTab(currentTab) {
tabs.forEach((tab) => {
tab.setAttribute("aria-selected", "false");
});
currentTab.setAttribute("aria-selected", "true");
}
function setActiveTabPanel(currentTabButton) {
const currentTabPanelId = currentTabButton.getAttribute("aria-controls");
const currentTabPanel = TabGroup.querySelector(`#${currentTabPanelId}`);
console.log(currentTabPanel);
hideAllTabPanels();
currentTabPanel.setAttribute("data-active", "true");
}
function hideAllTabPanels() {
const tabPanels = TabGroup.querySelectorAll(
".tab-group__innerblocks .tab",
);
tabPanels.forEach((tabPanel) => {
tabPanel.setAttribute("data-active", "false");
});
}
tabs.forEach((tab) => {
tab.addEventListener("click", () => {
setActiveTab(tab);
setActiveTabPanel(tab);
});
});
});
});

View File

@ -0,0 +1,30 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "carhop-blocks/tab",
"version": "0.1.0",
"title": "Tab",
"category": "carhop-blocks",
"icon": "smiley",
"description": "Tab pour la mise en forme supérieure d'éléments de contenu",
"example": {},
"supports": {
"html": false,
"color": {
"text": true,
"background": false,
"link": false
}
},
"textdomain": "tab",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"viewScript": "file:./view.js",
"attributes": {
"title": {
"type": "string",
"default": ""
}
}
}

View File

@ -0,0 +1,109 @@
import { __ } from "@wordpress/i18n";
import {
useBlockProps,
RichText,
InnerBlocks,
InspectorControls,
MediaUpload,
MediaUploadCheck,
} from "@wordpress/block-editor";
import "./editor.scss";
import { PanelBody, TextControl, Button } from "@wordpress/components";
export default function Edit({ attributes, setAttributes }) {
const { title, iconId, iconUrl } = attributes;
const blockProps = useBlockProps({
className: "block-chapo",
});
return (
<>
<InspectorControls>
<PanelBody title={__("Tab", "carhop-blocks")}>
<TextControl
label={__("Titre", "carhop-blocks")}
value={title}
onChange={(value) => setAttributes({ title: value })}
/>
</PanelBody>
<PanelBody title={__("Icône", "carhop-blocks")}>
<MediaUploadCheck>
<MediaUpload
onSelect={(media) =>
setAttributes({
iconId: media.id,
iconUrl: media.url,
})
}
allowedTypes={["image"]}
value={iconId}
render={({ open }) => (
<>
{iconUrl ? (
<div className="tab__icon-preview">
<img src={iconUrl} alt="" style={{ maxWidth: 48, height: "auto" }} />
<div style={{ marginTop: 8 }}>
<Button variant="secondary" onClick={open} style={{ marginRight: 8 }}>
{__("Remplacer", "carhop-blocks")}
</Button>
<Button
variant="tertiary"
isDestructive
onClick={() =>
setAttributes({ iconId: 0, iconUrl: "" })
}
>
{__("Supprimer", "carhop-blocks")}
</Button>
</div>
</div>
) : (
<Button variant="secondary" onClick={open}>
{__("Choisir une image", "carhop-blocks")}
</Button>
)}
</>
)}
/>
</MediaUploadCheck>
</PanelBody>
</InspectorControls>
<div {...blockProps}>
<RichText
tagName="h2"
className="tab__title"
placeholder="Titre"
value={title}
onChange={(value) => setAttributes({ title: value })}
/>
<InnerBlocks
allowedBlocks={[
"core/heading",
"core/paragraph",
"core/list",
"core/button",
"core/buttons",
"core/image",
"core/embed",
"core/quote",
"core/pullquote",
"core/media-text",
"core/table",
"core/group",
"core/columns",
"core/post-title",
"carhop-blocks/cta",
"carhop-blocks/heading",
"carhop-blocks/cta-group",
"carhop-blocks/audio-player",
"carhop-blocks/content-box",
"carhop-blocks/notice-panel",
"shortcode",
]}
template={[["core/paragraph", { content: "Contenu" }]]}
/>
</div>
</>
);
}

View File

@ -0,0 +1,9 @@
/**
* The following styles get applied inside the editor only.
*
* Replace them with your own styles or remove the file completely.
*/
.wp-block-create-block-chapo {
border: 1px dotted #f00;
}

View File

@ -0,0 +1,26 @@
import { registerBlockType } from "@wordpress/blocks";
import "./style.scss";
import Edit from "./edit";
import save from "./save";
import metadata from "./block.json";
registerBlockType(metadata.name, {
icon: {
src: (
<svg
xmlns="http://www.w3.org/2000/svg"
width="83.3"
height="83.3"
viewBox="0 0 83.3 83.3"
>
<path
fill="#146E63"
d="M72.7,0H10.7C4.8,0,0,4.8,0,10.7v62c0,5.9,4.8,10.7,10.7,10.7h62c5.9,0,10.7-4.8,10.7-10.7V10.7c0-5.9-4.8-10.7-10.7-10.7ZM10.7,5.8h62c2.7,0,4.8,2.2,4.8,4.8v8.7H5.8v-8.7c0-2.7,2.2-4.8,4.8-4.8ZM72.7,77.5H10.7c-2.7,0-4.8-2.2-4.8-4.8V25.2h71.7v47.5c0,1.3-.5,2.5-1.4,3.4s-2.1,1.4-3.4,1.4h0ZM9.7,12.5c0-1.5,1.3-2.7,2.8-2.8h19.5c.8-.1,1.7.2,2.3.7.6.6,1,1.3,1,2.2s-.4,1.6-1,2.2c-.6.6-1.5.8-2.3.7H12.5c-.8,0-1.5-.4-2-.9-.5-.6-.8-1.3-.8-2.1h0Z"
/>
</svg>
),
},
edit: Edit,
save,
});

View File

@ -0,0 +1,17 @@
import { useBlockProps } from "@wordpress/block-editor";
import { InnerBlocks } from "@wordpress/block-editor";
import { RichText } from "@wordpress/block-editor";
export default function save({ attributes }) {
const { title } = attributes;
const blockProps = useBlockProps.save({
className: "tab",
});
return (
<>
<RichText.Content value={title} tagName="h2" className="tab__title" />
<InnerBlocks.Content />
</>
);
}

View File

@ -0,0 +1,12 @@
/**
* The following styles get applied both on the front of your site
* and in the editor.
*
* Replace them with your own styles or remove the file completely.
*/
.wp-block-create-block-chapo {
background-color: #21759b;
color: #fff;
padding: 2px;
}

View File

@ -0,0 +1,25 @@
/**
* Use this file for JavaScript code that you want to run in the front-end
* on posts/pages that contain this block.
*
* When this file is defined as the value of the `viewScript` property
* in `block.json` it will be enqueued on the front end of the site.
*
* Example:
*
* ```js
* {
* "viewScript": "file:./view.js"
* }
* ```
*
* If you're not making any changes to this file because your project doesn't need any
* JavaScript running in the front-end, then you should delete this file and remove
* the `viewScript` property from `block.json`.
*
* @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#view-script
*/
/* eslint-disable no-console */
console.log( 'Hello World! (from create-block-chapo block)' );
/* eslint-enable no-console */