{{--
A row (or column) of radios that submit one value under one name.
Props:
name Field name, in HTML form. Required.
label Label for the group as a whole.
options Assoc array of value => label.
value Fallback selection; flashed old input wins. Unlike a
checkbox group this is a plain form_old() fallback, and
that is correct: a radio cannot be un-picked by the user,
so a missing key after a failed submit never means "the
user cleared me" — it means the group was not on the
submitted form, and the caller's value is still what to
show. See form_old()'s docblock.
required Red asterisk on the label + the `required` attribute on
every radio. That differs from the checkbox group on
purpose: `required` on each member of a *radio* group
means "pick one of us", which is exactly what the asterisk
promises, whereas on checkboxes it would demand all of
them.
inline true (default) lays the radios out in one wrapping row;
false stacks them.
group-class Extra classes for the wrapper, e.g. "col-md-6".
Everything else — data-*, disabled — passes through to every radio.
Checked matching is strict on the string form of the value, so a 0-valued
radio is selectable and an absent selection never marks it by accident
(the null == 0 trap form_selected_values() exists to close).
Accessibility: a radio group is a single control, so the wrapper carries
role="radiogroup" and takes its accessible name from the group label,
verbatim. The checkbox group has no such role because its boxes really are
independent controls.
No consumer yet — this is built for the Bookings module.
---
spatie/laravel-html variant. html()->radio() is called for the element but
never for its state, and that is structural rather than a preference: its
checked condition is `(its own old-input match) || $checked`, so "definitely
not checked" is not expressible through it — whatever it decides, a caller
can only ever add to. Value and checked are therefore set here, off the
library's own strict resolution, the same reason checkbox-group does not use
html()->checkbox().
No test can catch a regression that hands state back to html()->radio(),
because on today's inputs the two resolutions agree; this paragraph is the
guard. If you move state back into the builder, the failure will be silent
and will show up as an option that cannot be un-checked.
--}}
@props([
'name',
'label' => null,
'options' => [],
'value' => null,
'required' => false,
'inline' => true,
'groupClass' => null,
])
@php
$baseId = form_field_id($name);
$error = form_error_message($name);
$selected = form_selected_values(form_old($name, $value));
// Each radio owns its id, so a caller supplied one is never passed on.
$passthrough = form_passthrough_attributes($attributes, ['id']);
$group = html()->div()
->attribute('role', 'radiogroup')
->attributeIf(filled($label), 'aria-label', (string) $label);
foreach ($options as $optionValue => $optionLabel) {
// Slugged from the option value, the way the checkbox group does it,
// so the library has one id rule: {field}_{value}. Shared flaw, shared
// with that copy: '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);
// The group's error message lives once, under the group, so every
// radio points at that one id.
$radio = html()->radio($name)
->id($optionId)
->class('form-check-input')
->addClassIf($error !== null, 'is-invalid')
->attribute('value', (string) $optionValue)
->attributeIf(in_array((string) $optionValue, $selected, true), 'checked')
->attributeIf((bool) $required, 'required')
->attributeIf($error !== null, 'aria-invalid', 'true')
->attributeIf($error !== null, 'aria-describedby', $baseId.'_error')
->attributes($passthrough);
$group = $group->addChild(
html()->div()
->class('form-check')
->addClassIf((bool) $inline, 'form-check-inline')
->addChildren([
$radio,
html()->label()
->class('form-check-label')
->attribute('for', $optionId)
->text((string) $optionLabel),
])
);
}
@endphp
{!! $group->render() !!}