{{-- Grid of checkboxes that submit as one array — the wireframe's Amenities block (docs/wireframes-v12/add-project.html ~line 250). Props: name Base field name. Submitted as name[]; writing the brackets yourself ("amenities[]") is accepted too and the error key resolves to "amenities" either way. label Label for the group as a whole. value Fallback list of checked values, used on a fresh form only. Flashed old input wins — including the *absence* of old input for this key once a submit has happened, which is how "the user unticked every box" survives a failed validation instead of being reverted to :value. required Red asterisk only. A `required` attribute on each box would force the user to tick *every* one, which is never what "this group is required" means — enforce it with a validation rule instead. A `required` attribute passed by a caller is stripped from the boxes rather than obeyed. options Assoc array of value => label. group-class Extra classes for the wrapper, e.g. "col-md-12". Checked matching is strict on the string form of the value, so a 0-valued box behaves like any other. CR-2 (spec §12): the layout wrapper is the wireframe's four-column grid rather than a Bootstrap `row`/`col-md-3` pair. v12 writes it inline on add-project.html as `.amenity-grid` / `.amenity-item`; those two names are domain-specific and this component is not, so the same declarations are in the app-additions tail of nrems-wireframe.css under `.form-check-grid`. The boxes themselves keep Bootstrap's `.form-check` / `.form-check-input` / `.form-check-label`. Those are already bridged to the wireframe's look (accent colour, 18px box) and, unlike the layout, they carry no wireframe equivalent to move to. --- spatie/laravel-html variant. html()->checkbox() is not used: it resolves old input for the field name itself, which for an array field hands back an array where a string value is expected. --}} @props([ 'name', 'label' => null, 'value' => [], 'required' => false, 'options' => [], 'groupClass' => null, ]) @php $fieldName = str_ends_with($name, '[]') ? $name : $name.'[]'; $baseId = form_field_id($name); $error = form_error_message($name); $checked = form_selected_values(form_old_array($name, $value)); // `id` and `required` never reach a box: each box owns its id, and a // required attribute would demand every single one be ticked. $passthrough = form_passthrough_attributes($attributes, ['id', 'required']); $row = html()->div()->class('form-check-grid'); foreach ($options as $optionValue => $optionLabel) { // Ids are derived from the option value, and never from a caller // supplied id: every box in the group needs its own. Shared flaw, // shared with the copy in radio.blade.php: 'a b' and 'a_b' slug to the // same thing and would render duplicate ids — disambiguate when the two // copies are pulled out into a form_option_id() helper. $optionId = $baseId.'_'.preg_replace('/[^A-Za-z0-9_-]+/', '_', (string) $optionValue); // Every box carries the group's error state: the message lives once, // under the group, so each box points at that one id. $box = html()->input('checkbox') ->class('form-check-input') ->addClassIf($error !== null, 'is-invalid') ->id($optionId) ->name($fieldName) ->attribute('value', (string) $optionValue) ->attributeIf(in_array((string) $optionValue, $checked, true), 'checked') ->attributeIf($error !== null, 'aria-invalid', 'true') ->attributeIf($error !== null, 'aria-describedby', $baseId.'_error') ->attributes($passthrough); $row = $row->addChild( html()->div()->class('form-check')->addChildren([ $box, html()->label() ->class('form-check-label') ->attribute('for', $optionId) ->text((string) $optionLabel), ]) ); } @endphp {!! $row->render() !!}