diff --git a/blocks/all.php b/blocks/all.php
index c973909..9f3aa9e 100644
--- a/blocks/all.php
+++ b/blocks/all.php
@@ -43,5 +43,8 @@ function ( array $categories ) {
// Load optional blocks.
require_once plugin_dir_path( __FILE__ ) . 'legacy/header/class-header-block.php';
+require_once plugin_dir_path( __FILE__ ) . 'site-header/class-site-header-block.php';
+require_once plugin_dir_path( __FILE__ ) . 'desktop-menu/class-desktop-menu-block.php';
+require_once plugin_dir_path( __FILE__ ) . 'mobile-menu/class-mobile-menu-block.php';
require_once plugin_dir_path( __FILE__ ) . 'post-listing/class-post-listing-block.php';
require_once plugin_dir_path( __FILE__ ) . 'search-and-filter/class-search-and-filter-block.php';
diff --git a/blocks/desktop-menu/assets/_desktop-menu.scss b/blocks/desktop-menu/assets/_desktop-menu.scss
new file mode 100644
index 0000000..48ff7f9
--- /dev/null
+++ b/blocks/desktop-menu/assets/_desktop-menu.scss
@@ -0,0 +1,23 @@
+@use '/scss/globals';
+
+@mixin render {
+ .desktop-menu__wrapper {
+ & > ul {
+ display: flex;
+ flex-wrap: wrap;
+ gap: globals.$space-vertical globals.$space-horizontal;
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+
+ & > li {
+ margin: 0;
+ padding: 0;
+
+ & > a {
+ display: block;
+ }
+ }
+ }
+ }
+}
diff --git a/blocks/desktop-menu/class-desktop-menu-block.php b/blocks/desktop-menu/class-desktop-menu-block.php
new file mode 100644
index 0000000..e31e64b
--- /dev/null
+++ b/blocks/desktop-menu/class-desktop-menu-block.php
@@ -0,0 +1,69 @@
+ ';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function name(): string {
+ return 'desktop-menu';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function label(): string {
+ return 'Desktop Menu';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function fields(): array {
+ return array(
+ array(
+ 'key' => 'field_desktop_menu_block_menu_location',
+ 'name' => 'menu_location',
+ 'label' => 'Menu',
+ 'type' => 'select',
+ 'choices' => $this->get_menu_choices(),
+ ),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function template(): string {
+ return __DIR__ . '/templates/block.php';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function use_default_wrapper_template(): bool {
+ return false;
+ }
+}
diff --git a/blocks/desktop-menu/templates/block.php b/blocks/desktop-menu/templates/block.php
new file mode 100644
index 0000000..8768c7f
--- /dev/null
+++ b/blocks/desktop-menu/templates/block.php
@@ -0,0 +1,26 @@
+get_field( 'menu_location' );
+?>
+
+
diff --git a/blocks/mobile-menu/assets/_mobile-menu.scss b/blocks/mobile-menu/assets/_mobile-menu.scss
new file mode 100644
index 0000000..dbe1294
--- /dev/null
+++ b/blocks/mobile-menu/assets/_mobile-menu.scss
@@ -0,0 +1,75 @@
+@use '/scss/globals';
+
+@mixin render {
+ .mobile-menu__wrapper {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ padding: globals.$space-vertical globals.$space-horizontal;
+ }
+
+ .mobile-menu__toggle-wrapper {
+ display: flex;
+ justify-content: flex-end;
+ padding-bottom: globals.$space-vertical;
+ }
+
+ .mobile-menu__toggle {
+ display: block;
+ position: relative;
+ width: 40px;
+ height: 40px;
+ overflow: hidden;
+ background-color: grey;
+ border: none;
+ font-size: 0;
+ text-indent: 100px;
+ white-space: nowrap;
+ cursor: pointer;
+
+ &::after {
+ content: '';
+ display: block;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 20px;
+ height: 20px;
+ background-color: black;
+ mask-image: url('data:image/svg+xml,%3Csvg%20width%3D%2238%22%20height%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M%207.1210938%200.7265625%20L%203.7265625%204.1210938%20L%2015.605469%2016%20L%203.7265625%2027.878906%20L%207.1210938%2031.273438%20L%2019%2019.394531%20L%2030.878906%2031.273438%20L%2034.273438%2027.878906%20L%2022.394531%2016%20L%2034.273438%204.1210938%20L%2030.878906%200.7265625%20L%2019%2012.605469%20L%207.1210938%200.7265625%20z%20%22%20%2F%3E%3C%2Fsvg%3E');
+ mask-size: 100%;
+ mask-position: center;
+ mask-repeat: no-repeat;
+ transform: translate(-50%, -50%);
+ }
+ }
+
+ .mobile-menu__inner {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ margin-bottom: calc(globals.$space-bottom * -1);
+
+ & > * {
+ margin-bottom: globals.$space-bottom;
+ }
+
+ ul {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ gap: globals.$space-vertical globals.$space-horizontal;
+ list-style-type: none;
+
+ & > li {
+ margin: 0;
+ padding: 0;
+
+ & > a {
+ display: block;
+ }
+ }
+ }
+ }
+}
diff --git a/blocks/mobile-menu/assets/mobile-menu.js b/blocks/mobile-menu/assets/mobile-menu.js
new file mode 100644
index 0000000..f5076c1
--- /dev/null
+++ b/blocks/mobile-menu/assets/mobile-menu.js
@@ -0,0 +1,74 @@
+/**
+ * @class Mobile_Menu
+ * @classdesc
+ * Manages the behavior of a mobile menu component.
+ * Provides the ability to close the menu via the internal toggle element.
+ * Triggers a 'mobile_menu_toggle' event, which can be handled externally to show or hide the menu.
+ */
+class Mobile_Menu {
+
+ /**
+ * Stores references to relevant DOM elements for the mobile menu.
+ * @type {Object}
+ * @property {jQuery} wrapper - The top-level menu wrapper element.
+ * @property {jQuery} toggle - The toggle element used to trigger menu open/close.
+ */
+ elements = {};
+
+ /**
+ * Creates an instance of Mobile_Menu.
+ * @param {jQuery} wrapper - The jQuery object representing the menu's wrapper element.
+ */
+ constructor(wrapper) {
+ this.loadElements(wrapper); // Cache relevant DOM elements.
+ this.initializeToggle(); // Attach event listeners to the toggle.
+ }
+
+ /**
+ * Caches references to important DOM elements within the menu wrapper.
+ *
+ * @param {jQuery} wrapper - The menu's outer wrapper element.
+ */
+ loadElements(wrapper) {
+ this.elements.wrapper = wrapper;
+ this.elements.toggle = wrapper.find('.mobile-menu__toggle');
+ }
+
+ /**
+ * Sets up the event listener for the toggle element.
+ * When clicked, it calls the toggle() method to trigger the menu event.
+ */
+ initializeToggle() {
+ this.elements.toggle.on(
+ 'click',
+ () => {
+ this.toggle();
+ }
+ );
+ }
+
+ /**
+ * Triggers a global event to toggle the mobile menu's state.
+ * Separate scripts should listen for the 'mobile_menu_toggle' event and handle show/hide logic.
+ */
+ toggle() {
+ jQuery(window).trigger('mobile_menu_toggle');
+ }
+}
+
+jQuery(document).ready(
+ () => {
+ // Store all Mobile_Menu instances globally for potential later access.
+ window.mobileMenu = [];
+
+ // Find all menu wrappers on the page.
+ let wrappers = jQuery('.mobile-menu__wrapper');
+
+ // Instantiate a Mobile_Menu controller for each wrapper found.
+ wrappers.each(
+ (index) => {
+ window.mobileMenu.push(new Mobile_Menu(wrappers.eq(index)));
+ }
+ );
+ }
+);
diff --git a/blocks/mobile-menu/class-mobile-menu-block.php b/blocks/mobile-menu/class-mobile-menu-block.php
new file mode 100644
index 0000000..bc8f61b
--- /dev/null
+++ b/blocks/mobile-menu/class-mobile-menu-block.php
@@ -0,0 +1,106 @@
+ 'field_mobile_menu_block_menu_location',
+ 'name' => 'menu_location',
+ 'label' => 'Menu',
+ 'type' => 'select',
+ 'choices' => $this->get_menu_choices(),
+ ),
+ ),
+ __DIR__ . '/templates/menu.php',
+ array(),
+ 'menu'
+ ),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function template(): string {
+ return __DIR__ . '/templates/block.php';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function use_default_wrapper_template(): bool {
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setup(): bool {
+ $this->register_script();
+ return parent::setup();
+ }
+
+ /**
+ * Registers the mobile-menu JavaScript file.
+ */
+ protected function register_script() {
+ add_action(
+ 'wp_enqueue_scripts',
+ function () {
+ wp_register_script(
+ 'mobile_menu',
+ plugin_dir_url( __FILE__ ) . 'assets/mobile-menu.js',
+ array(
+ 'jquery',
+ ),
+ 1,
+ true
+ );
+ }
+ );
+ }
+}
diff --git a/blocks/mobile-menu/templates/block.php b/blocks/mobile-menu/templates/block.php
new file mode 100644
index 0000000..12ddabd
--- /dev/null
+++ b/blocks/mobile-menu/templates/block.php
@@ -0,0 +1,42 @@
+
+
+
diff --git a/blocks/mobile-menu/templates/menu.php b/blocks/mobile-menu/templates/menu.php
new file mode 100644
index 0000000..5f1f9ac
--- /dev/null
+++ b/blocks/mobile-menu/templates/menu.php
@@ -0,0 +1,26 @@
+get_field( 'menu_location' );
+?>
+
+
diff --git a/blocks/site-header/assets/_site-header.scss b/blocks/site-header/assets/_site-header.scss
new file mode 100644
index 0000000..c89b3f5
--- /dev/null
+++ b/blocks/site-header/assets/_site-header.scss
@@ -0,0 +1,174 @@
+@use '/scss/globals';
+@use '/scss/extenders/container';
+
+@mixin render {
+ .site-header__outer-wrapper {
+ @extend %container__outer-wrapper;
+ }
+
+ .site-header__wrapper {
+ @extend %container__wrapper;
+ opacity: 0;
+ }
+
+ .site-header__wrapper--active {
+ opacity: 1;
+ }
+
+ .site-header__inner {
+ @extend %container__inner;
+ }
+
+ .site-header__sections {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: globals.$space-vertical globals.$space-horizontal;
+
+ & > *:first-child {
+ margin-right: auto;
+ }
+ }
+
+ .site-header__section--device-visibility-mobile {
+ @media only screen and (min-width: globals.$breakpoint-medium) {
+ display: none;
+ }
+ }
+
+ .site-header__section--device-visibility-desktop {
+ display: none;
+
+ @media only screen and (min-width: globals.$breakpoint-medium) {
+ display: initial;
+ }
+ }
+
+ .site-header__section--desktop-menu {
+ display: none;
+
+ @media only screen and (min-width: globals.$breakpoint-medium) {
+ display: unset;
+ }
+ }
+
+ .site-header__section--mobile-menu-toggle {
+ @media only screen and (min-width: globals.$breakpoint-medium) {
+ display: none;
+ }
+ }
+
+ .site-header__logo {
+ a,
+ img {
+ display: block;
+ width: 100%;
+ height: auto;
+ }
+ }
+
+ .site-header__mobile-menu-toggle {
+ display: block;
+ position: relative;
+ width: 40px;
+ height: 40px;
+ overflow: hidden;
+ background-color: grey;
+ border: none;
+ font-size: 0;
+ text-indent: 100px;
+ white-space: nowrap;
+ cursor: pointer;
+
+ &::before,
+ &::after {
+ content: '';
+ display: block;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 20px;
+ height: 20px;
+ background-color: black;
+ mask-size: 100%;
+ mask-position: center;
+ mask-repeat: no-repeat;
+ transform: translate(-50%, -50%);
+ }
+
+ &::before {
+ mask-image: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2238%22%20height%3D%2232%22%3E%3Cpath%20d%3D%22M%200%2C0%20H%2038.4%20V%204.8%20H%200%20Z%20m%200%2C13.6%20h%2038.4%20v%204.8%20H%200%20Z%20M%200%2C27.2%20H%2038.4%20V%2032%20H%200%20Z%22%2F%3E%3C%2Fsvg%3E');
+ }
+
+ &::after {
+ mask-image: url('data:image/svg+xml,%3Csvg%20width%3D%2238%22%20height%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M%207.1210938%200.7265625%20L%203.7265625%204.1210938%20L%2015.605469%2016%20L%203.7265625%2027.878906%20L%207.1210938%2031.273438%20L%2019%2019.394531%20L%2030.878906%2031.273438%20L%2034.273438%2027.878906%20L%2022.394531%2016%20L%2034.273438%204.1210938%20L%2030.878906%200.7265625%20L%2019%2012.605469%20L%207.1210938%200.7265625%20z%20%22%20%2F%3E%3C%2Fsvg%3E');
+ opacity: 0;
+ }
+
+ &[aria-checked="true"] {
+ &::before {
+ opacity: 0;
+ }
+ &::after {
+ opacity: 1;
+ }
+ }
+ }
+
+ .site-header__general {
+ margin-bottom: calc(globals.$space-bottom * -1);
+
+ & > * {
+ margin-bottom: globals.$space-bottom;
+ }
+ }
+
+ .site-header__mobile-menu-outer-wrapper {
+ @media only screen and (min-width: globals.$breakpoint-medium) {
+ display: none;
+ }
+ }
+
+ .site-header__mobile-menu-wrapper {
+ position: fixed;
+ top: 0;
+ right: 0%;
+ z-index: 1000;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ transition: background-color 0.5s;
+
+ &[hidden] {
+ display: block;
+ right: -100%;
+ background-color: rgba(0, 0, 0, 0);
+ transition: left 0s linear 0.5s, right 0s linear 0.5s, background-color 0.5s;
+ }
+ }
+
+ .site-header__mobile-menu {
+ display: flex;
+ flex-direction: column;
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: 1100;
+ width: 80%;
+ max-width: 300px;
+ height: 100%;
+ overflow: auto;
+ background-color: white;
+ transition: transform 0.5s;
+
+ & > .wp-block-template-part {
+ display: flex;
+ flex-grow: 1;
+ flex-direction: column;
+ }
+ }
+
+ .site-header__mobile-menu-wrapper[hidden] .site-header__mobile-menu {
+ transform: translate(100%, 0);
+ }
+}
diff --git a/blocks/site-header/assets/site-header.js b/blocks/site-header/assets/site-header.js
new file mode 100644
index 0000000..1e24f3c
--- /dev/null
+++ b/blocks/site-header/assets/site-header.js
@@ -0,0 +1,236 @@
+/**
+ * Class representing a site header component with accessibility and mobile menu support.
+ *
+ * This class ensures that each header instance is uniquely identified, manages ARIA and inert
+ * attributes for accessibility, and sets up event listeners for responsive and accessible
+ * mobile menu toggling.
+ */
+class Site_Header {
+
+ /**
+ * Unique instance number for this header, used to generate unique IDs for internal elements.
+ * @type {number|null}
+ */
+ instanceNumber = null;
+
+ /**
+ * Stores jQuery references to important DOM elements in the header.
+ * @type {Object}
+ * @property {jQuery} wrapper - The main header wrapper element.
+ * @property {jQuery} mobileMenuToggle - The mobile menu toggle button.
+ * @property {jQuery} mobileMenuWrapper - The mobile menu wrapper element.
+ */
+ elements = {};
+
+ /**
+ * Create a Site_Header instance.
+ * @param {jQuery} wrapper - The jQuery-wrapped root element for the site header instance.
+ */
+ constructor(wrapper) {
+ this.setInstanceNumber();
+ this.loadElements(wrapper);
+ this.assignHiddenAttributes();
+ jQuery(window).resize(() => {this.assignHiddenAttributes()});
+ this.initializeMobileMenu();
+ this.activate();
+ }
+
+ /**
+ * Sets a unique instance number for this header instance.
+ * This is used to generate unique IDs for elements such as the mobile menu.
+ * The number is stored on the global window object to ensure uniqueness across instances.
+ */
+ setInstanceNumber() {
+ if (typeof window.headerCount == 'undefined') {
+ window.headerCount = -1;
+ }
+
+ window.headerCount++;
+ this.instanceNumber = window.headerCount;
+ }
+
+ /**
+ * Loads and stores key DOM elements for this instance.
+ * @param {jQuery} wrapper - The jQuery-wrapped root element for the site header instance.
+ */
+ loadElements(wrapper) {
+ this.elements.wrapper = wrapper;
+ this.elements.mobileMenuToggle = wrapper.find('.site-header__mobile-menu-toggle');
+ this.elements.mobileMenuWrapper = wrapper.find('.site-header__mobile-menu-wrapper');
+ }
+
+ /**
+ * Assigns `aria-hidden` and `inert` attributes to specific elements based on their visibility.
+ *
+ * This method ensures that assistive technology (such as screen readers) correctly ignores
+ * elements that are not visually visible to users. It uses a visibility check relying on
+ * layout and computed style support (via `hasLayoutSupport`). If this support is not present,
+ * the function halts to avoid incorrectly marking all elements as hidden.
+ *
+ * For each target element:
+ * - If the element is not visible (`:visible` is false), it assigns `aria-hidden="true"`
+ * and sets the `inert` property to `true`.
+ * - If the element is visible, it removes the `aria-hidden` attribute and sets the `inert`
+ * property to `false`.
+ */
+ assignHiddenAttributes() {
+ // Define selectors for elements whose visibility should control ARIA/inert state.
+ let selectors = [
+ '.site-header__section--desktop-menu',
+ '.site-header__section--mobile-menu-toggle',
+ '.site-header__mobile-menu-outer-wrapper'
+ ];
+
+ // Find all target elements within the wrapper.
+ let elements = this.elements.wrapper.find(selectors.join(', '));
+
+ // Prevent further execution if layout and computed style support is not available.
+ // This avoids marking all elements as hidden when the environment cannot determine visibility.
+ if (!this.helpers.hasLayoutSupport()) {
+ return;
+ }
+
+ elements.each(
+ (index) => {
+ let element = elements.eq(index);
+
+ // If the element is not visible, mark it as hidden for assistive technology.
+ if ( ! element.is(':visible') ) {
+ element.attr('aria-hidden', 'true');
+ element.prop('inert', true);
+ return;
+ }
+
+ // If visible, remove hidden attributes/properties.
+ element.removeAttr('aria-hidden');
+ element.prop('inert', false);
+ }
+ );
+ }
+
+ /**
+ * Initializes the mobile menu functionality for this header instance.
+ *
+ * - Sets a unique ID on the mobile menu wrapper and links it to the toggle button via `aria-controls`.
+ * - Attaches click event listeners for toggling the mobile menu.
+ * - Listens for a custom 'mobile_menu_toggle' event on the window.
+ */
+ initializeMobileMenu() {
+ let id = 'header-mobile-menu-wrapper-' + this.instanceNumber;
+
+ this.elements.mobileMenuToggle.attr('aria-controls', id);
+ this.elements.mobileMenuWrapper.prop('id', id);
+
+ this.elements.mobileMenuToggle.on(
+ 'click',
+ () => {
+ this.toggleMobileMenu();
+ }
+ );
+
+ this.elements.mobileMenuWrapper.on(
+ 'click',
+ (event) => {
+ if(!this.elements.mobileMenuWrapper.is(event.target)) {
+ return;
+ }
+
+ this.toggleMobileMenu();
+ }
+ );
+
+ jQuery(window).on(
+ 'mobile_menu_toggle',
+ () => {
+ this.toggleMobileMenu();
+ }
+ );
+ }
+
+ /**
+ * Toggles the visibility and ARIA/inert attributes of the mobile menu.
+ *
+ * This method:
+ * - Updates `aria-checked` and `aria-expanded` on the toggle button.
+ * - Shows/hides the menu wrapper with `hidden`, `aria-hidden`, and `inert`.
+ */
+ toggleMobileMenu() {
+ let checked = this.elements.mobileMenuToggle.attr('aria-checked') == 'true' ? true : false;
+
+ checked = ! checked;
+
+ this.elements.mobileMenuToggle.attr('aria-checked', checked ? 'true' : 'false');
+ this.elements.mobileMenuToggle.attr('aria-expanded', checked ? 'true' : 'false');
+ document.activeElement.blur();
+ this.elements.mobileMenuWrapper.prop('inert', ! checked);
+ this.elements.mobileMenuWrapper.prop('hidden', ! checked);
+ this.elements.mobileMenuWrapper.attr('aria-hidden', checked ? 'false' : true);
+ }
+
+ /**
+ * Adds the 'site-header__wrapper--active' class to the wrapper after initialization.
+ * This can be used in CSS to make the header opaque or trigger other visual changes.
+ */
+ activate() {
+ this.elements.wrapper.addClass('site-header__wrapper--active');
+ }
+
+ /**
+ * Helper functions for the Site_Header class.
+ * @type {Object}
+ */
+ helpers = {
+ /**
+ * Checks if the environment supports layout and computed style features needed
+ * for determining element visibility as browsers do.
+ *
+ * Specifically, verifies:
+ * - window.getComputedStyle is available and functional
+ * - offsetWidth reflects the element's width
+ * - getClientRects() returns at least one rect for a visible element
+ *
+ * @returns {boolean} True if layout and computed style features are supported; otherwise, false.
+ */
+ hasLayoutSupport: () => {
+ // Ensure getComputedStyle exists and is a function.
+ if (typeof window.getComputedStyle !== 'function') return false;
+
+ // Create a visible test element with explicit size.
+ var el = document.createElement('div');
+ el.style.display = 'block';
+ el.style.width = '10px';
+ el.style.height = '10px';
+ document.body.appendChild(el);
+
+ // Check for correct computed style, offsetWidth, and getClientRects support.
+ var result =
+ window.getComputedStyle(el).width === '10px' && // CSS width is correctly reported
+ el.offsetWidth === 10 && // offsetWidth reflects the set width
+ typeof el.getClientRects === 'function' && // getClientRects exists
+ el.getClientRects().length > 0; // getClientRects returns at least one rect
+
+ // Clean up the test element.
+ document.body.removeChild(el);
+
+ return result;
+ }
+ }
+}
+
+/**
+ * On document ready, initialize a Site_Header instance for each `.site-header__wrapper` element.
+ * Each instance is stored globally in window.siteHeader.
+ */
+jQuery(document).ready(
+ () => {
+ window.siteHeader = [];
+
+ let wrappers = jQuery('.site-header__wrapper');
+
+ wrappers.each(
+ (index) => {
+ window.siteHeader.push(new Site_Header(wrappers.eq(index)));
+ }
+ );
+ }
+);
diff --git a/blocks/site-header/class-site-header-block.php b/blocks/site-header/class-site-header-block.php
new file mode 100644
index 0000000..6a95b6a
--- /dev/null
+++ b/blocks/site-header/class-site-header-block.php
@@ -0,0 +1,164 @@
+ ';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function name(): string {
+ return 'site-header';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function label(): string {
+ return 'Site header';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function child_blocks(): array {
+ return array(
+ new Child_Block(
+ 'general',
+ 'General',
+ array(
+ array(
+ 'key' => 'field_header_block_general_device_visibility',
+ 'name' => 'device_visibility',
+ 'label' => 'Device Visibility',
+ 'type' => 'select',
+ 'choices' => array(
+ 'all' => 'All',
+ 'mobile' => 'Mobile Only',
+ 'desktop' => 'Desktop Only',
+ ),
+ ),
+ ),
+ __DIR__ . '/templates/general.php',
+ array(),
+ 'editor-help'
+ ),
+ new Child_Block(
+ 'logo',
+ 'Logo',
+ array(
+ array(
+ 'key' => 'field_header_block_logo_information',
+ 'name' => 'information',
+ 'label' => 'Information',
+ 'type' => 'message',
+ 'message' => 'This will render the file located at: "/images/logo.svg", relative to the active theme root directory.',
+ ),
+ ),
+ __DIR__ . '/templates/logo.php',
+ array(),
+ ' ',
+ ),
+ new Child_Block(
+ 'desktop-menu',
+ 'Desktop Menu',
+ array(
+ array(
+ 'key' => 'field_header_block_desktop_menu_information',
+ 'name' => 'information',
+ 'label' => 'Information',
+ 'type' => 'message',
+ 'message' => 'The "desktop-menu" template part will be rendered here.',
+ ),
+ ),
+ __DIR__ . '/templates/desktop-menu.php',
+ array(),
+ ' '
+ ),
+ new Child_Block(
+ 'mobile-menu-toggle',
+ 'Mobile Menu Toggle',
+ array(
+ array(
+ 'key' => 'field_header_block_mobile_menu_toggle_information',
+ 'name' => 'information',
+ 'label' => 'Information',
+ 'type' => 'message',
+ 'message' => 'This button will toggle the display of the "mobile-menu" template part.',
+ ),
+ ),
+ __DIR__ . '/templates/mobile-menu-toggle.php',
+ array(),
+ 'menu'
+ ),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function template(): string {
+ return __DIR__ . '/templates/block.php';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function modifier_classes(): array {
+ $classes = array();
+
+ if ( is_admin() ) {
+ array_push( $classes, 'active' );
+ }
+
+ return $classes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setup(): bool {
+ $this->register_script();
+ return parent::setup();
+ }
+
+ /**
+ * Registers the header JavaScript file.
+ */
+ protected function register_script() {
+ add_action(
+ 'wp_enqueue_scripts',
+ function () {
+ wp_register_script(
+ 'site_header',
+ plugin_dir_url( __FILE__ ) . 'assets/site-header.js',
+ array(
+ 'jquery',
+ ),
+ 1,
+ true
+ );
+ }
+ );
+ }
+}
diff --git a/blocks/site-header/templates/block.php b/blocks/site-header/templates/block.php
new file mode 100644
index 0000000..ae944f1
--- /dev/null
+++ b/blocks/site-header/templates/block.php
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
diff --git a/blocks/site-header/templates/desktop-menu.php b/blocks/site-header/templates/desktop-menu.php
new file mode 100644
index 0000000..0efd463
--- /dev/null
+++ b/blocks/site-header/templates/desktop-menu.php
@@ -0,0 +1,16 @@
+
+
+
diff --git a/blocks/site-header/templates/general.php b/blocks/site-header/templates/general.php
new file mode 100644
index 0000000..6176d54
--- /dev/null
+++ b/blocks/site-header/templates/general.php
@@ -0,0 +1,32 @@
+ 'Place any content here.',
+ ),
+ ),
+);
+?>
+
+
\ No newline at end of file
diff --git a/blocks/site-header/templates/logo.php b/blocks/site-header/templates/logo.php
new file mode 100644
index 0000000..bfc5f62
--- /dev/null
+++ b/blocks/site-header/templates/logo.php
@@ -0,0 +1,21 @@
+
+
+
diff --git a/blocks/site-header/templates/mobile-menu-toggle.php b/blocks/site-header/templates/mobile-menu-toggle.php
new file mode 100644
index 0000000..956fab7
--- /dev/null
+++ b/blocks/site-header/templates/mobile-menu-toggle.php
@@ -0,0 +1,26 @@
+
+
+