Toggle Content

Title 1Title 2

Second block

This is a default paragraph.

Second block 2

This is a default paragraph.

First toggle 1

This is paragraph for Title 1

First toggle 2

This is paragraph for Title 2

Title 1Title 2

First toggle 1

This is paragraph for Title 1

First toggle 2

This is paragraph for Title 2

block.json

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "wlc/toggle-content",
  "title": "Toggle Content",
  "category": "widgets",
  "icon": "admin-links",
  "description": "A block to display toggle content",
  "keywords": [
    "toggle", "content"
  ],
  "attributes": {
    "blockId": {
      "type": "string"
    },
    "activeTab": {
      "type": "number"
    },
    "switchType": {
      "type": "string",
      "default": "rounded"
    },
    "switcherWidth": {
      "type": "number",
      "default": 84
    },
    "switcherHeight": {
      "type": "number",
      "default": 42
    },
    "switcherPrimaryColor": {
      "type": "string",
      "default": "#4b8fff"
    },
    "switcherSecondaryColor": {
      "type": "string",
      "default": "#ffffff"
    },
    "toggleBackground": {
      "type": "string",
      "default": "#f8f8f8"
    },
    "toggleTextColor": {
      "type": "string",
      "default": "#6a72a5"
    },
    "toggleActiveTextColor": {
      "type": "string",
      "default": "#4b8fff"
    },
    "toggleActiveBackground": {
      "type": "string",
      "default": "#ffffff"
    },
    "toggleTitle1": {
      "type": "string",
      "default": "Title 1"
    },
    "toggleTitle2": {
      "type": "string",
      "default": "Title 2"
    }
  },
  "version": "1.0.0",
  "textdomain": "WLC",
  "editorStyle": "file:./index.css",
  "editorScript": "file:./index.js",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

edit.js

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';
import {
  useBlockProps,
  InspectorControls,
  InnerBlocks,
  RichText,
} from '@wordpress/block-editor';
import {
  PanelBody,
  SelectControl,
  RangeControl,
  ColorPicker,
} from '@wordpress/components';
import { useState, useEffect } from '@wordpress/element';

const TEMPLATE = [
  [ 'wlc/toggle-content-tab' ],
  [ 'wlc/toggle-content-tab' ],
];

const uniqueIds = [];

const edit = ( { attributes, setAttributes, clientId } ) => {
  const {
    blockId,
    activeTab,
    switchType,
    switcherWidth,
    switcherHeight,
    switcherPrimaryColor,
    switcherSecondaryColor,
    toggleBackground,
    toggleTextColor,
    toggleActiveTextColor,
    toggleActiveBackground,
    toggleTitle1,
    toggleTitle2
  } = attributes;

  useEffect( () => {
    if ( ( null === blockId || '' === blockId ) || uniqueIds.includes( blockId ) ) {
      const newUniqueId = 'field-' + clientId.substr(2, 9).replace('-', '');

      setAttributes( { blockId: newUniqueId } );
      uniqueIds.push( newUniqueId );
    } else {
      uniqueIds.push( blockId );
    }
  }, [] );

  const [ localActiveTab, setLocalActiveTab ] = useState( activeTab || 0 );

  useEffect( () => {
    setAttributes( { activeTab: localActiveTab } );
  }, [ localActiveTab ] );

  const blockProps = useBlockProps( {
    style: {
      '--switcher-primary-color': switcherPrimaryColor,
      '--switcher-secondary-color': switcherSecondaryColor,
      '--toggle-background': toggleBackground,
      '--toggle-text-color': toggleTextColor,
      '--toggle-active-text-color': toggleActiveTextColor,
      '--toggle-active-background': toggleActiveBackground,
    },
  } );

  const handleTabClick = ( tabIndex ) => {
    if ( tabIndex !== activeTab ) {
      setLocalActiveTab( tabIndex );
    }
  };

  return (
    <div { ...blockProps }>
      <InspectorControls>
        <PanelBody title={ __( 'Toggle Content Settings', 'WLC' ) }>
          <SelectControl
            label={ __( 'Switch Type', 'WLC' ) }
            value={ switchType }
            options={ [
              { label: __( 'Rounded', 'WLC' ), value: 'rounded' },
              { label: __( 'Rectangle', 'WLC' ), value: 'rectangle' },
              { label: __( 'Toggle', 'WLC' ), value: 'toggle' },
            ] }
            onChange={ ( value ) => setAttributes( { switchType: value } ) }
          />
          { ( switchType !== 'toggle' ) ? (
            <>
              <RangeControl
                label={ __( 'Switcher Width (px)', 'WLC' ) }
                value={ switcherWidth }
                onChange={ ( value ) => setAttributes( { switcherWidth: value } ) }
                min={ 40 }
                max={ 150 }
              />
              <RangeControl
                label={ __( 'Switcher Height (px)', 'WLC' ) }
                value={ switcherHeight }
                onChange={ ( value ) => setAttributes( { switcherHeight: value } ) }
                min={ 20 }
                max={ 150 }
              />
              <div className="components-base-control">
                <div className="components-base-control__field">
                  <label className="components-base-control__label">
                    { __( 'Switcher Primary Color', 'WLC' ) }
                  </label>
                  <ColorPicker
                    color={ switcherPrimaryColor }
                    onChangeComplete={ ( value ) => setAttributes( { switcherPrimaryColor: value.hex } ) }
                  />
                </div>
              </div>
              <div className="components-base-control">
                <div className="components-base-control__field">
                  <label className="components-base-control__label">
                    { __( 'Switcher Secondary Color', 'WLC' ) }
                  </label>
                  <ColorPicker
                    color={ switcherSecondaryColor }
                    onChangeComplete={ ( value ) => setAttributes( { switcherSecondaryColor: value.hex } ) }
                  />
                </div>
              </div>
            </>
          ) : (
            <>
              <div className="components-base-control">
                <div className="components-base-control__field">
                  <label className="components-base-control__label">
                    { __( 'Toggle Background', 'WLC' ) }
                  </label>
                  <ColorPicker
                    color={ toggleBackground }
                    onChangeComplete={ ( value ) => setAttributes( { toggleBackground: value.hex } ) }
                  />
                </div>
              </div>
              <div className="components-base-control">
                <div className="components-base-control__field">
                  <label className="components-base-control__label">
                    { __( 'Toggle Text Color', 'WLC' ) }
                  </label>
                  <ColorPicker
                    color={ toggleTextColor }
                    onChangeComplete={ ( value ) => setAttributes( { toggleTextColor: value.hex } ) }
                  />
                </div>
              </div>
              <div className="components-base-control">
                <div className="components-base-control__field">
                  <label className="components-base-control__label">
                    { __( 'Toggle Active Background', 'WLC' ) }
                  </label>
                  <ColorPicker
                    color={ toggleActiveBackground }
                    onChangeComplete={ ( value ) => setAttributes( { toggleActiveBackground: value.hex } ) }
                  />
                </div>
              </div>
              <div className="components-base-control">
                <div className="components-base-control__field">
                  <label className="components-base-control__label">
                    { __( 'Toggle Active Text Color', 'WLC' ) }
                  </label>
                  <ColorPicker
                    color={ toggleActiveTextColor }
                    onChangeComplete={ ( value ) => setAttributes( { toggleActiveTextColor: value.hex } ) }
                  />
                </div>
              </div>
            </>
          ) }
        </PanelBody>
      </InspectorControls>
      <div className="wlc-toggle-content">
        <div className="wlc-toggle-content__switcher">
          { ( switchType !== 'toggle' ) ? (
            <div className="toggle-switch switcher" data-type={ switchType }>
              <RichText
                tagName="span"
                className={`span-item--first span-item ${localActiveTab === 0 ? ' active' : ''}`}
                placeholder={ __( 'Enter title...', 'WLC' ) }
                value={ toggleTitle1 }
                onClick={ () => handleTabClick( 0 ) }
                onChange={ ( value ) => setAttributes( { toggleTitle1: value } ) }
              />
              <label
                className="toggle-switch__label"
                style={ { width: `${switcherWidth}px`, height: `${switcherHeight}px` } }
              >
                <input
                  type="checkbox"
                  className="toggle-switch__input"
                  onChange={ () => handleTabClick( localActiveTab === 0 ? 1 : 0 ) }
                  checked={ localActiveTab === 1 }
                />
                <span className="toggle-switch__controller"></span>
                <span className="toggle-switch__slider"></span>
              </label>
              <RichText
                tagName="span"
                className={`span-item--second span-item ${localActiveTab === 1 ? ' active' : ''}`}
                placeholder={ __( 'Enter title...', 'WLC' ) }
                value={ toggleTitle2 }
                onClick={ () => handleTabClick( 1 ) }
                onChange={ ( value ) => setAttributes( { toggleTitle2: value } ) }
              />
            </div>
          ) : (
            <div className="content-switch switcher" data-type={ switchType }>
              <input
                type="checkbox"
                id={`switcher-${blockId}`}
                className="content-switch__input"
                onChange={ () => handleTabClick( localActiveTab === 0 ? 1 : 0 ) }
                checked={ localActiveTab === 1 }
              />
              <label htmlFor={`switcher-${blockId}`} className="content-switch__label">
                <div className="content-switch__toggle"></div>
                <div className="content-switch__names">
                  <RichText
                    tagName="span"
                    className={`content-switch__name--first content-switch__name ${localActiveTab === 0 ? ' active' : ''}`}
                    placeholder={ __( 'Enter title...', 'WLC' ) }
                    value={ toggleTitle1 }
                    onChange={ ( value ) => setAttributes( { toggleTitle1: value } ) }
                  />
                  <RichText
                    tagName="span"
                    className={`content-switch__name--second content-switch__name ${localActiveTab === 1 ? ' active' : ''}`}
                    placeholder={ __( 'Enter title...', 'WLC' ) }
                    value={ toggleTitle2 }
                    onChange={ ( value ) => setAttributes( { toggleTitle2: value } ) }
                  />
                </div>
              </label>
            </div>
          ) }
        </div>
        <div className="wlc-toggle-content__inner" data-active-tab={ localActiveTab }>
          <InnerBlocks
            template={ TEMPLATE }
            allowedBlocks={ [ 'wlc/toggle-content-tab' ] }
            renderAppender={ false }
            templateLock="all"
          />
        </div>
      </div>
    </div>
  );
}

export default edit;

editor.scss

index.js

/**
 * WordPress dependencies
 */
import { registerBlockType } from '@wordpress/blocks';

/**
 * Internal dependencies
 */
import './editor.scss';
import './style.scss';
import edit from './edit';
import save from './save';
import metadata from './block.json';

registerBlockType( metadata.name, {
  edit,
  save,
} );

save.js

/**
 * WordPress dependencies
 */
import {
  useBlockProps,
  InnerBlocks,
  RichText,
} from '@wordpress/block-editor';

export default function save( { attributes } ) {
  const {
    blockId,
    activeTab,
    switchType,
    switcherWidth,
    switcherHeight,
    switcherPrimaryColor,
    switcherSecondaryColor,
    toggleBackground,
    toggleTextColor,
    toggleActiveTextColor,
    toggleActiveBackground,
    toggleTitle1,
    toggleTitle2
  } = attributes;

  const blockProps = useBlockProps.save( {
    style: {
      '--switcher-primary-color': switcherPrimaryColor,
      '--switcher-secondary-color': switcherSecondaryColor,
      '--toggle-background': toggleBackground,
      '--toggle-text-color': toggleTextColor,
      '--toggle-active-text-color': toggleActiveTextColor,
      '--toggle-active-background': toggleActiveBackground,
    },
  } );

  return (
    <div { ...blockProps }>
      <div className="wlc-toggle-content">
        <div className="wlc-toggle-content__switcher">
          { ( switchType !== 'toggle' ) ? (
            <div className="toggle-switch switcher" data-type={ switchType }>
              <RichText.Content tagName="span" value={ toggleTitle1 } className={`span-item--first span-item ${activeTab === 0 ? 'active' : ''}`} />
              <label
                htmlFor={`toggle-switch-${blockId}`}
                className="toggle-switch__label"
                style={ { width: `${switcherWidth}px`, height: `${switcherHeight}px` } }
              >
                <input id={`toggle-switch-${blockId}`} type="checkbox" className="toggle-switch__input" checked={ activeTab === 1 } />
                <span className="toggle-switch__controller"></span>
                <span className="toggle-switch__slider"></span>
              </label>
              <RichText.Content tagName="span" value={ toggleTitle2 } className={`span-item--second span-item ${activeTab === 1 ? 'active' : ''}`} />
            </div>
          ) : (
            <div className="content-switch switcher" data-type={ switchType }>
              <input type="checkbox" id={`content-switcher-${blockId}`} className="content-switch__input" checked={ activeTab === 1 } />
              <label id={`content-switcher-${blockId}`} className="content-switch__label">
                <div className="content-switch__toggle"></div>
                <div className="content-switch__names">
                  <RichText.Content tagName="span" value={ toggleTitle1 } className={`content-switch__name--first content-switch__name ${activeTab === 0 ? 'active' : ''}`} />
                  <RichText.Content tagName="span" value={ toggleTitle2 } className={`content-switch__name--second content-switch__name ${activeTab === 1 ? 'active' : ''}`} />
                </div>
              </label>
            </div>
          ) }
        </div>
        <div className="wlc-toggle-content__inner" data-active-tab={ activeTab }>
          <InnerBlocks.Content />
        </div>
      </div>
    </div>
  );
}

style.scss

.wlc-toggle-content {
  &__switcher {
    text-align: center;
    margin-bottom: 2rem;

    .toggle-switch {
      &[data-type="rounded"] {
        .toggle-switch__controller {
          border-radius: 1.375rem;
        }

        .toggle-switch__slider {
          border-radius: 1.375rem;
        }
      }

      .span-item {
        cursor: pointer;

        &.active {
          font-weight: bold;
        }
      }

      &__label {
        margin: 0 0.625rem;
        display: inline-block;
        position: relative;
        vertical-align: middle;
      }

      &__input {
        display: none;

        &:checked + .toggle-switch__controller {
          left: calc(100% - 2.313rem);
        }
      }

      &__controller {
        z-index: 1;
        top: 0.313rem;
        bottom: 0.313rem;
        left: 0.313rem;
        width: 2rem;
        position: absolute;
        transform: translateX(0);
        background-color: var(--switcher-secondary-color);
        transition: 0.4s;
      }

      &__slider {
        top: 0;
        right: 0;
        left: 0;
        bottom: 0;
        cursor: pointer;
        background-color: var(--switcher-primary-color);
        border: 0.188rem solid var(--switcher-primary-color);
        transition: 0.4s;
        position: absolute;
      }
    }

    .content-switch {
      width: 30%;
      border-radius: 100px;
      background: var(--toggle-background);
      margin-right: auto;
      margin-left: auto;
      padding: 0.438rem;

      &__input {
        display: none;

        &:checked + .content-switch__label {
          .content-switch__toggle {
            transform: translateX(100%);
          }
        }
      }

      &__label {
        cursor: pointer;
        width: 100%;
        height: 3rem;
        border-radius: 6.25rem;
        background-color: var(--toggle-background);
        display: block;
        position: relative;
      }

      &__toggle {
        z-index: 1;
        width: 50%;
        height: 100%;
        box-shadow: 0 0.125rem 0.938rem rgba(0, 0, 0, 0.15);
        border-radius: 6.25rem;
        background-color: var(--toggle-active-background);;
        transition: 0.4s;
        position: absolute;
      }

      &__names {
        width: 100%;
        height: 100%;
        font-size: 0.875rem;
        align-items: center;
        display: flex;
        justify-content: space-around;
        user-select: none;
        position: absolute;

        span {
          color: var(--toggle-text-color);
          z-index: 2;
          flex: 1;
          display: flex;
          align-items: center;
          justify-content: center;

          &.active {
            color: var(--toggle-active-text-color);
            font-weight: bold;
          }
        }
      }
    }
  }

  &__inner {
    @for $i from 0 through 5 {
      &[data-active-tab="#{$i}"] {
        .wp-block-wlc-toggle-content-tab:nth-child(#{$i + 1}) {
          display: block;
        }
      }
    }

    .wp-block-wlc-toggle-content-tab {
      display: none;
    }
  }
}

view.js

document.addEventListener( 'DOMContentLoaded', function () {
  const toggleContentBlocks = document.querySelectorAll( '.wlc-toggle-content' );

  if ( ! toggleContentBlocks.length ) return;

  toggleContentBlocks.forEach( block => {
    const switcher = block.querySelector( '.wlc-toggle-content__switcher .switcher' );
    const tab1 = switcher.querySelector( '.span-item--first, .content-switch__name--first' );
    const tab2 = switcher.querySelector( '.span-item--second, .content-switch__name--second' );
    const input = switcher.querySelector( 'input[type="checkbox"]' );
    const innerContent = block.querySelector( '.wlc-toggle-content__inner' );

    const setActiveTab = ( index ) => {
      if ( index === 0 ) {
        tab1.classList.add( 'active' );
        tab2.classList.remove( 'active' );
        input.checked = false;
        innerContent.setAttribute( 'data-active-tab', '0' );
      } else {
        tab1.classList.remove( 'active' );
        tab2.classList.add( 'active' );
        input.checked = true;
        innerContent.setAttribute( 'data-active-tab', '1' );
      }
    };

    tab1.addEventListener( 'click', () => setActiveTab( 0 ) );
    tab2.addEventListener( 'click', () => setActiveTab( 1 ) );
    input.addEventListener( 'change', () => setActiveTab( input.checked ? 1 : 0) );
  } );
} );