Files
myep-team/templates/forms.html.twig
T

350 lines
17 KiB
Twig

{% use 'form_div_layout.html.twig' %}
{%- block form_row -%}
{%- set widget_attr = {} -%}
{%- if help is not empty -%}
{%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%}
{%- endif -%}
<div{% with {attr: row_attr} %}{{ block('attributes') }}{% endwith %}>
{{- form_label(form) -}}
{{- form_widget(form, widget_attr) -}}
{{- form_errors(form) -}}
{{- form_help(form) -}}
</div>
{%- endblock form_row -%}
{%- block form_label -%}
{% set class = 'block font-bold pb-2' %}
{% if errors|length %}
{% set class = class ~ ' text-red-500' %}
{% endif %}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ class)|trim}) %}
{{ parent() }}
{%- endblock form_label -%}
{%- block form_errors -%}
{%- if errors|length > 0 -%}
<ul class="pb-2">
{%- for error in errors -%}
<li class="text-red-500">{{ error.message }}</li>
{%- endfor -%}
</ul>
{%- endif -%}
{%- endblock form_errors -%}
{# Muted and smaller than the label, so a help text never outweighs the field it explains. #}
{%- block form_help -%}
{%- set help_attr = help_attr|merge({'class': (help_attr.class|default('') ~ ' pt-1 text-sm text-gray-500')|trim}) -%}
{{ parent() }}
{%- endblock form_help -%}
{%- block form_widget_simple -%}
{%- set type = type|default('text') -%}
{%- if type != 'hidden' -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-xs ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset text-sm')|trim }) -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder-red-500 focus:ring-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-gray-400 focus:ring-primary' }) -%}
{%- endif -%}
{%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%}
{%- endif -%}
{{ parent() }}
{%- if disabled is defined and disabled == true -%}
<input type="hidden" name="{{ form.vars.full_name }}" value="{{ form.vars.value }}">
{%- endif -%}
{%- endblock form_widget_simple -%}
{%- block textarea_widget -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-xs ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset text-sm')|trim }) -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder-red-500 focus:ring-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-gray-400 focus:ring-primary' }) -%}
{%- endif -%}
{%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%}
{{ parent() }}
{%- if disabled is defined and disabled == true -%}
<input type="hidden" name="{{ form.vars.full_name }}" value="{{ form.vars.value }}">
{%- endif -%}
{%- endblock textarea_widget -%}
{%- block checkbox_widget -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' h-4 w-4 rounded-sm text-primary')|trim }) -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' border-red-500 text-red-500 ring-red-500 focus:ring-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' border-gray-300 focus:ring-primary' }) -%}
{%- endif -%}
{%- if attr.disabled is defined and attr.disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%}
<input type="checkbox" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
{%- endblock checkbox_widget -%}
{% block checkbox_row %}
<div class="relative flex items-start">
<div class="flex h-6 items-center">
{{ form_widget(form) }}
</div>
<div class="{{ html_classes('ml-3 leading-6', { 'text-red-500': errors|length }) }}">
<label for="{{ form.vars.id }}">
{{ form.vars.label }}
</label>
{{- form_errors(form) -}}
{{- form_help(form) -}}
</div>
</div>
{% endblock %}
{%- block choice_widget_collapsed -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-xs ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset text-sm')|trim }) -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder-red-500 focus:ring-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-gray-400 focus:ring-primary' }) -%}
{%- endif -%}
{%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%}
{{ parent() }}
{%- if disabled is defined and disabled == true -%}
<input type="hidden" name="{{ form.vars.full_name }}" value="{{ form.vars.value }}">
{%- endif -%}
{%- endblock choice_widget_collapsed -%}
{%- block choice_widget_expanded -%}
{%- for child in form %}
<div class="relative flex items-start py-2 first:pt-0">
<div class="min-w-0 flex-1 text-sm leading-4">
<label for="{{ child.vars.id }}" class="select-none font-medium text-gray-900">
{{ child.vars.label }}
</label>
</div>
<div class="ml-3 flex h-4 items-center">
{{- form_widget(child) -}}
</div>
</div>
{% endfor -%}
{%- endblock choice_widget_expanded -%}
{#
# The boarding points of a destination, as a radio list. Expanded rather than a dropdown
# because a row carries three things - the town, the street and the departure time - and two
# towns can repeat within one travel, where only the street tells them apart. App\Form\AssignmentType
# puts the latter two on the choice as data attributes.
#}
{%- block pickup_choice_widget -%}
{%- if choices is empty -%}
{# no destination chosen yet, or one without boarding points - the caller says which #}
<p class="text-sm leading-6 text-gray-500">{{ attr['data-empty-text']|default('Keine Zustiege verfügbar.') }}</p>
{%- elseif expanded -%}
{# 82% of destinations have seven boarding points or fewer and fit without scrolling;
the longest hold fifteen, which would otherwise stretch the form past the fields
beside it #}
<div class="max-h-80 divide-y divide-gray-100 overflow-y-auto rounded-md ring-1 ring-inset ring-gray-300">
{%- for child in form %}
{%- set departure = child.vars.attr['data-departure']|default('') -%}
{%- set street = child.vars.attr['data-street']|default('') -%}
<div class="flex items-start gap-3 px-3 py-2">
<div class="min-w-0 flex-1 text-sm leading-5">
<label for="{{ child.vars.id }}" class="flex select-none items-baseline gap-2">
<span class="font-medium text-gray-900">{{ child.vars.label }}</span>
{%- if departure is not empty %}
<span class="ml-auto shrink-0 tabular-nums text-gray-500">{{ departure }} Uhr</span>
{%- endif %}
</label>
{%- if street is not empty %}
<p class="truncate text-xs text-gray-500" title="{{ street }}">{{ street }}</p>
{%- endif %}
</div>
<div class="flex h-5 items-center">
{{- form_widget(child) -}}
</div>
</div>
{% endfor -%}
</div>
{%- else -%}
{{- block('choice_widget_collapsed') -}}
{%- endif -%}
{%- endblock pickup_choice_widget -%}
{#
# One questionnaire within a questionnaire form (App\Form\QuestionnaireType), which may hold
# several: its title, its description and its sections.
#}
{%- block questionnaire_part_row -%}
<section class="pb-4">
<h2 class="text-2xl font-bold pb-2">{{ label }}</h2>
{%- if help is not empty -%}
<p class="pb-6 text-gray-700">{{ help|nl2br }}</p>
{%- endif -%}
{%- for child in form -%}
{{- form_row(child) -}}
{%- endfor -%}
</section>
{%- endblock questionnaire_part_row -%}
{#
# A section of a questionnaire (App\Form\QuestionnaireType): its title and its questions. The
# questions' rows carry data-question-key and, when conditional, data-show-if, which
# assets/controllers/questionnaire_controller.js reads to show and hide them.
#}
{%- block questionnaire_section_row -%}
<fieldset class="pb-8">
{%- if label is not same as(false) -%}
<legend class="text-xl font-bold pb-4">{{ label }}</legend>
{%- endif -%}
<div class="flex flex-col gap-y-6">
{%- for child in form -%}
{{- form_row(child) -}}
{%- endfor -%}
</div>
</fieldset>
{%- endblock questionnaire_section_row -%}
{#
# A questionnaire's radio or checkbox list: each answer a whole clickable row that lights up on
# hover and stays marked once chosen, so it is plain where to click. Questionnaire-only; the
# app's other choice lists keep choice_widget_expanded.
#}
{%- block questionnaire_choice_widget -%}
<div class="flex flex-col gap-y-1" {{ block('widget_container_attributes') }}>
{%- for child in form -%}
<label for="{{ child.vars.id }}"
class="{{ html_classes('flex cursor-pointer select-none items-center gap-x-3 rounded-md px-3 py-2 text-sm ring-1 ring-inset transition-colors hover:bg-gray-100 has-[:checked]:bg-primary/10 has-[:checked]:ring-primary', { 'ring-red-500': errors|length, 'ring-gray-200': not errors|length }) }}">
{{- form_widget(child, { attr: { class: 'h-4 w-4 shrink-0 border-gray-300 text-primary focus:ring-primary' } }) -}}
<span class="text-gray-900">{{ child.vars.label }}</span>
</label>
{%- endfor -%}
</div>
{%- endblock questionnaire_choice_widget -%}
{# A 1-5 rating as a row of buttons, the radios themselves hidden. #}
{%- block questionnaire_rating_widget -%}
<div class="flex gap-x-2" role="radiogroup">
{%- for child in form -%}
<label class="cursor-pointer">
{{- form_widget(child, { attr: { class: 'peer sr-only' } }) -}}
<span class="{{ html_classes('flex h-10 w-10 items-center justify-center rounded-md text-sm font-bold ring-1 ring-inset peer-checked:bg-primary peer-checked:text-white peer-checked:ring-primary peer-focus-visible:ring-2', { 'ring-red-500 text-red-500': errors|length, 'ring-gray-300 text-gray-900': not errors|length }) }}">
{{- child.vars.label -}}
</span>
</label>
{%- endfor -%}
</div>
{%- endblock questionnaire_rating_widget -%}
{%- block birthday_widget -%}
<div class="grid grid-cols-3 gap-x-1">
{{ form_widget(form.children['day']) }}
{{ form_widget(form.children['month']) }}
{{ form_widget(form.children['year']) }}
</div>
{%- endblock -%}
{% block abbreviated_date_widget %}
{%- set class = 'flex items-center space-x-1 ' ~ attr.class|default('') -%}
{%- do form.setRendered -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder-red-500 focus:ring-red-500' }) -%}
{%- endif -%}
<div class="{{ class }}">
{{ form_widget(form.month, { 'attr': attr })}}
<span>/</span>
{{ form_widget(form.year, { 'attr': attr })}}
</div>
{% endblock %}
{%- block money_widget -%}
{% set currency_class = 'absolute top-1/2 transform -translate-y-1/2 right-0 mr-2' %}
{% if errors|length %}
{% set currency_class = currency_class ~ ' text-red-500' %}
{% else %}
{% set currency_class = currency_class ~ ' text-gray-600' %}
{% endif %}
<div class="relative">
{{ block('form_widget_simple') }}
<span class="{{ currency_class }}">€</span>
</div>
{%- endblock money_widget -%}
{%- block datepicker_widget -%}
{%- set minDate = form.vars.min_date ? form.vars.min_date | date('Y-m-d') : null -%}
{%- set maxDate = form.vars.max_date ? form.vars.max_date | date('Y-m-d') : null -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-xs ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset text-sm')|trim }) -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder:red-500 focus:ring-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-gray-900 focus:ring-primary' }) -%}
{%- endif -%}
{%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%}
<div {{ stimulus_controller('datepicker', { 'minDate': minDate, 'maxDate': maxDate, 'disableWeekends': form.vars.disable_weekends }) }}>
<input type="text" name="{{ form.vars.full_name }}" value="{{ value }}" {{ block('widget_attributes') }} {{ stimulus_target('datepicker', 'field') }} />
</div>
{%- if disabled is defined and disabled == true -%}
<input type="hidden" name="{{ form.vars.full_name }}" value="{{ value }}">
{%- endif -%}
{%- endblock datepicker_widget %}
{%- block autocomplete_widget -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' autocomplete-input block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-xs ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset text-sm')|trim }) -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder-red-500 focus:ring-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-gray-400 focus:ring-primary' }) -%}
{%- endif -%}
{%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%}
<div class="autocomplete"
data-controller="autocomplete"
{{ block('widget_attributes') }}
{% if form.vars.action %}data-action="{{ form.vars.action }}"{% endif %}
{% if form.vars.endpoint_url is defined %}data-autocomplete-url-value="{{ form.vars.endpoint_url }}"{% endif %}
{% if form.vars.choices is defined %}data-autocomplete-choices-value='{{ form.vars.choices | json_encode | raw }}'{% endif %}
>
<input type="text"
class="{{ attr.class }}"
value="{{ form.vars.initial_label }}"
{% if form.vars.placeholder %}placeholder="{{ form.vars.placeholder }}"{% endif %}
{{ stimulus_target('autocomplete', 'input') }}>
<ul class="autocomplete-result-list"></ul>
<input type="hidden"
name="{{ form.vars.full_name }}"
{{ stimulus_target('autocomplete', 'field') }}
value="{{ form.vars.value }}">
<button type="button"
class="autocomplete-icon hidden"
{{ stimulus_action('autocomplete', 'reset') }}
{{ stimulus_target('autocomplete', 'resetButton') }}>
{{ icon('close', 'w-4 h-4 text-gray-700') }}
</button>
<div class="autocomplete-icon"
{{ stimulus_target('autocomplete', 'searchIcon') }}>
{{ icon('search', 'w-4 h-4 text-gray-700') }}
</div>
</div>
{%- endblock autocomplete_widget -%}
{%- block multiselect_widget -%}
<div class="relative" {{ stimulus_controller('multiselect', { 'placeholder': form.vars.empty_label }) }}>
<div {{ stimulus_action('multiselect', 'toggle', 'click')}}
role="button"
class="relative rounded-md border border-gray-300 flex items-start space-x-2 px-3">
<div {{ stimulus_target('multiselect', 'label') }} class="flex-1 min-h-8 py-1.5 text-gray-900 text-sm"></div>
{{ icon('dots', 'w-4 h-4 mt-2') }}
</div>
<div {{ stimulus_target('multiselect', 'dropdown')}} class="absolute z-10 top-full -mt-1.5 left-0 inset-x-0 max-h-48 overflow-y-auto px-3 py-2 bg-white shadow-lg rounded-md rounded-t-none border border-gray-300 border-t-0 hidden">
{% for item in form.children %}
{{ form_row(item, { 'attr': { 'data-action': 'multiselect#update', 'data-multiselect-target': 'choice', 'data-label': item.vars.label } }) }}
{% endfor %}
</div>
</div>
{%- endblock multiselect_widget -%}