claro.theme

Same filename in other branches
  1. 9 core/themes/claro/claro.theme
  2. 10 core/themes/claro/claro.theme
  3. 11.x core/themes/claro/claro.theme

Functions to support theming in the Claro theme.

File

core/themes/claro/claro.theme

View source
<?php


/**
 * @file
 * Functions to support theming in the Claro theme.
 */
use Drupal\claro\ClaroPreRender;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\GeneratedLink;
use Drupal\Core\Link;
use Drupal\Core\Render\Element;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\media\MediaForm;
use Drupal\file\FileInterface;
use Drupal\views\ViewExecutable;

/**
 * Implements hook_theme_suggestions_HOOK_alter() for form_element.
 */
function claro_theme_suggestions_form_element_alter(&$suggestions, $variables) {
    if (!empty($variables['element']['#type'])) {
        $suggestions[] = 'form_element__' . $variables['element']['#type'];
    }
}

/**
 * Implements hook_theme_suggestions_HOOK_alter() for details.
 */
function claro_theme_suggestions_details_alter(&$suggestions, $variables) {
    if (!empty($variables['element']['#vertical_tab_item'])) {
        $suggestions[] = 'details__vertical_tabs';
    }
}

/**
 * Implements hook_preprocess_HOOK() for menu-local-tasks templates.
 *
 * Use preprocess hook to set #attached to child elements
 * because they will be processed by Twig and drupal_render will
 * be invoked.
 */
function claro_preprocess_menu_local_tasks(&$variables) {
    if (!empty($variables['primary'])) {
        $variables['primary']['#attached'] = [
            'library' => [
                'claro/drupal.nav-tabs',
            ],
        ];
    }
    elseif (!empty($variables['secondary'])) {
        $variables['secondary']['#attached'] = [
            'library' => [
                'claro/drupal.nav-tabs',
            ],
        ];
    }
    foreach (Element::children($variables['primary']) as $key) {
        $variables['primary'][$key]['#level'] = 'primary';
    }
    foreach (Element::children($variables['secondary']) as $key) {
        $variables['secondary'][$key]['#level'] = 'secondary';
    }
}

/**
 * Implements hook_preprocess_HOOK() for menu-local-task templates.
 */
function claro_preprocess_menu_local_task(&$variables) {
    $variables['link']['#options']['attributes']['class'][] = 'tabs__link';
    $variables['link']['#options']['attributes']['class'][] = 'js-tabs-link';
    // Ensure is-active class is set when the tab is active. The generic active
    // link handler applies stricter comparison rules than what is necessary for
    // tabs.
    if (isset($variables['is_active']) && $variables['is_active'] === TRUE) {
        $variables['link']['#options']['attributes']['class'][] = 'is-active';
    }
    if (isset($variables['element']['#level'])) {
        $variables['level'] = $variables['element']['#level'];
    }
}

/**
 * Implements hook_preprocess_HOOK() for menu-local-task Views UI templates.
 */
function claro_preprocess_menu_local_task__views_ui(&$variables) {
    // Remove 'tabs__link' without adding a new class because it couldn't be used
    // reliably.
    // @see https://www.drupal.org/node/3051605
    $link_class_index = array_search('tabs__link', $variables['link']['#options']['attributes']['class']);
    if ($link_class_index !== FALSE) {
        unset($variables['link']['#options']['attributes']['class'][$link_class_index]);
    }
}

/**
 * Implements template_preprocess_HOOK() for node_add_list.
 *
 * Makes node_add_list variables compatible with entity_add_list.
 */
function claro_preprocess_node_add_list(&$variables) {
    if (!empty($variables['content'])) {
        
        /** @var \Drupal\node\NodeTypeInterface $type */
        foreach ($variables['content'] as $type) {
            $label = $type->label();
            $description = $type->getDescription();
            $type_id = $type->id();
            $add_url = Url::fromRoute('node.add', [
                'node_type' => $type_id,
            ]);
            $variables['bundles'][$type_id] = [
                'label' => $label,
                'add_link' => Link::fromTextAndUrl($label, $add_url),
                'description' => [],
            ];
            if (!empty($description)) {
                $variables['bundles'][$type_id]['description'] = [
                    '#markup' => $description,
                ];
            }
        }
        $variables['attributes']['class'][] = 'node-type-list';
    }
}

/**
 * Implements template_preprocess_HOOK() for block_content_add_list.
 *
 * Makes block_content_add_list variables compatible with entity_add_list.
 */
function claro_preprocess_block_content_add_list(&$variables) {
    if (!empty($variables['content'])) {
        $query = \Drupal::request()->query
            ->all();
        
        /** @var \Drupal\block_content\BlockContentTypeInterface $type */
        foreach ($variables['content'] as $type) {
            $label = $type->label();
            $description = $type->getDescription();
            $type_id = $type->id();
            $add_url = Url::fromRoute('block_content.add_form', [
                'block_content_type' => $type_id,
            ], [
                'query' => $query,
            ]);
            $variables['bundles'][$type_id] = [
                'label' => $label,
                'add_link' => Link::fromTextAndUrl($label, $add_url),
                'description' => [],
            ];
            if (!empty($description)) {
                $variables['bundles'][$type_id]['description'] = [
                    '#markup' => $description,
                ];
            }
        }
    }
}

/**
 * Implements template_preprocess_HOOK() for entity_add_list.
 */
function claro_preprocess_entity_add_list(&$variables) {
    // Remove description if empty.
    foreach ($variables['bundles'] as $type_id => $values) {
        if (isset($values['description']['#markup']) && empty($values['description']['#markup'])) {
            $variables['bundles'][$type_id]['description'] = [];
        }
    }
}

/**
 * Implements hook_preprocess_block() for block content.
 *
 * Disables contextual links for all blocks except for layout builder blocks.
 */
function claro_preprocess_block(&$variables) {
    if (isset($variables['title_suffix']['contextual_links']) && !isset($variables['elements']['#contextual_links']['layout_builder_block'])) {
        unset($variables['title_suffix']['contextual_links']);
        unset($variables['elements']['#contextual_links']);
        $variables['attributes']['class'] = array_diff($variables['attributes']['class'], [
            'contextual-region',
        ]);
    }
}

/**
 * Implements template_preprocess_HOOK() for admin_block.
 */
function claro_preprocess_admin_block(&$variables) {
    if (!empty($variables['block']['content'])) {
        $variables['block']['content']['#attributes']['class'][] = 'admin-list--panel';
    }
}

/**
 * Implements template_preprocess_HOOK() for admin_block.
 */
function claro_preprocess_admin_block_content(&$variables) {
    foreach ($variables['content'] as &$item) {
        $link_attributes = $item['url']->getOption('attributes') ?: [];
        $link_attributes['class'][] = 'admin-item__link';
        $item['url']->setOption('attributes', $link_attributes);
        $item['link'] = Link::fromTextAndUrl($item['title'], $item['url']);
        if (empty($item['description']) || empty($item['description']['#markup'])) {
            unset($item['description']);
        }
    }
}

/**
 * Implements hook_preprocess_HOOK() for menu-local-action templates.
 */
function claro_preprocess_menu_local_action(array &$variables) {
    $variables['link']['#options']['attributes']['class'][] = 'button--primary';
    $variables['attributes']['class'][] = 'local-actions__item';
    $legacy_class_key = array_search('button-action', $variables['link']['#options']['attributes']['class']);
    if ($legacy_class_key !== FALSE) {
        $variables['link']['#options']['attributes']['class'][$legacy_class_key] = 'button--action';
    }
}

/**
 * Implements hook_element_info_alter().
 */
function claro_element_info_alter(&$type) {
    // Add a pre-render function that handles the sidebar of the node form.
    // @todo Refactor when https://www.drupal.org/node/3056089 is in.
    if (isset($type['container'])) {
        $container_pre_renders = !empty($type['container']['#pre_render']) ? $type['container']['#pre_render'] : [];
        array_unshift($container_pre_renders, [
            ClaroPreRender::class,
            'container',
        ]);
        $type['container']['#pre_render'] = $container_pre_renders;
    }
    // @todo Refactor when https://www.drupal.org/node/3016343 is fixed.
    if (isset($type['text_format'])) {
        $type['text_format']['#pre_render'][] = [
            ClaroPreRender::class,
            'textFormat',
        ];
    }
    // Add a pre-render function that handles dropbutton variants.
    if (isset($type['dropbutton'])) {
        $type['dropbutton']['#pre_render'][] = [
            ClaroPreRender::class,
            'dropButton',
        ];
    }
    if (isset($type['vertical_tabs'])) {
        $type['vertical_tabs']['#pre_render'][] = [
            ClaroPreRender::class,
            'verticalTabs',
        ];
    }
    // Add a pre-render to managed_file.
    if (isset($type['managed_file'])) {
        $type['managed_file']['#pre_render'][] = [
            ClaroPreRender::class,
            'managedFile',
        ];
    }
    // Add a pre-render to status_messages to alter the placeholder markup.
    if (isset($type['status_messages'])) {
        $type['status_messages']['#pre_render'][] = [
            ClaroPreRender::class,
            'messagePlaceholder',
        ];
    }
}

/**
 * Implements template_preprocess_filter_guidelines().
 */
function claro_preprocess_filter_guidelines(&$variables) {
    // Fix filter guidelines selector issue of 'filter/drupal.filter'.
    // @todo Remove when https://www.drupal.org/node/2881212 is fixed.
    $variables['attributes']['class'][] = 'filter-guidelines-item';
    $variables['attributes']['class'][] = 'filter-guidelines-' . $variables['format']->id();
}

/**
 * Implements template_preprocess_text_format_wrapper().
 *
 * @todo Remove when https://www.drupal.org/node/3016343 is fixed.
 */
function claro_preprocess_text_format_wrapper(&$variables) {
    $description_attributes = [];
    if (!empty($variables['attributes']['id'])) {
        $description_attributes['id'] = $variables['attributes']['aria-describedby'] = $variables['attributes']['id'];
        unset($variables['attributes']['id']);
    }
    $variables['description_attributes'] = new Attribute($description_attributes);
}

/**
 * Implements hook_theme_registry_alter().
 */
function claro_theme_registry_alter(&$theme_registry) {
    if (!empty($theme_registry['admin_block_content'])) {
        $theme_registry['admin_block_content']['variables']['attributes'] = [];
    }
    // @todo Remove when https://www.drupal.org/node/3016346 is fixed.
    if (!empty($theme_registry['text_format_wrapper'])) {
        $theme_registry['text_format_wrapper']['variables']['disabled'] = FALSE;
    }
}

/**
 * Implements hook_preprocess_install_page().
 */
function claro_preprocess_install_page(&$variables) {
    // Claro has custom styling for the install page.
    $variables['#attached']['library'][] = 'claro/install-page';
}

/**
 * Implements hook_preprocess_maintenance_page().
 */
function claro_preprocess_maintenance_page(&$variables) {
    // Claro has custom styling for the maintenance page.
    $variables['#attached']['library'][] = 'claro/maintenance-page';
}

/**
 * Implements hook_preprocess_HOOK() for details.
 *
 * @todo Revisit when https://www.drupal.org/node/3056089 is in.
 */
function claro_preprocess_details(&$variables) {
    $element = $variables['element'];
    if (!empty($element['#accordion_item'])) {
        // Details should appear as an accordion item.
        $variables['accordion_item'] = TRUE;
    }
    if (!empty($element['#accordion'])) {
        // Details should appear as a standalone accordion.
        $variables['accordion'] = TRUE;
    }
    if (!empty($element['#theme']) && $element['#theme'] === 'file_widget_multiple') {
        // Mark the details required if needed. If the file widget allows uploading
        // multiple files, the required state is checked by checking the state of
        // the first child.
        $variables['required'] = isset($element[0]['#required']) ? $element[0]['#required'] : !empty($element['#required']);
        // If the error is the same as the one in the multiple field widget element,
        // we have to avoid displaying it twice. Seven and Stark have this issue
        // as well.
        // @todo revisit when https://www.drupal.org/node/3084906 is fixed.
        if (isset($element['#errors']) && isset($variables['errors']) && $element['#errors'] === $variables['errors']) {
            unset($variables['errors']);
        }
    }
    $variables['disabled'] = !empty($element['#disabled']);
}

/**
 * Implements hook_form_alter().
 */
function claro_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
    $build_info = $form_state->getBuildInfo();
    $form_object = $form_state->getFormObject();
    // Make entity forms delete link use the action-link component.
    if (isset($form['actions']['delete']['#type']) && $form['actions']['delete']['#type'] === 'link' && !empty($build_info['callback_object']) && $build_info['callback_object'] instanceof EntityForm) {
        $form['actions']['delete'] = _claro_convert_link_to_action_link($form['actions']['delete'], 'trash', 'default', 'danger');
    }
    if ($form_object instanceof ViewsForm && strpos($form_object->getBaseFormId(), 'views_form_media_library') === 0) {
        if (isset($form['header'])) {
            $form['header']['#attributes']['class'][] = 'media-library-views-form__header';
            $form['header']['media_bulk_form']['#attributes']['class'][] = 'media-library-views-form__bulk_form';
        }
        $form['actions']['submit']['#attributes']['class'] = [
            'media-library-select',
        ];
    }
}

/**
 * Implements hook_preprocess_HOOK() for links.
 */
function claro_preprocess_links(&$variables) {
    foreach ($variables['links'] as $links_item) {
        if (!empty($links_item['link']) && !empty($links_item['link']['#url']) && $links_item['link']['#url'] instanceof Url) {
            if ($links_item['link']['#url']->isRouted()) {
                switch ($links_item['link']['#url']->getRouteName()) {
                    case 'system.theme_settings_theme':
                        $links_item['link'] = _claro_convert_link_to_action_link($links_item['link'], 'cog', 'small');
                        break;
                    case 'system.theme_uninstall':
                        $links_item['link'] = _claro_convert_link_to_action_link($links_item['link'], 'ex', 'small');
                        break;
                    case 'system.theme_set_default':
                    case 'system.theme_install':
                        $links_item['link'] = _claro_convert_link_to_action_link($links_item['link'], 'checkmark', 'small');
                        break;
                }
            }
        }
    }
}

/**
 * Converts a link render element to an action link.
 *
 * This helper merges every attributes from $link['#attributes'], from
 * $link['#options']['attributes'] and from the Url object's.
 *
 * @param array $link
 *   Link renderable array.
 * @param string|null $icon_name
 *   The name of the needed icon. When specified, a CSS class will be added with
 *   the following pattern: 'action-link--icon-[icon_name]'. If the needed icon
 *   is not implemented in CSS, no icon will be added.
 *   Currently available icons are:
 *    - checkmark,
 *    - cog,
 *    - ex,
 *    - plus,
 *    - trash.
 * @param string $size
 *   Name of the small action link variant. Defaults to 'default'.
 *   Supported sizes are:
 *    - default,
 *    - small,
 *    - extrasmall.
 * @param string $variant
 *   Variant of the action link. Supported variants are 'default' and 'danger'.
 *   Defaults to 'default'.
 *
 * @return array
 *   The link renderable converted to action link.
 */
function _claro_convert_link_to_action_link(array $link, $icon_name = NULL, $size = 'default', $variant = 'default') {
    // Early opt-out if we cannot do anything.
    if (empty($link['#type']) || $link['#type'] !== 'link' || empty($link['#url'])) {
        return $link;
    }
    // \Drupal\Core\Render\Element\Link::preRenderLink adds $link['#attributes']
    // to $link[#options]['attributes'] if it is not empty, but it does not merges
    // the 'class' subkey deeply.
    // Because of this, when $link[#options]['attributes']['class'] is set, the
    // classes defined in $link['#attributes']['class'] are ignored.
    //
    // To keep this behavior we repeat this for action-link, which means that this
    // conversion happens a bit earlier. We unset $link['#attributes'] to prevent
    // Link::preRenderLink() doing the same, because for action-links, that would
    // be needless.
    $link += [
        '#options' => [],
    ];
    if (isset($link['#attributes'])) {
        $link['#options'] += [
            'attributes' => [],
        ];
        $link['#options']['attributes'] += $link['#attributes'];
        unset($link['#attributes']);
    }
    $link['#options'] += [
        'attributes' => [],
    ];
    $link['#options']['attributes'] += [
        'class' => [],
    ];
    // Determine the needed (type) variant.
    $variants_supported = [
        'default',
        'danger',
    ];
    $variant = is_string($variant) && in_array($variant, $variants_supported) ? $variant : reset($variants_supported);
    // Remove button, button modifier CSS classes and other unwanted ones.
    $link['#options']['attributes']['class'] = array_diff($link['#options']['attributes']['class'], [
        'button',
        'button--action',
        'button--primary',
        'button--danger',
        'button--small',
        'button--extrasmall',
        'link',
    ]);
    // Adding the needed CSS classes.
    $link['#options']['attributes']['class'][] = 'action-link';
    // Add the variant-modifier CSS class only if the variant is not the default.
    if ($variant !== reset($variants_supported)) {
        $link['#options']['attributes']['class'][] = Html::getClass("action-link--{$variant}");
    }
    // Add the icon modifier CSS class.
    if (!empty($icon_name)) {
        $link['#options']['attributes']['class'][] = Html::getClass("action-link--icon-{$icon_name}");
    }
    if ($size && in_array($size, [
        'small',
        'extrasmall',
    ])) {
        $link['#options']['attributes']['class'][] = Html::getClass("action-link--{$size}");
    }
    // If the provided $link is an item of the 'links' theme function, then only
    // the attributes of the Url object are processed during rendering.
    $url_attributes = $link['#url']->getOption('attributes') ?: [];
    $url_attributes = NestedArray::mergeDeep($url_attributes, $link['#options']['attributes']);
    $link['#url']->setOption('attributes', $url_attributes);
    return $link;
}

/**
 * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
 *
 * Changes vertical tabs to container.
 */
function claro_form_node_form_alter(&$form, FormStateInterface $form_state) {
    $form['#theme'] = [
        'node_edit_form',
    ];
    $form['#attached']['library'][] = 'claro/node-form';
    $form['advanced']['#type'] = 'container';
    $form['advanced']['#accordion'] = TRUE;
    $form['meta']['#type'] = 'container';
    $form['meta']['#access'] = TRUE;
    $form['revision_information']['#type'] = 'container';
    $form['revision_information']['#group'] = 'meta';
    $form['revision_information']['#attributes']['class'][] = 'entity-meta__revision';
}

/**
 * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\media\MediaForm.
 */
function claro_form_media_form_alter(&$form, FormStateInterface $form_state) {
    // Only attach CSS from core if this form comes from Media core, and not from
    // the contrib Media Entity 1.x branch.
    if (\Drupal::moduleHandler()->moduleExists('media') && $form_state->getFormObject() instanceof MediaForm) {
        // @todo Revisit after https://www.drupal.org/node/2892304 is in. It
        // introduces a footer region to these forms which will allow for us to
        // display a top border over the published checkbox by defining a
        // media-edit-form.html.twig template the same way node does.
        $form['#attached']['library'][] = 'claro/media-form';
    }
}

/**
 * Implements hook_views_ui_display_top_alter().
 */
function claro_views_ui_display_top_alter(&$element) {
    // @todo remove this after https://www.drupal.org/node/3051605 has been
    //   solved.
    $element['tabs']['#prefix'] = preg_replace('/(class="(.+\\s)?)tabs(\\s.+"|")/', '$1views-tabs$3', $element['tabs']['#prefix']);
    $element['tabs']['#prefix'] = preg_replace('/(class="(.+\\s)?)secondary(\\s.+"|")/', '$1views-tabs--secondary$3', $element['tabs']['#prefix']);
    foreach (Element::children($element['tabs']) as $tab) {
        $element['tabs'][$tab]['#theme'] = 'menu_local_task__views_ui';
    }
    // Change top extra actions to use the small dropbutton variant.
    // @todo Revisit after https://www.drupal.org/node/3057581 is added.
    if (!empty($element['extra_actions'])) {
        $element['extra_actions']['#dropbutton_type'] = 'small';
    }
}

/**
 * Implements hook_views_ui_display_tab_alter().
 */
function claro_views_ui_display_tab_alter(&$element) {
    // We process the dropbutton-like element on views edit form's
    // display settings top section.
    //
    // That element should be a regular Dropbutton.
    //
    // After that the reported issue is fixed and the element is rendered with
    // the Dropbutton type, we just have to set it's '#dropbutton_type' to
    // 'extrasmall'.
    //
    // @todo: revisit after https://www.drupal.org/node/3057577 is fixed.
    $dummy_dropbutton =& $element['details']['top']['actions'];
    if ($dummy_dropbutton) {
        $child_keys = Element::children($dummy_dropbutton);
        $prefix_regex = '/(<.*class\\s*= *["\']?)([^"\']*)(.*)/i';
        $child_count = 0;
        foreach ($child_keys as $key) {
            if (in_array($key, [
                'prefix',
                'suffix',
            ])) {
                continue;
            }
            $nested_child_keys = Element::children($dummy_dropbutton[$key], TRUE);
            if (!empty($nested_child_keys)) {
                foreach ($nested_child_keys as $nested_key) {
                    $child_count++;
                    $prefix = $dummy_dropbutton[$key][$nested_key]['#prefix'];
                    $dummy_dropbutton[$key][$nested_key]['#prefix'] = preg_replace($prefix_regex, '$1$2 dropbutton__item dropbutton__item--extrasmall$3', $prefix);
                }
            }
            else {
                $child_count++;
                $prefix = $dummy_dropbutton[$key]['#prefix'];
                $dummy_dropbutton[$key]['#prefix'] = preg_replace($prefix_regex, '$1$2 dropbutton__item dropbutton__item--extrasmall$3', $prefix);
            }
        }
        if (!empty($dummy_dropbutton['prefix']) && !empty($dummy_dropbutton['prefix']['#markup'])) {
            $classes = 'dropbutton--extrasmall ';
            $classes .= $child_count > 1 ? 'dropbutton--multiple' : 'dropbutton--single';
            $prefix = $dummy_dropbutton['prefix']['#markup'];
            $dummy_dropbutton['prefix']['#markup'] = preg_replace($prefix_regex, '$1$2 ' . $classes . '$3', $prefix);
        }
    }
}

/**
 * Implements hook_preprocess_HOOK for views_exposed_form.
 */
function claro_preprocess_views_exposed_form(&$variables) {
    $form =& $variables['form'];
    // Add BEM classes for items in the form.
    // Sorted keys.
    $child_keys = Element::children($form, TRUE);
    $last_key = NULL;
    $child_before_actions_key = NULL;
    foreach ($child_keys as $child_key) {
        if (!empty($form[$child_key]['#type'])) {
            if ($form[$child_key]['#type'] === 'actions') {
                // We need the key of the element that precedes the actions element.
                $child_before_actions_key = $last_key;
                $form[$child_key]['#attributes']['class'][] = 'views-exposed-form__item';
                $form[$child_key]['#attributes']['class'][] = 'views-exposed-form__item--actions';
            }
            if (!in_array($form[$child_key]['#type'], [
                'hidden',
                'actions',
            ])) {
                $form[$child_key]['#wrapper_attributes']['class'][] = 'views-exposed-form__item';
                $last_key = $child_key;
            }
        }
    }
    if ($child_before_actions_key) {
        // Add a modifier class to the item that precedes the form actions.
        $form[$child_before_actions_key]['#wrapper_attributes']['class'][] = 'views-exposed-form__item--preceding-actions';
    }
}

/**
 * Implements hook_form_FORM_ID_alter() for views_exposed_form.
 */
function claro_form_views_exposed_form_alter(&$form, FormStateInterface $form_state) {
    $view = $form_state->getStorage()['view'];
    $view_title = $view->getTitle();
    // Add a label so screenreaders can identify the purpose of the exposed form
    // without having to scan content that appears further down the page.
    $form['#attributes']['aria-label'] = t('Filter the contents of the %view_title view', [
        '%view_title' => $view_title,
    ]);
}

/**
 * Implements hook_preprocess_form_element().
 */
function claro_preprocess_form_element(&$variables) {
    if (!empty($variables['element']['#errors'])) {
        $variables['label']['#attributes']['class'][] = 'has-error';
    }
    if ($variables['disabled']) {
        $variables['label']['#attributes']['class'][] = 'is-disabled';
        if (!empty($variables['description']['attributes'])) {
            $variables['description']['attributes']->addClass('is-disabled');
        }
    }
}

/**
 * Implements template_preprocess_HOOK() for input.
 */
function claro_preprocess_input(&$variables) {
    if (!empty($variables['element']['#title_display']) && $variables['element']['#title_display'] === 'attribute' && !empty((string) $variables['element']['#title'])) {
        $variables['attributes']['title'] = (string) $variables['element']['#title'];
    }
    $type_api = $variables['element']['#type'];
    $type_html = $variables['attributes']['type'];
    $text_types_html = [
        'text',
        'email',
        'tel',
        'number',
        'search',
        'password',
        'date',
        'time',
        'file',
        'color',
        'datetime-local',
        'url',
        'month',
        'week',
    ];
    if (in_array($type_html, $text_types_html)) {
        $variables['attributes']['class'][] = 'form-element';
        $variables['attributes']['class'][] = Html::getClass('form-element--type-' . $type_html);
        $variables['attributes']['class'][] = Html::getClass('form-element--api-' . $type_api);
        if (!empty($variables['element']['#autocomplete_route_name'])) {
            $variables['autocomplete_message'] = t('Loading…');
        }
    }
    if (in_array($type_html, [
        'checkbox',
        'radio',
    ])) {
        $variables['attributes']['class'][] = 'form-boolean';
        $variables['attributes']['class'][] = Html::getClass('form-boolean--type-' . $type_html);
    }
}

/**
 * Implements template_preprocess_HOOK() for textarea.
 */
function claro_preprocess_textarea(&$variables) {
    $variables['attributes']['class'][] = 'form-element';
    $variables['attributes']['class'][] = 'form-element--type-textarea';
    $variables['attributes']['class'][] = 'form-element--api-textarea';
}

/**
 * Implements template_preprocess_HOOK() for select.
 */
function claro_preprocess_select(&$variables) {
    if (!empty($variables['element']['#title_display']) && $variables['element']['#title_display'] === 'attribute' && !empty((string) $variables['element']['#title'])) {
        $variables['attributes']['title'] = (string) $variables['element']['#title'];
    }
    $variables['attributes']['class'][] = 'form-element';
    $variables['attributes']['class'][] = $variables['element']['#multiple'] ? 'form-element--type-select-multiple' : 'form-element--type-select';
    if (in_array('block-region-select', $variables['attributes']['class'])) {
        $variables['attributes']['class'][] = 'form-element--extrasmall';
    }
}

/**
 * Implements template_preprocess_HOOK() for datetime_wrapper.
 */
function claro_preprocess_datetime_wrapper(&$variables) {
    if (!empty($variables['element']['#errors'])) {
        $variables['title_attributes']['class'][] = 'has-error';
    }
    if (!empty($variables['element']['#disabled'])) {
        $variables['title_attributes']['class'][] = 'is-disabled';
        if (!empty($variables['description_attributes'])) {
            $variables['description_attributes']->addClass('is-disabled');
        }
    }
}

/**
 * Implements template_preprocess_HOOK() for fieldset.
 */
function claro_preprocess_fieldset(&$variables) {
    $element = $variables['element'];
    $composite_types = [
        'checkboxes',
        'radios',
    ];
    if (!empty($element['#type']) && in_array($element['#type'], $composite_types) && !empty($variables['element']['#children_errors'])) {
        $variables['legend_span']['attributes']->addClass('has-error');
    }
    if (!empty($element['#disabled'])) {
        $variables['legend_span']['attributes']->addClass('is-disabled');
        if (!empty($variables['description']) && !empty($variables['description']['attributes'])) {
            $variables['description']['attributes']->addClass('is-disabled');
        }
    }
    // Remove 'container-inline' class from the main attributes and add a flag
    // instead.
    // @todo remove this after https://www.drupal.org/node/3059593 has been
    //   resolved.
    if (!empty($variables['attributes']['class'])) {
        $container_inline_key = array_search('container-inline', $variables['attributes']['class']);
        if ($container_inline_key !== FALSE) {
            unset($variables['attributes']['class'][$container_inline_key]);
            $variables['inline_items'] = TRUE;
        }
    }
}

/**
 * Implements hook_preprocess_HOOK() for field_multiple_value_form.
 */
function claro_preprocess_field_multiple_value_form(&$variables) {
    // Make disabled available for the template.
    $variables['disabled'] = !empty($variables['element']['#disabled']);
    if ($variables['multiple']) {
        // Add an additional CSS class for the field label table cell.
        // This repeats the logic of template_preprocess_field_multiple_value_form()
        // without using '#prefix' and '#suffix' for the wrapper element.
        //
        // If the field is multiple, we don't have to check the existence of the
        // table header cell.
        //
        // @see template_preprocess_field_multiple_value_form().
        $header_attributes = [
            'class' => [
                'form-item__label',
                'form-item__label--multiple-value-form',
            ],
        ];
        if (!empty($variables['element']['#required'])) {
            $header_attributes['class'][] = 'js-form-required';
            $header_attributes['class'][] = 'form-required';
        }
        // Using array_key_first() for addressing the first header cell would be
        // more elegant here, but we can rely on the related theme.inc preprocess.
        $variables['table']['#header'][0]['data'] = [
            '#type' => 'html_tag',
            '#tag' => 'h4',
            '#value' => $variables['element']['#title'],
            '#attributes' => $header_attributes,
        ];
        if ($variables['disabled']) {
            $variables['table']['#attributes']['class'][] = 'tabledrag-disabled';
            $variables['table']['#attributes']['class'][] = 'js-tabledrag-disabled';
            // We will add the 'is-disabled' CSS class to the disabled table header
            // cells.
            $header_attributes['class'][] = 'is-disabled';
            foreach ($variables['table']['#header'] as &$cell) {
                if (is_array($cell) && isset($cell['data'])) {
                    $cell = $cell + [
                        'class' => [],
                    ];
                    $cell['class'][] = 'is-disabled';
                }
                else {
                    // We have to modify the structure of this header cell.
                    $cell = [
                        'data' => $cell,
                        'class' => [
                            'is-disabled',
                        ],
                    ];
                }
            }
        }
        // Make add-more button smaller.
        if (!empty($variables['button'])) {
            $variables['button']['#attributes']['class'][] = 'button--small';
        }
    }
}

/**
 * Implements hook_preprocess_HOOK() for form_element__password_confirm.
 */
function claro_preprocess_form_element__password_confirm(&$variables) {
    // Add CSS classes needed for theming the password confirm widget.
    $variables['attributes']['class'][] = 'password-confirm';
    $variables['attributes']['class'][] = 'is-initial';
    $variables['attributes']['class'][] = 'is-password-empty';
    $variables['attributes']['class'][] = 'is-confirm-empty';
}

/**
 * Implements hook_preprocess_HOOK() for form_element__password.
 */
function claro_preprocess_form_element__password(&$variables) {
    if (!empty($variables['element']['#array_parents']) && in_array('pass1', $variables['element']['#array_parents'])) {
        // This is the main password form element.
        $variables['attributes']['class'][] = 'password-confirm__password';
    }
    if (!empty($variables['element']['#array_parents']) && in_array('pass2', $variables['element']['#array_parents'])) {
        // This is the password confirm form element.
        $variables['attributes']['class'][] = 'password-confirm__confirm';
    }
}

/**
 * Implements template_preprocess_HOOK() for filter_tips.
 */
function claro_preprocess_filter_tips(&$variables) {
    $variables['#attached']['library'][] = 'filter/drupal.filter';
}

/**
 * Implements template_preprocess_HOOK() for table.
 */
function claro_preprocess_table(&$variables) {
    // Adding table sort indicator CSS class for inactive sort link.
    // @todo Revisit after https://www.drupal.org/node/3025726 or
    // https://www.drupal.org/node/1973418 is in.
    if (!empty($variables['header'])) {
        foreach ($variables['header'] as &$header_cell) {
            // For 8.6.x and below.
            // @todo Remove this after 8.6.x is out of support.
            if ($header_cell['content'] instanceof GeneratedLink) {
                $dom_doc = Html::load($header_cell['content']->getGeneratedLink());
                $anchors = $dom_doc->getElementsByTagName('a');
                if (!empty($anchors)) {
                    foreach ($anchors as $anchor) {
                        $anchor_href = $anchor->getAttribute('href');
                        $parsed_url = UrlHelper::parse($anchor_href);
                        $query = !empty($parsed_url['query']) ? $parsed_url['query'] : [];
                        if (isset($query['order']) && isset($query['sort'])) {
                            $header_cell['attributes']->addClass('sortable-heading');
                        }
                    }
                }
            }
            // For 8.7.x and above.
            if ($header_cell['content'] instanceof Link) {
                $query = $header_cell['content']->getUrl()
                    ->getOption('query') ?: [];
                if (isset($query['order']) && isset($query['sort'])) {
                    $header_cell['attributes']->addClass('sortable-heading');
                }
            }
        }
    }
    // Mark the whole table and the first cells if rows are draggable.
    if (!empty($variables['rows'])) {
        $draggable_row_found = FALSE;
        foreach ($variables['rows'] as &$row) {
            
            /** @var \Drupal\Core\Template\Attribute $row['attributes'] */
            if (!empty($row['attributes']) && $row['attributes']->hasClass('draggable')) {
                if (!$draggable_row_found) {
                    $variables['attributes']['class'][] = 'draggable-table';
                    $draggable_row_found = TRUE;
                }
                reset($row['cells']);
                $first_cell_key = key($row['cells']);
                // The 'attributes' key is always here and it is an
                // \Drupal\Core\Template\Attribute.
                // @see template_preprocess_table();
                $row['cells'][$first_cell_key]['attributes']->addClass('tabledrag-cell');
                // Check that the first cell is empty or not.
                if (empty($row['cells'][$first_cell_key]) || empty($row['cells'][$first_cell_key]['content'])) {
                    $row['cells'][$first_cell_key]['attributes']->addClass('tabledrag-cell--only-drag');
                }
            }
        }
    }
}

/**
 * Implements template_preprocess_HOOK() for field_ui_table.
 */
function claro_preprocess_field_ui_table(&$variables) {
    claro_preprocess_table($variables);
}

/**
 * Implements template_preprocess_HOOK() for views_view_table.
 *
 * @todo Revisit after https://www.drupal.org/node/3025726 or
 * https://www.drupal.org/node/1973418 is in.
 */
function claro_preprocess_views_view_table(&$variables) {
    if (!empty($variables['header'])) {
        foreach ($variables['header'] as &$header_cell) {
            if (!empty($header_cell['url'])) {
                $parsed_url = UrlHelper::parse($header_cell['url']);
                $query = !empty($parsed_url['query']) ? $parsed_url['query'] : [];
                if (isset($query['order']) && isset($query['sort'])) {
                    $header_cell['attributes']->addClass('sortable-heading');
                }
            }
        }
    }
}

/**
 * Implements hook_preprocess_HOOK() for links__dropbutton__operations.
 *
 * Operations always use the extra small dropbutton variant.
 */
function claro_preprocess_links__dropbutton__operations(&$variables) {
    $item_classes = [
        'dropbutton__item',
        'dropbutton__item--extrasmall',
    ];
    $variables['attributes']['class'][] = 'dropbutton--extrasmall';
    foreach ($variables['links'] as &$link_data) {
        $link_data['attributes']->addClass($item_classes);
    }
}

/**
 * Implements hook_preprocess_HOOK() for links__dropbutton.
 */
function claro_preprocess_links__dropbutton(&$variables) {
    // Add the right CSS class for the dropbutton list that helps reducing FOUC.
    if (!empty($variables['links'])) {
        $variables['attributes']['class'][] = count($variables['links']) > 1 ? 'dropbutton--multiple' : 'dropbutton--single';
    }
    $item_classes = [
        'dropbutton__item',
    ];
    // Check that the dropbutton has a supported variant class.
    // @todo Revisit after https://www.drupal.org/node/3057581 is added.
    if (!empty($variables['attributes']['class'])) {
        if (array_search('dropbutton--small', $variables['attributes']['class'])) {
            $item_classes[] = 'dropbutton__item--small';
        }
        elseif (array_search('dropbutton--extrasmall', $variables['attributes']['class'])) {
            $item_classes[] = 'dropbutton__item--extrasmall';
        }
    }
    foreach ($variables['links'] as &$link_data) {
        $link_data['attributes']->addClass($item_classes);
    }
}

/**
 * Implements hook_preprocess_HOOK() for views_ui_display_tab_bucket.
 */
function claro_preprocess_views_ui_display_tab_bucket(&$variables) {
    // Instead of re-styling Views UI dropbuttons with module-specific CSS styles,
    // change dropbutton variants to the extra small version.
    // @todo Revisit after https://www.drupal.org/node/3057581 is added.
    if (!empty($variables['actions']) && $variables['actions']['#type'] === 'dropbutton') {
        $variables['actions']['#dropbutton_type'] = 'extrasmall';
    }
}

/**
 * Implements hook_preprocess_HOOK() for status_messages.
 */
function claro_preprocess_status_messages(&$variables) {
    $variables['title_ids'] = [];
    foreach ($variables['message_list'] as $message_type => $messages) {
        $variables['title_ids'][$message_type] = Html::getUniqueId("message-{$message_type}-title");
    }
}

/**
 * Implements hook_preprocess_HOOK() for system_themes_page.
 */
function claro_preprocess_system_themes_page(&$variables) {
    if (!empty($variables['theme_groups'])) {
        foreach ($variables['theme_groups'] as &$theme_group) {
            if (!empty($theme_group['themes'])) {
                foreach ($theme_group['themes'] as &$theme_card) {
                    
                    /**
                     * @todo Remove dependency on attributes after
                     *   https://www.drupal.org/project/drupal/issues/2511548 has been
                     *   resolved.
                     */
                    if (isset($theme_card['screenshot']['#attributes']) && $theme_card['screenshot']['#attributes'] instanceof Attribute && $theme_card['screenshot']['#attributes']->hasClass('no-screenshot')) {
                        unset($theme_card['screenshot']);
                    }
                    $theme_card['title_id'] = Html::getUniqueId($theme_card['name'] . '-label');
                    $description_is_empty = empty((string) $theme_card['description']);
                    // Set description_id only if the description is not empty.
                    if (!$description_is_empty) {
                        $theme_card['description_id'] = Html::getUniqueId($theme_card['name'] . '-description');
                    }
                    if (!empty($theme_card['operations']) && !empty($theme_card['operations']['#theme']) && $theme_card['operations']['#theme'] === 'links') {
                        $theme_card['operations']['#theme'] = 'links__action_links';
                    }
                }
            }
        }
    }
}

/**
 * Implements hook_preprocess_HOOK() for links__action_links.
 */
function claro_preprocess_links__action_links(&$variables) {
    $variables['attributes']['class'][] = 'action-links';
    foreach ($variables['links'] as $delta => $link_item) {
        $variables['links'][$delta]['attributes']->addClass('action-links__item');
    }
}

/**
 * Implements hook_preprocess_HOOK() for file_managed_file.
 */
function claro_preprocess_file_managed_file(&$variables) {
    // Produce the same renderable element structure as image widget has.
    $child_keys = Element::children($variables['element']);
    foreach ($child_keys as $child_key) {
        $variables['data'][$child_key] = $variables['element'][$child_key];
    }
    _claro_preprocess_file_and_image_widget($variables);
}

/**
 * Implements hook_preprocess_HOOK() for file_widget_multiple.
 */
function claro_preprocess_file_widget_multiple(&$variables) {
    $has_upload = FALSE;
    if (isset($variables['table']['#type']) && $variables['table']['#type'] === 'table') {
        // Add a variant class for the table.
        $variables['table']['#attributes']['class'][] = 'table-file-multiple-widget';
        // Mark table disabled if the field widget is disabled.
        if (isset($variables['element']['#disabled']) && $variables['element']['#disabled']) {
            $variables['table']['#attributes']['class'][] = 'tabledrag-disabled';
            $variables['table']['#attributes']['class'][] = 'js-tabledrag-disabled';
            // We will add the 'is-disabled' CSS class to the disabled table header
            // cells.
            foreach ($variables['table']['#header'] as &$cell) {
                if (is_array($cell) && isset($cell['data'])) {
                    $cell = $cell + [
                        'class' => [],
                    ];
                    $cell['class'][] = 'is-disabled';
                }
                else {
                    // We have to modify the structure of this header cell.
                    $cell = [
                        'data' => $cell,
                        'class' => [
                            'is-disabled',
                        ],
                    ];
                }
            }
        }
        // Mark operations column cells with a CSS class.
        if (isset($variables['table']['#rows']) && is_array($variables['table']['#rows'])) {
            foreach ($variables['table']['#rows'] as $row_key => $row) {
                if (isset($row['data']) && is_array($row['data'])) {
                    $last_cell = end($row['data']);
                    $last_cell_key = key($row['data']);
                    if (is_array($last_cell['data'])) {
                        foreach ($last_cell['data'] as $last_cell_item) {
                            if (isset($last_cell_item['#attributes']['class']) && is_array($last_cell_item['#attributes']['class']) && in_array('remove-button', $last_cell_item['#attributes']['class'])) {
                                $variables['table']['#rows'][$row_key]['data'][$last_cell_key] += [
                                    'class' => [],
                                ];
                                $variables['table']['#rows'][$row_key]['data'][$last_cell_key]['class'][] = 'file-operations-cell';
                                break 1;
                            }
                        }
                    }
                }
            }
        }
        // Add a CSS class to the table if an upload widget is present.
        // This is required for removing the border of the last table row.
        if (!empty($variables['element'])) {
            $element_keys = Element::children($variables['element']);
            foreach ($element_keys as $delta) {
                if (!isset($variables['element'][$delta]['upload']['#access']) || $variables['element'][$delta]['upload']['#access'] !== FALSE) {
                    $has_upload = TRUE;
                    break 1;
                }
            }
        }
        $variables['table']['#attributes']['class'][] = $has_upload ? 'table-file-multiple-widget--has-upload' : 'table-file-multiple-widget--no-upload';
    }
    $table_is_not_empty = isset($variables['table']['#rows']) && !empty($variables['table']['#rows']);
    $table_is_accessible = !isset($variables['table']['#access']) || isset($variables['table']['#access']) && $variables['table']['#access'] !== FALSE;
    $variables['has_table'] = $table_is_not_empty && $table_is_accessible;
}

/**
 * Implements hook_preprocess_HOOK() for image_widget.
 */
function claro_preprocess_image_widget(&$variables) {
    // Stable adds the file size as #suffix for image file_link renderable array.
    // We have to remove that because we will render it in our file_link template
    // for every kind of files, and not just for images.
    if (!empty($variables['element']['fids']['#value'])) {
        $file = reset($variables['element']['#files']);
        unset($variables['data']['file_' . $file->id()]['filename']['#suffix']);
    }
    _claro_preprocess_file_and_image_widget($variables);
}

/**
 * Helper pre-process callback for file_managed_file and image_widget.
 *
 * @param array $variables
 *   The renderable array of image and file widgets, with 'element' and 'data'
 *   keys.
 */
function _claro_preprocess_file_and_image_widget(array &$variables) {
    $element = $variables['element'];
    $main_item_keys = [
        'upload',
        'upload_button',
        'remove_button',
    ];
    // Calculate helper values for the template.
    $upload_is_accessible = !isset($element['upload']['#access']) || $element['upload']['#access'] !== FALSE;
    $is_multiple = !empty($element['#cardinality']) && $element['#cardinality'] !== 1;
    $has_value = isset($element['#value']['fids']) && !empty($element['#value']['fids']);
    // File widget properties.
    $display_can_be_displayed = !empty($element['#display_field']);
    // Display is rendered in a separate table cell for multiple value widgets.
    $display_is_visible = $display_can_be_displayed && !$is_multiple && isset($element['display']['#type']) && $element['display']['#type'] !== 'hidden';
    $description_can_be_displayed = !empty($element['#description_field']);
    $description_is_visible = $description_can_be_displayed && isset($element['description']);
    // Image widget properties.
    $alt_can_be_displayed = !empty($element['#alt_field']);
    $alt_is_visible = $alt_can_be_displayed && (!isset($element['alt']['#access']) || $element['alt']['#access'] !== FALSE);
    $title_can_be_displayed = !empty($element['#title_field']);
    $title_is_visible = $title_can_be_displayed && (!isset($element['title']['#access']) || $element['title']['#access'] !== FALSE);
    $variables['multiple'] = $is_multiple;
    $variables['upload'] = $upload_is_accessible;
    $variables['has_value'] = $has_value;
    $variables['has_meta'] = $alt_is_visible || $title_is_visible || $display_is_visible || $description_is_visible;
    $variables['display'] = $display_is_visible;
    // Render file upload input and upload button (or file name and remove button,
    // if the field is not empty) in an emphasized div.
    foreach ($variables['data'] as $key => $item) {
        $item_is_filename = isset($item['filename']['#file']) && $item['filename']['#file'] instanceof FileInterface;
        // Move filename to main items.
        if ($item_is_filename) {
            $variables['main_items']['filename'] = $item;
            unset($variables['data'][$key]);
            continue;
        }
        // Move buttons, upload input and hidden items to main items.
        if (in_array($key, $main_item_keys)) {
            $variables['main_items'][$key] = $item;
            unset($variables['data'][$key]);
        }
    }
}

/**
 * Implements hook_preprocess_views_view_fields().
 *
 * This targets each rendered media item in the grid display of the media
 * library's modal dialog.
 */
function claro_preprocess_views_view_fields__media_library(array &$variables) {
    // Add classes to media rendered entity field so it can be targeted for
    // styling. Adding this class in a template is very difficult to do.
    if (isset($variables['fields']['rendered_entity']->wrapper_attributes)) {
        $variables['fields']['rendered_entity']->wrapper_attributes
            ->addClass('media-library-item__click-to-select-trigger');
    }
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function claro_form_media_library_add_form_alter(array &$form, FormStateInterface $form_state) {
    $form['#attributes']['class'][] = 'media-library-add-form';
    $form['#attached']['library'][] = 'claro/media_library.theme';
    // If there are unsaved media items, apply styling classes to various parts
    // of the form.
    if (isset($form['media'])) {
        $form['#attributes']['class'][] = 'media-library-add-form--with-input';
        // Put a wrapper around the informational message above the unsaved media
        // items.
        $form['description']['#template'] = '<p class="media-library-add-form__description">{{ text }}</p>';
    }
    else {
        $form['#attributes']['class'][] = 'media-library-add-form--without-input';
    }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function claro_form_media_library_add_form_upload_alter(array &$form, FormStateInterface $form_state) {
    $form['#attributes']['class'][] = 'media-library-add-form--upload';
    if (isset($form['container'])) {
        $form['container']['#attributes']['class'][] = 'media-library-add-form__input-wrapper';
    }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function claro_form_media_library_add_form_oembed_alter(array &$form, FormStateInterface $form_state) {
    $form['#attributes']['class'][] = 'media-library-add-form--oembed';
    // If no media items have been added yet, add a couple of styling classes
    // to the initial URL form.
    if (isset($form['container'])) {
        $form['container']['#attributes']['class'][] = 'media-library-add-form__input-wrapper';
        $form['container']['url']['#attributes']['class'][] = 'media-library-add-form-oembed-url';
        $form['container']['submit']['#attributes']['class'][] = 'media-library-add-form-oembed-submit';
    }
}

/**
 * Implements hook_preprocess_item_list__media_library_add_form_media_list().
 *
 * This targets each new, unsaved media item added to the media library, before
 * they are saved.
 */
function claro_preprocess_item_list__media_library_add_form_media_list(array &$variables) {
    foreach ($variables['items'] as &$item) {
        $item['value']['preview']['#attributes']['class'][] = 'media-library-add-form__preview';
        $item['value']['fields']['#attributes']['class'][] = 'media-library-add-form__fields';
        $item['value']['remove_button']['#attributes']['class'][] = 'media-library-add-form__remove-button';
        // #source_field_name is set by AddFormBase::buildEntityFormElement()
        // to help themes and form_alter hooks identify the source field.
        $fields =& $item['value']['fields'];
        $source_field_name = $fields['#source_field_name'];
        if (isset($fields[$source_field_name])) {
            $fields[$source_field_name]['#attributes']['class'][] = 'media-library-add-form__source-field';
        }
    }
}

/**
 * Implements hook_preprocess_media_library_item__widget().
 *
 * This targets each media item selected in an entity reference field.
 */
function claro_preprocess_media_library_item__widget(array &$variables) {
    $variables['content']['remove_button']['#attributes']['class'][] = 'media-library-item__remove';
}

/**
 * Implements hook_preprocess_media_library_item__small().
 *
 * This targets each pre-selected media item selected when adding new media in
 * the modal media library dialog.
 */
function claro_preprocess_media_library_item__small(array &$variables) {
    $variables['content']['select']['#attributes']['class'][] = 'media-library-item__click-to-select-checkbox';
}

/**
 * @todo Remove this when https://www.drupal.org/project/drupal/issues/2999549
 * lands.
 *
 * @see \Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::formElement()
 */
function claro_preprocess_fieldset__media_library_widget(array &$variables) {
    if (isset($variables['prefix']['weight_toggle'])) {
        $variables['prefix']['weight_toggle']['#attributes']['class'][] = 'media-library-widget__toggle-weight';
    }
    if (isset($variables['suffix']['open_button'])) {
        $variables['suffix']['open_button']['#attributes']['class'][] = 'media-library-open-button';
    }
}

/**
 * Implements hook_views_pre_render().
 */
function claro_views_pre_render(ViewExecutable $view) {
    $add_classes = function (&$option, array $classes_to_add) {
        $classes = preg_split('/\\s+/', $option);
        $classes = array_filter($classes);
        $classes = array_merge($classes, $classes_to_add);
        $option = implode(' ', array_unique($classes));
    };
    if ($view->id() === 'media_library') {
        if ($view->display_handler->options['defaults']['css_class']) {
            $add_classes($view->displayHandlers
                ->get('default')->options['css_class'], [
                'media-library-view',
            ]);
        }
        else {
            $add_classes($view->display_handler->options['css_class'], [
                'media-library-view',
            ]);
        }
        if ($view->current_display === 'page') {
            if (array_key_exists('media_bulk_form', $view->field)) {
                $add_classes($view->field['media_bulk_form']->options['element_class'], [
                    'media-library-item__click-to-select-checkbox',
                ]);
            }
            if (array_key_exists('rendered_entity', $view->field)) {
                $add_classes($view->field['rendered_entity']->options['element_class'], [
                    'media-library-item__content',
                ]);
            }
            if (array_key_exists('edit_media', $view->field)) {
                $add_classes($view->field['edit_media']->options['alter']['link_class'], [
                    'media-library-item__edit',
                ]);
            }
            if (array_key_exists('delete_media', $view->field)) {
                $add_classes($view->field['delete_media']->options['alter']['link_class'], [
                    'media-library-item__remove',
                ]);
            }
        }
        elseif (strpos($view->current_display, 'widget') === 0) {
            if (array_key_exists('rendered_entity', $view->field)) {
                $add_classes($view->field['rendered_entity']->options['element_class'], [
                    'media-library-item__content',
                ]);
            }
            if (array_key_exists('media_library_select_form', $view->field)) {
                $add_classes($view->field['media_library_select_form']->options['element_wrapper_class'], [
                    'media-library-item__click-to-select-checkbox',
                ]);
            }
            if ($view->display_handler->options['defaults']['css_class']) {
                $add_classes($view->displayHandlers
                    ->get('default')->options['css_class'], [
                    'media-library-view--widget',
                ]);
            }
            else {
                $add_classes($view->display_handler->options['css_class'], [
                    'media-library-view--widget',
                ]);
            }
        }
    }
}

Functions

Title Deprecated Summary
claro_element_info_alter Implements hook_element_info_alter().
claro_form_alter Implements hook_form_alter().
claro_form_media_form_alter Implements hook_form_BASE_FORM_ID_alter() for \Drupal\media\MediaForm.
claro_form_media_library_add_form_alter Implements hook_form_BASE_FORM_ID_alter().
claro_form_media_library_add_form_oembed_alter Implements hook_form_FORM_ID_alter().
claro_form_media_library_add_form_upload_alter Implements hook_form_FORM_ID_alter().
claro_form_node_form_alter Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
claro_form_views_exposed_form_alter Implements hook_form_FORM_ID_alter() for views_exposed_form.
claro_preprocess_admin_block Implements template_preprocess_HOOK() for admin_block.
claro_preprocess_admin_block_content Implements template_preprocess_HOOK() for admin_block.
claro_preprocess_block Implements hook_preprocess_block() for block content.
claro_preprocess_block_content_add_list Implements template_preprocess_HOOK() for block_content_add_list.
claro_preprocess_datetime_wrapper Implements template_preprocess_HOOK() for datetime_wrapper.
claro_preprocess_details Implements hook_preprocess_HOOK() for details.
claro_preprocess_entity_add_list Implements template_preprocess_HOOK() for entity_add_list.
claro_preprocess_fieldset Implements template_preprocess_HOOK() for fieldset.
claro_preprocess_fieldset__media_library_widget @todo Remove this when https://www.drupal.org/project/drupal/issues/2999549 lands.
claro_preprocess_field_multiple_value_form Implements hook_preprocess_HOOK() for field_multiple_value_form.
claro_preprocess_field_ui_table Implements template_preprocess_HOOK() for field_ui_table.
claro_preprocess_file_managed_file Implements hook_preprocess_HOOK() for file_managed_file.
claro_preprocess_file_widget_multiple Implements hook_preprocess_HOOK() for file_widget_multiple.
claro_preprocess_filter_guidelines Implements template_preprocess_filter_guidelines().
claro_preprocess_filter_tips Implements template_preprocess_HOOK() for filter_tips.
claro_preprocess_form_element Implements hook_preprocess_form_element().
claro_preprocess_form_element__password Implements hook_preprocess_HOOK() for form_element__password.
claro_preprocess_form_element__password_confirm Implements hook_preprocess_HOOK() for form_element__password_confirm.
claro_preprocess_image_widget Implements hook_preprocess_HOOK() for image_widget.
claro_preprocess_input Implements template_preprocess_HOOK() for input.
claro_preprocess_install_page Implements hook_preprocess_install_page().
claro_preprocess_item_list__media_library_add_form_media_list Implements hook_preprocess_item_list__media_library_add_form_media_list().
claro_preprocess_links Implements hook_preprocess_HOOK() for links.
claro_preprocess_links__action_links Implements hook_preprocess_HOOK() for links__action_links.
claro_preprocess_links__dropbutton Implements hook_preprocess_HOOK() for links__dropbutton.
claro_preprocess_links__dropbutton__operations Implements hook_preprocess_HOOK() for links__dropbutton__operations.
claro_preprocess_maintenance_page Implements hook_preprocess_maintenance_page().
claro_preprocess_media_library_item__small Implements hook_preprocess_media_library_item__small().
claro_preprocess_media_library_item__widget Implements hook_preprocess_media_library_item__widget().
claro_preprocess_menu_local_action Implements hook_preprocess_HOOK() for menu-local-action templates.
claro_preprocess_menu_local_task Implements hook_preprocess_HOOK() for menu-local-task templates.
claro_preprocess_menu_local_tasks Implements hook_preprocess_HOOK() for menu-local-tasks templates.
claro_preprocess_menu_local_task__views_ui Implements hook_preprocess_HOOK() for menu-local-task Views UI templates.
claro_preprocess_node_add_list Implements template_preprocess_HOOK() for node_add_list.
claro_preprocess_select Implements template_preprocess_HOOK() for select.
claro_preprocess_status_messages Implements hook_preprocess_HOOK() for status_messages.
claro_preprocess_system_themes_page Implements hook_preprocess_HOOK() for system_themes_page.
claro_preprocess_table Implements template_preprocess_HOOK() for table.
claro_preprocess_textarea Implements template_preprocess_HOOK() for textarea.
claro_preprocess_text_format_wrapper Implements template_preprocess_text_format_wrapper().
claro_preprocess_views_exposed_form Implements hook_preprocess_HOOK for views_exposed_form.
claro_preprocess_views_ui_display_tab_bucket Implements hook_preprocess_HOOK() for views_ui_display_tab_bucket.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.