diff --git a/templates/input/form-element-label.html.twig b/templates/input/form-element-label.html.twig index ff59587..d8ec357 100644 --- a/templates/input/form-element-label.html.twig +++ b/templates/input/form-element-label.html.twig @@ -25,7 +25,7 @@ title_display == 'invisible' and not (is_checkbox or is_radio) ? 'sr-only', required ? 'js-form-required', required ? 'form-required', - required ? '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) -%} 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 db40f02..bc0dc84 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\image\Entity\ImageStyle; @@ -213,6 +214,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'); + } } /** @@ -338,3 +362,42 @@ function wxt_bootstrap_preprocess_paragraph(&$variables) { $variables['link_provider_label'] = $allowed_values[$key] ?? $key; } } + +/** + * 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'; + } + } +}