diff --git a/css/style.css b/css/style.css index df205e2..99617f0 100644 --- a/css/style.css +++ b/css/style.css @@ -479,7 +479,6 @@ section.gc-nav h1 { } .select-wrapper { - display: inline-block; position: relative; width: fit-content; } @@ -538,19 +537,6 @@ section.gc-nav h1 { color: #8a6d3b; } -.form-required:after { - content: ""; - background-image: url(../images/required.svg); - display: inline-block; - line-height: 1; - color: #e00; - background-size: 7px 7px; - width: 7px; - vertical-align: super; - margin-left: 4px; - height: 7px; -} - .form-item h4.label { border: none; padding: 0px; diff --git a/sass/component/_form.scss b/sass/component/_form.scss index 9736a06..8534594 100644 --- a/sass/component/_form.scss +++ b/sass/component/_form.scss @@ -9,7 +9,6 @@ // around the select element to style it properly. // $see http://stackoverflow.com/q/21103542 .select-wrapper { - display: inline-block; position: relative; width: fit-content; .form-inline & { @@ -71,21 +70,6 @@ } } -// Use CSS for required mark. -// Inspired from https://www.drupal.org/node/2152217. -.form-required:after { - content:""; - background-image: url(../images/required.svg); - display: inline-block; - line-height:1; - color: #e00; - background-size:7px 7px; - width: 7px; - vertical-align: super; - margin-left: 4px; - height: 7px; -} - .form-item { h4.label { border: none; diff --git a/templates/input/form-element-label.html.twig b/templates/input/form-element-label.html.twig index bc82bf5..d8ec357 100644 --- a/templates/input/form-element-label.html.twig +++ b/templates/input/form-element-label.html.twig @@ -18,31 +18,35 @@ * @see template_preprocess_form_element_label() */ #} + {%- set classes = [ - 'control-label', title_display == 'after' ? 'option', title_display == 'invisible' and not (is_checkbox or is_radio) ? 'sr-only', required ? 'js-form-required', required ? 'form-required', + required and not (is_checkbox or is_radio) ? 'required' ] -%} {% if title is not empty and title_display == 'invisible' and (is_checkbox or is_radio) -%} - {# - Clear but preserve label text as attribute (e.g. for screen readers) for - checkboxes/radio buttons when it actually should be invisible. - #} + {# Make invisible labels for checkboxes/radios to make them screen-reader friendly #} {%- set attributes = attributes.setAttribute('title', title) -%} {%- set title = null -%} {%- endif -%} + {# Labels for single checkboxes/radios contain the element itself and thus have always to be rendered regardless of whether they have a title or not. #} {%- if title is not empty or is_checkbox or is_radio -%} - {{ element }}{{ title }} - {%- if description -%} + + {{ element }} + {{ title }} + {% if required %} + + {% endif %} + {% if description %}

{{ description }}

- {%- endif -%} + {% endif %} {%- endif -%} diff --git a/templates/input/form-element.html.twig b/templates/input/form-element.html.twig index 8e2cbd7..eb9917e 100644 --- a/templates/input/form-element.html.twig +++ b/templates/input/form-element.html.twig @@ -57,11 +57,12 @@ 'js-form-item-' ~ name|clean_class, title_display not in ['after', 'before'] ? 'form-no-label', disabled == 'disabled' ? 'form-disabled', - is_form_group ? 'form-group', is_radio ? 'radio', is_checkbox ? 'checkbox', + is_single_checkbox ? 'checkbox-standalone', is_autocomplete ? 'form-autocomplete', - has_error ? 'error has-error' + has_error ? 'error has-error', + required ? 'required', ] %}{% set description_classes = [ @@ -70,6 +71,11 @@ description_display == 'invisible' ? 'visually-hidden', ] %} + +{% if is_form_group %} +
+{% endif %} + {% if label_display in ['before', 'invisible'] %} {{ label }} @@ -99,3 +105,7 @@
{% endif %} + +{% if is_form_group %} + +{% endif %} diff --git a/templates/system/fieldset.html.twig b/templates/system/fieldset.html.twig new file mode 100644 index 0000000..c3dc235 --- /dev/null +++ b/templates/system/fieldset.html.twig @@ -0,0 +1,80 @@ +{# +/** + * @file + * Default theme implementation for a fieldset element and its children. + * + * Available variables: + * - attributes: HTML attributes for the
element. + * - errors: (optional) Any errors for this
element, may not be set. + * - required: Boolean indicating whether the
element is required. + * - legend: The element containing the following properties: + * - title: Title of the
, intended for use as the text + of the . + * - attributes: HTML attributes to apply to the element. + * - description: The description element containing the following properties: + * - content: The description content of the
. + * - attributes: HTML attributes to apply to the description container. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element (default). + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. + * - children: The rendered child elements of the
. + * - prefix: The content to add before the
children. + * - suffix: The content to add after the
children. + * + * @see template_preprocess_fieldset() + * + * @ingroup themeable + */ +#} +{% + set classes = [ + 'js-form-item', + 'form-item', + 'js-form-wrapper', + 'form-wrapper', + ] +%} + + {% + set legend_span_classes = [ + 'field-name', + 'fieldset-legend', + required ? 'js-form-required', + required ? 'form-required', + ] + %} + {% + set legend_classes = [ + required ? 'required', + ] + %} + {# Always wrap fieldset legends in a for CSS positioning. #} + + {{ legend.title }} + {% if required %} + ({{ 'required'|t }}) + {% endif %} + +
+ {% if description_display == 'before' and description.content %} + {{ description.content }}
+ {% endif %} + {% if errors %} +
+ {{ errors }} +
+ {% endif %} + {% if prefix %} + {{ prefix }} + {% endif %} + {{ children }} + {% if suffix %} + {{ suffix }} + {% endif %} + {% if description_display in ['after', 'invisible'] and description.content %} + {{ description.content }} + {% endif %} + +
diff --git a/wxt_bootstrap.theme b/wxt_bootstrap.theme index a4f0cf7..eeff87b 100644 --- a/wxt_bootstrap.theme +++ b/wxt_bootstrap.theme @@ -23,6 +23,7 @@ * @see \Drupal\bootstrap\Registry */ +use Drupal\Component\Utility\Html; use Drupal\Core\Form\FormStateInterface; use Drupal\file\Entity\File; use Drupal\node\NodeInterface; @@ -207,6 +208,29 @@ function wxt_bootstrap_preprocess_input(&$variables) { else { $variables['search_submit'] = 'false'; } + + // Only for checkboxes. + $type_attr = $variables['attributes']['type'] ?? NULL; + $type = $type_attr ? (string) $type_attr->value() : NULL; + + if ($type == 'checkbox') { + // We need inputs that look like name="base[option]". + $name_attr = $variables['attributes']['name'] ?? NULL; + $name = $name_attr ? (string) $name_attr->value() : NULL; + if (!$name || !preg_match('/^([^\\[]+)\\[.+\\]$/', $name, $m)) { + return; + } + + $base = $m[1]; + // Build a stable, CSS-safe class per base name. + $group_class = 'js-wet-group-' . Html::getClass($base); + $selector = '.' . $group_class; + + // Attach the shared class and the validation rule to every checkbox in the group. + $variables['attributes']->addClass($group_class); + $variables['attributes']->setAttribute('data-rule-require_from_group', '[1,"' . $selector . '"]'); + $variables['attributes']->setAttribute('data-rule-required', 'false'); + } } /** @@ -278,3 +302,42 @@ function wxt_bootstrap_preprocess_page_title(&$variables) { $variables['lead_title'] = $node->get($checkFieldName)->first()->getString(); } } + +/** + * Implements hook_preprocess_form_element(). + */ +function wxt_bootstrap_preprocess_form_element(array &$variables) { + // Mirror #required from the render array to Twig's $required so the template + // can add required classes/markers (used by GCWeb/WET and legend styling). + if (!empty($variables['element']['#required'])) { + $variables['required'] = TRUE; + } + + // Webform context: if this is the single "checkbox" plugin, force Bootstrap's + // form-group wrapper and flag it as a single checkbox so spacing/markup match + if (!empty($variables['element']['#webform_plugin_id'])) { + if ($variables['element']['#webform_plugin_id'] == 'checkbox') { + $variables['is_form_group'] = TRUE; + $variables['is_single_checkbox'] = TRUE; + } + } +} + +/** + * Implements hook_preprocess_fieldset(). + */ +function wxt_bootstrap_preprocess_fieldset(array &$variables) { + $element = $variables['element'] ?? []; + $checkbox_radio_group = [ + 'radios', + 'checkboxes', + ]; + + // When the fieldset is a radios/checkboxes group, add a marker class so + // WET/GCWeb can style spacing/legend consistently. + if (in_array($element['#type'], $checkbox_radio_group)) { + if (isset($variables['attributes'])) { + $variables['attributes']['class'][] = 'chkbxrdio-grp'; + } + } +}