Component - Quick Filters

Quick Filters (Best of Content) Quick filters are an alternative way to display the traditional search facets (checkbox based). This is just example code. It will become obsolete once the relevant CSS and JS are moved to the www theme. Please reference the latest code in the www theme as the source of truth. Important Notes: This is a minimal component with a few CSS classes. JavaScript and js-* classes are required. Example is shown in the code snippet below. Demo

More filters

Category

Category

Category

Twig
Not available in Twig. Please use plain HTML.
HTML
<form>
  <div class="c-www-quick-filters">
    <div class="c-www-quick-filters__list-wrapper js-www-quick-filters-scroll-wrapper">
      <ul class="c-www-quick-filters__list js-www-quick-filters-scroll">
        // Render each quick filter as a list item.
        <li class="c-www-quick-filters__list-item">
          <input type="checkbox" id="filter-id" class="c-www-quick-filters__input">
          <label for="filter-id" class="e-bolt-button e-bolt-button--secondary e-bolt-button--small">Filter label</label>
        </li>
      </ul>
    </div>
    <div class="c-www-quick-filters__more">
      // Assemble "more filters" button and modal here. The modal would contain all possible filters.
      // {% include '@bolt-elements-button/button.twig' with {
      //   content: 'More filters',
      //   size: 'small',
      //   hierarchy: 'secondary',
      //   icon_before: icon_filter,
      //   attributes: {
      //     type: 'button',
      //     'data-bolt-modal-target': '.js-bolt-modal',
      //   },
      // } only %}
      // {% include '@bolt-components-modal/modal.twig' with {
      //   attributes: {
      //     class: 'js-bolt-modal',
      //   },
      //   ...
      // } only %}
    </div>
  </div>
</form>
CSS
@import '@bolt/core-v3.x';

$www-quick-filters-overflow-shadow-width: var(--bolt-spacing-x-small);
$www-quick-filters-button-shadow-offset: var(--bolt-spacing-y-medium);

.c-www-quick-filters {
  display: flex;
  justify-content: center;
  white-space: nowrap;
  margin: calc(#{$www-quick-filters-button-shadow-offset} * -1) 0
    calc(#{$www-quick-filters-button-shadow-offset} * -1)
    calc(#{$www-quick-filters-button-shadow-offset} * -0.5);
}

.c-www-quick-filters__list-wrapper {
  flex-basis: auto;
  flex-grow: 0;
  flex-shrink: 1;
  position: relative;
  overflow: hidden;
  transition: margin-left var(--bolt-transition);

  &:before,
  &:after {
    content: '';
    opacity: 0;
    position: absolute;
    top: 0;
    bottom: 0;
    width: calc(#{$www-quick-filters-overflow-shadow-width} * 2);
    pointer-events: none;
    background: radial-gradient(rgba(black, 0.2), rgba(black, 0) 50%);
  }

  &:before {
    left: calc(#{$www-quick-filters-overflow-shadow-width} * -1);
    z-index: 1;
  }

  &:after {
    right: calc(#{$www-quick-filters-overflow-shadow-width} * -1);
  }

  &.is-overflowing {
    &.is-not-start {
      margin-left: calc(#{$www-quick-filters-button-shadow-offset} / 2);
    }

    &.is-not-start:before,
    &.is-not-end:after {
      opacity: 1;
    }

    & + .c-www-quick-filters__more {
      margin-left: var(
        --bolt-spacing-x-small
      ); // This is the spacing between the quick filters and the more filters button.
    }
  }
}

.c-www-quick-filters__list,
.c-www-quick-filters__more {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  height: 100%;
  padding: #{$www-quick-filters-button-shadow-offset} 0;
}

.c-www-quick-filters__list {
  @include bolt-horizontal-scroll;
  position: relative;
  margin: 0;
  list-style: none;
}

.c-www-quick-filters__list-item {
  padding-right: var(--bolt-spacing-x-xsmall);

  &:first-child {
    padding-left: #{$www-quick-filters-overflow-shadow-width};
  }

  &:last-child {
    padding-right: #{$www-quick-filters-overflow-shadow-width};
  }
}

.c-www-quick-filters__input {
  @include bolt-visuallyhidden;

  & + .e-bolt-button {
    padding-right: var(--bolt-spacing-x-medium);
    padding-left: var(--bolt-spacing-x-medium);

    &:after {
      transition: opacity var(--bolt-transition);
    }
  }

  &:checked + .e-bolt-button,
  &:focus + .e-bolt-button {
    transform: translate3d(0, 0, 0);
  }

  &:focus + .e-bolt-button {
    outline: var(--bolt-focus-ring);
    outline-offset: 2px;
  }

  &:checked + .e-bolt-button {
    color: var(--m-bolt-link);
    box-shadow: 0 0 0 1px var(--bolt-color-navy-light);
    background-image: linear-gradient(
      rgba(bolt-color(gray, light), 0.2),
      rgba(bolt-color(gray, light), 0.2)
    );

    &:after {
      content: '';
      opacity: 1;
      top: 50%;
      left: 0;
      transform: translate3d(
          calc(100% + var(--bolt-spacing-x-xxsmall)),
          -60%,
          0
        )
        rotate(45deg);
      width: 0.5em;
      height: 0.75em;
      border-right: 2px solid var(--m-bolt-headline);
      border-bottom: 2px solid var(--m-bolt-headline);
      border-radius: 0;
      box-shadow: none;
    }
  }

  &:not(:checked):focus + .e-bolt-button {
    &:after {
      display: none;
    }
  }

  &:checked:focus + .e-bolt-button {
    color: var(--m-bolt-link);
    box-shadow: none;
  }
}
JavaScript
const quickFiltersScroll = el => {
  if (!el) return;

  const wrapper = el.closest('.js-www-quick-filters-scroll-wrapper');

  const handleScroll = () => {
    const wrapperWidth = wrapper.offsetWidth;
    const buffer = 1; // Use buffer due to sub-pixel rounding differences between scroll and wrapper width
    const notStart = el.scrollLeft > buffer;
    const notEnd = el.scrollLeft < el.scrollWidth - wrapperWidth - buffer;
    const isOverflowing = el.scrollWidth > wrapperWidth;

    if (isOverflowing) {
      wrapper.classList.add('is-overflowing');
      if (notStart) {
        wrapper.classList.add('is-not-start');
      } else {
        wrapper.classList.remove('is-not-start');
      }
      if (notEnd) {
        wrapper.classList.add('is-not-end');
      } else {
        wrapper.classList.remove('is-not-end');
      }
    } else {
      wrapper.classList.remove('is-overflowing');
      wrapper.classList.remove('is-not-start');
      wrapper.classList.remove('is-not-end');
    }
  };

  el.addEventListener('scroll', handleScroll, { passive: true });
  window.addEventListener('resize', handleScroll, { passive: true });

  handleScroll(); // Call once onload to setup initial classes
};

const quickFiltersScrollEl = document.querySelector(
  '.js-www-quick-filters-scroll',
);

if (quickFiltersScrollEl) {
  quickFiltersScroll(quickFiltersScrollEl);
}

community docs

Full page mockups are not available on this view-all page. Use the side nav to explore each page individually.

oclp docs

Full page mockups are not available on this view-all page. Use the side nav to explore each page individually.

portal docs

Full page mockups are not available on this view-all page. Use the side nav to explore each page individually.

user docs

Full page mockups are not available on this view-all page. Use the side nav to explore each page individually.

wysiwyg kitchen sink

Column 1 Column 2 Column 3

Ideal CKEditor Config

Bolt needs to create an example of CKEditor that can map to web components, and all the related Bolt components need to become web components.

ClassicEditor
  .create( document.querySelector( '#editor' ), {
    heading: {
      options: [
        { model: 'paragraph', view: 'bolt-text', title: 'Paragraph' },
        { model: 'headline1', view: 'bolt-text', title: 'Headline xxxlarge h1', attributes: { headline: true, font-size: 'xxxlarge', tag: 'h1' } },
        { model: 'headline2', view: 'bolt-text', title: 'Headline xxlarge h2', attributes: { headline: true, font-size: 'xxlarge', tag: 'h2' } },
        { model: 'headline3', view: 'bolt-text', title: 'Headline xlarge h3', attributes: { headline: true, font-size: 'xlarge', tag: 'h3' } },
        { model: 'headline4', view: 'bolt-text', title: 'Headline large h4', attributes: { headline: true, font-size: 'large', tag: 'h4' } },
        { model: 'headline5', view: 'bolt-text', title: 'Headline small h5', attributes: { headline: true, font-size: 'small', tag: 'h5' } },
        { model: 'headline6', view: 'bolt-text', title: 'Headline xsmall h6', attributes: { headline: true, font-size: 'xsmall', tag: 'h6' } },
        { model: 'subheadline1', view: 'bolt-text', title: 'Subheadline xxlarge', attributes: { subheadline: true, font-size: 'xxlarge', tag: 'p' } },
        { model: 'subheadline2', view: 'bolt-text', title: 'Subheadline xlarge', attributes: { subheadline: true, font-size: 'xlarge', tag: 'p' } },
        { model: 'subheadline3', view: 'bolt-text', title: 'Subheadline large', attributes: { subheadline: true, font-size: 'large', tag: 'p' } },
        { model: 'eyebrow', view: 'bolt-text', title: 'Eyebrow', attributes: { eyebrow: true, tag: 'p' } },
        { model: 'link', view: 'bolt-text', title: 'Link', attributes: { eyebrow: true, tag: 'a' } },
        { model: 'blockquote', view: 'bolt-blockquote', title: 'Blockquote' },
        { model: 'ordered-list', view: 'bolt-ordered-list', title: 'Ordered list' },
        { model: 'unordered-list', view: 'bolt-unordered-list', title: 'Unordered list' },
        { model: 'code', view: 'bolt-code-snippet', title: 'Code snippet' },
      ]
    }
  } )
  .then( ... )
  .catch( ... );

CKEditor Docs

The docs show that mapping each style option to a specific tag and class is possible, but we need to figure out if it can do attributes as well. Otherwise, we'd have to make sure our web components can work without any attributes.

https://github.com/ckeditor/ckeditor-dev/tree/major/plugins
https://docs.ckeditor.com/ckeditor4/latest/guide/dev_acf.html
https://docs.ckeditor.com/ckeditor4/latest/api/CKEDITOR_config.html#cfg-protectedSource

Headlines

Headline 1 is XXLarge Headline

Headline 2 is XLarge Headline

Headline 3 is Large Headline

Text

Body text is medium sized text.

Italic text is emphasized.

Bold text is strong.

Superscript text is sup.

Subscript text is sub.

Link

Link is link component with default settings.

Blockquote

Blockquote is blockquote component with default settings.

Ordered List

Ordered list is
Ordered list component
with default settings

Unordered List

Unordered list is
Unordered list component
with default settings

Code

Code text is the Code Snippet component with default settings.

Table

Column 1 Column 2 Column 3
Row 1 R1C1 R1C2 R1C3
Row 2 R2C1 R2C2 R2C3
Row 3 R3C1 R3C2 R3C3
Foter F1C1 F1C2 F1C3

Missing in Bolt

Figure (Image and Video): the figure component needs to be styled.
All existing components other than the code snippet are not yet web components