Let menu entries appear as buttons with icon, colours and phone placement

The menu editor gets "Als Button darstellen" per entry: shape, appearance and
size from the button design, fill and ink with hover and palette, icon colour,
icon from the dialog, motion, icon-only and where the button sits on the phone
(in the opened menu, beside the burger, or there as an icon only). Colours go
into the page stylesheet under a stable per-button id because the CSP forbids
inline styles. ADR 110.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5.5 committed 2026-10-07 22:36:12 +02:00
1 parent 48ae2443cd
commit 13f6759df8
22 files changed
+623 -14

No files matched your search

+1 -1
View File
@@ -492,7 +492,7 @@ final class EditorController extends AbstractController
// CSS, Bereichs- und Sektionsstilen und der Formulare mit ihren Farben. Vorher
// fehlte all das, und "sieht in der Vorschau anders aus" war immer wahr.
$documents = [$document, ...array_values($areas), ...array_values($sections)];
$styles = $this->theme->css($theme);
$styles = $this->theme->css($theme).$this->menus->buttonCss($theme['buttons']);
foreach ($documents as $doc) {
$styles .= $this->styles->compileDocument($doc).$this->code->compileDocument($doc);
}
+12 -1
View File
@@ -2,8 +2,11 @@
namespace App\Controller;
use App\Content\ButtonDesign;
use App\Content\MenuButton;
use App\Content\MenuRepository;
use App\Content\PageRepository;
use App\Content\Theme;
use App\Domain\InvalidMenu;
use App\Domain\StaleWrite;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -14,7 +17,7 @@ use Symfony\Component\Routing\Attribute\Route;
final class MenuController extends AbstractController
{
public function __construct(private MenuRepository $menus, private PageRepository $pages)
public function __construct(private MenuRepository $menus, private PageRepository $pages, private Theme $theme)
{
}
@@ -67,6 +70,7 @@ final class MenuController extends AbstractController
$slot = $request->query->getString('slot', 'header');
$slot = isset(MenuRepository::SLOTS[$slot]) ? $slot : 'header';
$menu = $this->menus->all()[$slot] ?? null;
$theme = $this->theme->load();
$rows = $this->flatten($menu['items'] ?? []);
$selected = [];
foreach ($rows as &$row) {
@@ -81,6 +85,8 @@ final class MenuController extends AbstractController
'slots' => MenuRepository::SLOTS, 'slot' => $slot, 'slotName' => MenuRepository::SLOTS[$slot],
'menu' => $menu, 'rows' => $rows,
'menuIcons' => \App\Content\DocumentValidator::ICONS,
// Fuer die Button-Vorschau und die Farbwahl am Eintrag: dieselben Daten wie im Editor.
'buttonSchema' => ['buttons' => $theme['buttons'], 'colors' => $theme['colors'], 'fonts' => $theme['fonts'], 'buttonOptions' => ButtonDesign::OPTIONS, 'mobile' => MenuButton::MOBILE],
]);
}
@@ -123,6 +129,7 @@ final class MenuController extends AbstractController
'pageId' => $item['pageId'] ?? '', 'anchor' => $item['anchor'] ?? '', 'url' => $item['url'] ?? '',
'newTab' => $item['newTab'] ?? false,
'icon' => $item['icon'] ?? '', 'iconPosition' => $item['iconPosition'] ?? 'before',
'button' => $item['button'] ?? null,
];
array_push($rows, ...$this->flatten($item['children'] ?? [], $level + 1));
}
@@ -152,6 +159,7 @@ final class MenuController extends AbstractController
'icon' => (string) ($row['icon'] ?? ''), 'iconPosition' => (string) ($row['iconPosition'] ?? 'before'),
'level' => max(1, min(MenuRepository::MAX_DEPTH, (int) ($row['level'] ?? 1))),
'position' => (int) ($row['position'] ?? 0),
'button' => MenuButton::fromForm($row),
];
}
usort($flat, static fn (array $a, array $b): int => $a['position'] <=> $b['position']);
@@ -166,6 +174,9 @@ final class MenuController extends AbstractController
if ($row['newTab']) {
$item['newTab'] = true;
}
if (null !== $row['button']) {
$item['button'] = $row['button'];
}
if ('url' === $row['type']) {
$item['url'] = $row['url'];
} else {
+1 -1
View File
@@ -138,7 +138,7 @@ final class PageController extends AbstractController
// for the small script that marks the page as scrolled.
// Den Nach-oben-Knopf blendet dasselbe Skript ein; ohne es bleibt er dauerhaft sichtbar.
$needsScrolled = (isset($areas['header']) && $this->hasScrolled($areas['header'])) || (bool) ($this->settings->load()['scrollTop'] ?? false);
$styles = $this->theme->css($theme).$this->scrollTopStyles($this->settings->load(), $theme).$this->consent->styles($theme).$this->styles->compileDocument($page['document']).$this->code->compileDocument($page['document']);
$styles = $this->theme->css($theme).$this->menus->buttonCss($theme['buttons']).$this->scrollTopStyles($this->settings->load(), $theme).$this->consent->styles($theme).$this->styles->compileDocument($page['document']).$this->code->compileDocument($page['document']);
foreach ([...array_values($areas), ...array_values($sections)] as $extra) {
$styles .= $this->styles->compileDocument($extra).$this->code->compileDocument($extra);
}
+1 -1
View File
@@ -179,7 +179,7 @@ final class PartialController extends AbstractController
}
// Wie die Seitenvorschau: eigenes CSS, Sektionsstile und Formulare mit ihren Farben.
$styles = $this->theme->css($theme).$this->styles->compileDocument($document).$this->code->compileDocument($document);
$styles = $this->theme->css($theme).$this->menus->buttonCss($theme['buttons']).$this->styles->compileDocument($document).$this->code->compileDocument($document);
foreach ($sections as $section) {
$styles .= $this->styles->compileDocument($section).$this->code->compileDocument($section);
}