More flexible layouts

Created on 25 June 2020, about 4 years ago
Updated 19 May 2024, about 1 month ago

Motivation

I was frustrated with the limitations of the layouts provided by the other Foundation Layouts module for creating arbitrary grid cell widths, offsets, order, and I wanted to use XY Grid in my projects. I see this project addresses the latter challenge, but not really the former.

Rather than creating a layout for every conceivable combination of cell widths at every breakpoint, I decided to create fewer, more flexible layouts.

Example Use

Imagine if you wanted 3 columns:

<div class="cell small-6 medium-3 medium-order-1 medium-offset-1 large-4"></div>
<div class="cell small-6 medium-4 medium-order-0 large-4"></div>
<div class="cell small-12 medium-3 large-4"></div>

While this example is a bit contrived, designs don't always follow an expected pattern. Foundation provides a lot of classes to manipulate cells. It would be ridiculous to try to create every possible layout for every design requirement. Let's put the power in the hands of the site builder.

Features

  • 4 templates ( 1 col, 2 col, 3 col, 4 col ). We can easily add support for more cells.
  • User selectable cell widths per cell per breakpoint ( small - 1, 2, 3, etc )
  • Support for cell order per breakpoint
  • Support for cell offset per breakpoint
  • Support for xlarge breakpoint

Since this module is currently in a private repo, and would need some additional cleanup before it would be ready for public consumption, I am just posting my layout plugin file and a template so the community can take a look and see if this is something that could be considered as an enhancement to this project. If nothing else, it may provide some inspiration to providing more flexible layouts.

If you are interested in collaborating, please let me know.

namespace Drupal\foundation_layouts\Plugin\Layout;

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\Layout\LayoutDefault;

/**
 * Layout class for Foundation Layouts.
 */
class FoundationLayouts extends LayoutDefault implements PluginFormInterface {

  /**
   * {@inheridoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return parent::defaultConfiguration() + [
      'wrappers' => [],
      'wrapper_classes' => '',
      'wrapper_container' => FALSE,
      'wrapper_gutters' => [],
      'sizes' => [],
      'order' => [],
      'offsets' => [],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $configuration = $this->getConfiguration();
    $regions = $this->getPluginDefinition()->getRegions();
    $breakpoints = $this->_breakpoints();
    $region_sizes = $this->_region_sizes();
    $region_offsets = $this->_region_offsets();
    $region_orders = [];
    for($i = 0; $i < count($regions); $i++) {
      $region_orders[$i] = $i;
    }

    $form['attributes'] = [
      '#group' => 'additional_settings',
      '#type' => 'details',
      '#title' => $this->t('Wrapper attributes'),
      '#description' => $this->t('Attributes for the outermost element.'),
      '#tree' => TRUE,
    ];

    $form['attributes']['wrapper_classes'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Wrapper classes'),
      '#description' => $this->t('Add additional classes to the outermost element.'),
      '#default_value' => $configuration['wrapper_classes'],
      '#weight' => 1,
    ];

    $form['attributes']['wrapper_gutters']['x'] = [
      '#type' => 'select',
      '#title' => $this->t('X-axis gutters'),
      '#description' => $this->t('How gutters are applied to columns on the X-axis.'),
      '#options' => ['none' => 'None', 'grid-margin-x' => 'Margin', 'grid-padding-x' => 'Padding'],
      '#default_value' => isset($configuration['wrapper_gutters']['x']) ? $configuration['wrapper_gutters']['x'] : 'none',
    ];

    $form['attributes']['wrapper_gutters']['y'] = [
      '#type' => 'select',
      '#title' => $this->t('Y-axis gutters'),
      '#description' => $this->t('How gutters are applied to columns on the Y-axis.'),
      '#options' => ['none' => 'None', 'grid-margin-y' => 'Margin', 'grid-padding-y' => 'Padding'],
      '#default_value' => isset($configuration['wrapper_gutters']['y']) ? $configuration['wrapper_gutters']['y'] : 'none',
    ];
    
    $form['attributes']['wrapper_container'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Contained wrapper'),
      '#description' => $this->t('If checked, the wrapper will contain the regions to a max width.'),
      '#default_value' => $configuration['wrapper_container'],
      '#weight' => 0,
    ];

    $form['regions'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Region Configuration'),
      '#description' => $this->t('Customize each region configuration.'),
      '#weight' => 3,
      '#tree' => TRUE,
    ];

    foreach ($regions as $region_name => $region_definition) {

      $form['regions'][$region_name] = [
        '#type' => 'details',
        '#title' => $this->t('Region: @region', ['@region' => $region_name]),
        '#group' => 'region_config',
        '#weight' => $i,
        '#tree' => TRUE,
      ];

      foreach($breakpoints as $bp_name => $bp_label) {

        $form['regions'][$region_name][$bp_name] = [
          '#type' => 'details',
          '#title' => $this->t('@label settings', ['@label' => $bp_label]),
          '#tree' => TRUE,
        ];

        $form['regions'][$region_name][$bp_name]['size'] = [
          '#type' => 'select',
          '#title' => $this->t('@label columns', ['@label' => $bp_label]),
          '#description' => $this->t('Number of columns for @label screens.', ['@label' => $bp_label]),
          '#options' => $region_sizes,
          '#default_value' => isset($configuration['sizes'][$region_name][$bp_name]) ? $configuration['sizes'][$region_name][$bp_name] : 'auto',
        ];

        $form['regions'][$region_name][$bp_name]['offset'] = [
          '#type' => 'select',
          '#title' => $this->t('@label offset', ['@label' => $bp_label]),
          '#description' => $this->t('Number of columns offset for @label screens.', ['@label' => $bp_label]),
          '#options' => $region_offsets,
          '#default_value' => isset($configuration['offsets'][$region_name][$bp_name]) ? $configuration['offsets'][$region_name][$bp_name] : 0,
        ];

        $form['regions'][$region_name][$bp_name]['order'] = [
          '#type' => 'select',
          '#title' => $this->t('@label order', ['@label' => $bp_label]),
          '#description' => $this->t('Set the column order for @label screens.', ['@label' => $bp_label]),
          '#options' => $region_orders,
          '#default_value' => isset($configuration['orders'][$region_name][$bp_name]) ? $configuration['orders'][$region_name][$bp_name] : 0,
        ];

      }
    }
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {

    $breakpoints = $this->_breakpoints();
    $regions = $form_state->getValue('regions');
    foreach ($regions as $region_name) {
      foreach ($breakpoints as $bp => $breakpoint) {
        $column_count = $regions[$region_name][$bp]['size'] + $regions[$region_name][$bp]['offset'];
        if ($column_count > 12) {
          $form_state->setErrorByName("regions][$region_name][$bp]", $this->t("The total columns, including offsets, can not total more than 12."));
        }
      }
    }

  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {

    $breakpoints = $this->_breakpoints();

    $this->configuration['attributes'] = $form_state->getValue('attributes');
    foreach (['wrapper_classes', 'wrapper_container', 'wrapper_gutters'] as $name) {
      $this->configuration[$name] = $this->configuration['attributes'][$name];
      unset($this->configuration['attributes'][$name]);
    }

    $regions = $form_state->getValue('regions');
    foreach ($regions as $region_name => $region_config) {
      foreach ($breakpoints as $bp => $breakpoint) {
        $this->configuration['sizes'][$region_name][$bp] = $regions[$region_name][$bp]['size'];
        $this->configuration['offsets'][$region_name][$bp] = $regions[$region_name][$bp]['offset'];
        $this->configuration['orders'][$region_name][$bp] = $regions[$region_name][$bp]['order'];
      }
    }

  }

  /**
   * Returns an array of breakpoints.
   *
   * @return array $breakpoints
   */
  private function _breakpoints() {
     $breakpoints = [
      'small' => 'Small',
      'medium' => 'Medium',
      'large' => 'Large',
      'xlarge' => 'Extra Large',
    ];
    return $breakpoints;
  }

  /**
   * Returns an array of region_sizes.
   *
   * @return array $region_sizes
   */
  private function _region_sizes() {
    // Column sizes.
    $region_sizes = [
      'auto' => 'Auto',
      1 => 'One Col',
      2 => 'Two Col',
      3 => 'Three Col',
      4 => 'Four Col',
      5 => 'Five Col',
      6 => 'Six Col',
      7 => 'Seven Col',
      8 => 'Eight Col',
      9 => 'Nine Col',
      10 => 'Ten Col',
      11 => 'Eleven Col',
      12 => 'Twelve Col',
    ];
    return $region_sizes;
  }

  /**
   * Returns an array of region_offsets.
   *
   * @return array $region_offsets
   */
  private function _region_offsets() {
    // Offsets.
    $region_offsets = [
      0 => '0 Cols',
      1 => '1 Col',
      2 => '2 Cols',
      3 => '3 Cols',
      4 => '4 Cols',
      5 => '5 Cols',
      6 => '6 Cols',
      7 => '7 Cols',
      8 => '8 Cols',
      9 => '9 Cols',
      10 => '10 Cols',
      11 => '11 Cols',
    ];
    return $region_offsets;
  }
}

Here is my 4 column template file:

{% set container = settings.wrapper_container %}
{% set sizes = settings.sizes %}
{% set offsets = settings.offsets %}
{% set orders = settings.orders %}
{%
  set leftOuter_classes = [
    'cell',
    'small-' ~ sizes.leftOuter.small,
    'medium-' ~ sizes.leftOuter.medium,
    'large-' ~ sizes.leftOuter.large,
    'xlarge-' ~ sizes.leftOuter.xlarge,
    'small-offset-' ~ offsets.leftOuter.small,
    'medium-offset-' ~ offsets.leftOuter.medium,
    'large-offset-' ~ offsets.leftOuter.large,
    'xlarge-offset-' ~ offsets.leftOuter.xlarge,
    'small-order-' ~ orders.leftOuter.small,
    'medium-order-' ~ orders.leftOuter.medium,
    'large-order-' ~ orders.leftOuter.large,
    'xlarge-order-' ~ orders.leftOuter.xlarge
  ]
%}
{%
  set leftInner_classes = [
    'cell',
    'small-' ~ sizes.leftInner.small,
    'medium-' ~ sizes.leftInner.medium,
    'large-' ~ sizes.leftInner.large,
    'xlarge-' ~ sizes.leftInner.xlarge,
    'small-offset-' ~ offsets.leftInner.small,
    'medium-offset-' ~ offsets.leftInner.medium,
    'large-offset-' ~ offsets.leftInner.large,
    'xlarge-offset-' ~ offsets.leftInner.xlarge,
    'small-order-' ~ orders.leftInner.small,
    'medium-order-' ~ orders.leftInner.medium,
    'large-order-' ~ orders.leftInner.large,
    'xlarge-order-' ~ orders.leftInner.xlarge
  ]
%}
{%
  set rightInner_classes = [
    'cell',
    'small-' ~ sizes.rightInner.small,
    'medium-' ~ sizes.rightInner.medium,
    'large-' ~ sizes.rightInner.large,
    'xlarge-' ~ sizes.rightInner.xlarge,
    'small-offset-' ~ offsets.rightInner.small,
    'medium-offset-' ~ offsets.rightInner.medium,
    'large-offset-' ~ offsets.rightInner.large,
    'xlarge-offset-' ~ offsets.rightInner.xlarge,
    'small-order-' ~ orders.rightInner.small,
    'medium-order-' ~ orders.rightInner.medium,
    'large-order-' ~ orders.rightInner.large,
    'xlarge-order-' ~ orders.rightInner.xlarge
  ]
%}
{%
  set rightOuter_classes = [
    'cell',
    'small-' ~ sizes.rightOuter.small,
    'medium-' ~ sizes.rightOuter.medium,
    'large-' ~ sizes.rightOuter.large,
    'xlarge-' ~ sizes.rightOuter.xlarge,
    'small-offset-' ~ offsets.rightOuter.small,
    'medium-offset-' ~ offsets.rightOuter.medium,
    'large-offset-' ~ offsets.rightOuter.large,
    'xlarge-offset-' ~ offsets.rightOuter.xlarge,
    'small-order-' ~ orders.rightOuter.small,
    'medium-order-' ~ orders.rightOuter.medium,
    'large-order-' ~ orders.rightOuter.large,
    'xlarge-order-' ~ orders.rightOuter.xlarge
  ]
%}
  
{% if container == TRUE %}
  <div class="grid-container">
{% endif %}
  <div {{ attributes.addClass('grid-x', settings.wrapper_classes, settings.wrapper_gutters.x, settings.wrapper_gutters.y) }}>
    {% if content.leftOuter %}
      <div {{ region_attributes.leftOuter.addClass(leftOuter_classes) }}>
        {{ content.leftOuter }}
      </div>
    {% endif %}
    {% if content.leftInner %}
      <div {{ region_attributes.leftInner.addClass(leftInner_classes) }}>
        {{ content.leftInner }}
      </div>
    {% endif %}
    {% if content.rightInner %}
      <div {{ region_attributes.rightInner.addClass(rightInner_classes) }}>
        {{ content.rightInner %}
      </div>
    {% endif %}
    {% if content.rightOuter %}
      <div {{ region_attributes.rightOuter.addClass(rightOuter_classes) }}>
        {{ content.right }}
      </div>
    {% endif %}
  </div>
{% if container == TRUE %}
  </div>
{% endif %}
✨ Feature request
Status

Active

Version

2.0

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States scottsawyer Atlanta

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • First commit to issue fork.
Production build 0.69.0 2024