FLZ UI Components
Shared WordPress UI, validation and block components for Tagore plugins.
by Tagore-Gymnasium / Simon · github.com/filzmann/flz_ui_components
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/filzmann/flz_ui_components/archive/refs/heads/main.zipReadme
FLZ UI Components 0.1.11
flz_ui_components stellt gemeinsame UI-Bausteine für die eigenen Tagore-Plugins bereit. Das Plugin ist bewusst klein gehalten: normale PHP-Templates bleiben normale PHP-Templates, bekommen aber zentrale Renderer, einheitliche Klassen, gemeinsame Formularvalidierung und wiederverwendbare Assets.
Installation lokal
Das Plugin liegt im Plugin-Repository:
~/projects/tagore-plugins/flz_ui_components
In der lokalen WordPress-Instanz wird es per Symlink verfügbar gemacht. Im WordPress-Plugin-Verzeichnis sollen keine Plugin-Dateien direkt bearbeitet werden.
Beispiel:
ln -s ~/projects/tagore-plugins/flz_ui_components ~/projects/tagore-local/public/wp-content/plugins/flz_ui_components
Falls die lokale WordPress-Struktur abweicht, den Zielpfad entsprechend anpassen.
Backend-Demo
Im WordPress-Backend gibt es nach Aktivierung eine lebende Komponentenübersicht unter:
FLZ UI Components
Die Seite zeigt Buttons, Icon-Buttons, Notices, CSV-Panel, Field Matrix, Cards, Editable Rows, Formularfelder und die serverseitige Validierung mit Beispielwerten.
Gutenberg-Blocks für Shortcodes
Frontend-Plugins können vorhandene Shortcodes als dynamische Gutenberg-Blocks registrieren. Das Rendering läuft weiter über den Shortcode, der Editor bekommt aber einen auffindbaren Block mit optionalen Inspector-Feldern:
flz_ui_register_shortcode_block(
array(
'name' => 'flz/beispiel',
'shortcode' => 'flz_beispiel',
'title' => 'FLZ Beispiel',
'description' => 'Frontend-Ausgabe des Beispielplugins.',
'attributes' => array(
'danke' => array(
'type' => 'string',
'default' => '',
),
),
'fields' => array(
'danke' => array(
'label' => 'Danke-Seite-ID',
'description' => 'Optionaler Redirect nach erfolgreicher Aktion.',
),
),
)
);
Die Blocks erscheinen in der Kategorie Tagore / FLZ. Bestehende Shortcodes
bleiben dadurch kompatibel, neue Seiten können aber ohne manuelle
Shortcode-Syntax gepflegt werden.
Der Editor-Placeholder nutzt useBlockProps(), damit dynamische
Shortcode-Blöcke im Gutenberg-Editor zuverlässig auswählbar, bearbeitbar und
entfernbar bleiben.
Admin-Tabellen
Für sortierbare Tabellenköpfe steht ein gemeinsamer Link-Renderer bereit:
echo flz_ui()->admin_table_sort_link(
array(
'label' => 'Name',
'sort' => 'name',
'current_sort' => $orderby,
'current_order' => $order,
'url_args' => array(
'page' => 'flz_beispiel',
),
)
);
Die Fachplugins bleiben für Sanitizing, erlaubte Sortierschlüssel und die eigentliche Datenfilterung zuständig. Das UI-Plugin kümmert sich nur um konsistentes Markup, Umschaltlogik und Escaping.
Für die wiederkehrenden Query-Parameter stehen kleine Admin-Helper bereit:
$orderby = flz_ui_admin_orderby(array('name', 'created_at'), 'name');
$order = flz_ui_admin_order();
$filter = flz_ui_admin_filter_text('search');
flz_ui_admin_compare() kapselt den stabilen Vergleich für in PHP sortierte
Admin-Tabellen.
Filterformulare in Tabellenköpfen nutzen die Klasse
.flz-ui-table-filter-form. Das gemeinsame UI-Script sendet solche GET-Filter
automatisch ab: Texteingaben nach kurzer Entprellung, Selects sofort beim
Wechsel. Ein sichtbarer Filterbutton darf als Fallback für deaktiviertes
JavaScript stehen bleiben.
Floating Action Panel
Für lange Frontend-Seiten mit einer wichtigen Aktion gibt es
floating_action_panel(). Die Komponente rendert einen Sticky-Button und ein
seitliches Panel; auf schmalen Displays wird daraus ein Bottom-Sheet. Ohne
JavaScript bleibt das Panel über den Anker-Link erreichbar.
echo flz_ui()->floating_action_panel(
array(
'id' => 'flz-demo-panel',
'title' => 'Anmeldung',
'button_label' => 'Zur Anmeldung',
'content' => $already_escaped_form_html,
'open' => $has_form_messages,
)
);
content wird bewusst nicht durch wp_kses_post() gefiltert, damit Formulare
und Nonces erhalten bleiben. Aufrufende Plugins müssen den Inhalt deshalb
bereits sicher rendern und escapen.
Datumsformat
Sichtbare Datumswerte werden projektweit deutsch als TT.MM.JJ ausgegeben:
echo esc_html(flz_ui_format_date($mysql_date));
echo esc_html(flz_ui_format_datetime($mysql_datetime));
echo esc_html(flz_ui_format_time($mysql_time));
Buttons
echo flz_ui()->button_new(
array(
'href' => admin_url('admin.php?page=flz-ags&action=new'),
'label' => 'Neue AG',
)
);
echo flz_ui()->button_save(
array(
'label' => 'Speichern',
)
);
echo flz_ui()->button_delete(
array(
'label' => 'Eintrag löschen',
)
);
echo flz_ui()->button_filter(
array(
'label' => 'Liste filtern',
)
);
Buttons für riskante Aktionen bekommen zentral eine auffällige Farbe und eine Sicherheitsabfrage:
button_delete()für Löschenbutton_clear()für Entfernen/Leerenbutton_reset()für Zurücksetzen/Überschreibenbutton_upload()für Upload/Import mit möglicher Datenänderung
Die Standardfrage kann überschrieben oder bewusst deaktiviert werden:
echo flz_ui()->button_reset(
array(
'label' => 'Teilnehmerliste leeren',
'confirm' => 'Wirklich alle Teilnehmer löschen?',
)
);
echo flz_ui()->button_delete(
array(
'label' => 'Testeintrag löschen',
'confirm' => false,
)
);
Icon-Buttons sind im Normalfall icon-only. Der Text wird visuell ausgeblendet, der Browser-Titel erscheint beim Hover. icon_alt muss die Funktion beschreiben, nicht die Grafik:
echo flz_ui()->icon_button(
array(
'icon' => 'plus',
'icon_alt' => 'Neue AG anlegen',
'href' => $url,
'variant' => 'primary',
)
);
Inline-SVGs haben kein alt-Attribut. Der funktionale Alternativtext wird deshalb über role="img", aria-label und title ausgegeben. Beim icon-only Button bekommt zusätzlich der Button selbst aria-label und title.
Formularfelder
Formularhüllen, Nonces und versteckte Felder sollen ebenfalls über die Komponenten laufen. So bleiben Sicherheitsfelder und Markup zentral änderbar:
echo flz_ui()->form_start(
array(
'method' => 'post',
'action' => admin_url('admin-post.php'),
'nonce' => 'flz_ags_save_course',
'hidden' => array(
'action' => 'flz_ags_save_course',
'course_id' => $course_id,
),
'class' => 'flz-ags-admin-form',
)
);
echo flz_ui()->hidden('selected', $selected_id);
echo flz_ui()->form_end();
Für einzelne riskante Aktionen in Tabellen gibt es action_form_button(). Die
Komponente rendert Formular, Hidden Fields, Nonce und Button-Preset zusammen:
echo flz_ui()->action_form_button(
array(
'preset' => 'delete',
'label' => 'Datensatz löschen',
'method' => 'post',
'nonce' => 'flz_admin_action',
'hidden' => array(
'record_delete' => $record_id,
),
)
);
echo flz_ui()->input(
'email',
array(
'name' => 'email',
'label' => 'E-Mail',
'value' => $email,
'required' => true,
'autocomplete'=> 'email',
'errors' => $result ?? array(),
)
);
echo flz_ui()->input(
'time',
array(
'name' => 'start_time',
'label' => 'Beginn',
'value' => $start_time,
'required' => true,
)
);
Unterstützte Feldtypen:
textemailpassworddatetimedatetime-localnumberintegerurltelhiddentextareaselectcheckboxradio
Für Speziallayouts wie Karten oder Tabellen gibt es zusätzlich einzelne Choice-Inputs ohne Wrapper:
echo flz_ui()->radio_input(
array(
'name' => 'slot_id',
'value' => $slot_id,
'checked' => $is_selected,
'required' => true,
)
);
CSV-Panel
Wiederkehrende CSV-Bereiche sollen über csv_panel() laufen. Dadurch bleiben
Format-Hinweis, Export, Upload-Formular und riskante Import-Bestätigung zentral:
echo flz_ui()->csv_panel(
array(
'title' => 'Lehrkräfte CSV',
'description' => 'CSV exportieren oder neue Daten importieren.',
'format' => 'Geschlecht(m/f); Name; Vorname; Email',
'export' => array(
'href' => $csv_url,
'label' => 'CSV herunterladen',
),
'upload' => array(
'file_name' => 'teacher-csv',
'button_label' => 'CSV importieren',
'nonce' => 'flzest_admin_action',
),
)
);
Nicht verarbeitete CSV-Zeilen können zentral angezeigt werden:
echo flz_ui()->csv_unprocessed_notice($unprocessed_rows);
Field Matrix
Für wiederholbare kleine Feldgruppen, z. B. AG-Slots, gibt es field_matrix().
Sie rendert mindestens eine Zeile und ergänzt per Plus-Button weitere leere
Zeilen:
echo flz_ui()->field_matrix(
array(
'id' => 'flz-ags-slots',
'rows' => $slot_rows,
'columns' => array(
array(
'label' => 'Aktiv',
'field' => array(
'type' => 'checkbox',
'name' => 'slots[{index}][is_active]',
'label' => 'aktiv',
'checked_key' => 'is_active',
),
),
array(
'label' => 'Beginn',
'field' => array(
'type' => 'time',
'name' => 'slots[{index}][start_time]',
'value_key' => 'start_time',
'aria_label'=> 'Beginn',
),
),
),
)
);
Cards und Choice Cards
card() und choice_card() bilden ruhige Inhalts- und Auswahlkarten. Die Optik
orientiert sich an den News-Kacheln der Website: Bild oben, klare Typografie,
Kurztext, Metadaten und dezente Aktionen.
echo flz_ui()->card(
array(
'image_url' => $image_url,
'image_alt' => 'Basketball AG',
'title' => 'Basketball AG',
'text' => 'Spaß am Basketballspiel für Neulinge und Fortgeschrittene.',
'meta' => array(
'Zeit' => 'Do, 13:40–15:10',
'Plätze' => '8 frei von 20',
),
'actions' => array(
array('href' => $url, 'label' => 'Details anzeigen'),
),
)
);
echo flz_ui()->choice_card(
array(
'name' => 'slot_id',
'value' => $slot_id,
'checked' => $is_selected,
'required' => true,
'title' => $slot_title,
'meta' => array('Zeit' => $slot_time),
)
);
Editable Rows
Für Backend-Tabellen, in denen ein Datensatz direkt in der Tabellenzeile
bearbeitet werden soll, gibt es editable_row(). Die Komponente setzt sich aus
den vorhandenen Formularfeldern und Buttons zusammen. Das Formular liegt in der
Aktionszelle; die Felder werden über das HTML-Attribut form zugeordnet. So
bleibt das Tabellen-Markup gültig.
echo flz_ui()->button_new(
array(
'label' => 'Neue Schule anlegen',
'attrs' => array(
'data-flz-ui-show-new-row' => 'flz-school-new',
),
)
);
echo flz_ui()->editable_row(
array(
'id' => 'flz-school-new',
'new' => true,
'row_hidden' => true,
'form' => array(
'method' => 'post',
'nonce' => 'flz_admin_action',
'hidden' => array('school_id' => ''),
),
'cells' => array(
array(
'view' => '',
'field' => array(
'type' => 'text',
'name' => 'name',
'label' => 'Name',
'required' => true,
),
),
array(
'view' => '',
'field' => array(
'type' => 'number',
'name' => 'available_seats',
'label' => 'Freie Plätze',
'min' => 0,
),
),
),
'save' => array(
'label' => 'Schule speichern',
'attrs' => array('name' => 'school_submit'),
),
)
);
Wenn nicht alle Formularfelder sinnvoll in die Haupttabelle passen, kann eine Detailzeile ergänzt werden:
'details' => array(
'label' => 'Buchung bearbeiten',
'fields' => array(
array('type' => 'email', 'name' => 'parent[email]', 'label' => 'E-Mail'),
array('type' => 'text', 'name' => 'parent[studentClass]', 'label' => 'Klasse Schüler*in'),
),
),
Validierung
Serverseitige Validierung bleibt führend. HTML5-Attribute und JS sind nur Komfort.
$schema = array(
'email' => array(
'type' => 'email',
'label' => 'E-Mail',
'required' => true,
),
'start_time' => array(
'type' => 'time',
'label' => 'Beginn',
'required' => true,
),
);
$result = flz_ui_validate($_POST, $schema);
if (!$result->is_valid()) {
echo flz_ui()->notice($result->first_error(), 'error');
}
$values = $result->values();
Fehlermeldungen sind deutsch und feldbezogen, z. B.:
„E-Mail“ muss eine gültige E-Mail-Adresse enthalten.„Beginn“ muss eine gültige Uhrzeit im Format HH:MM enthalten.„Name“ ist ein Pflichtfeld.