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:
1 parent
48ae2443cd
commit
13f6759df8
22 files changed
+623
-14
No files matched your search
@@ -737,6 +737,80 @@ final class CmsTest extends TestCase
|
||||
self::assertStringNotContainsString('<a href="/" aria-current="page">', $other);
|
||||
}
|
||||
|
||||
/**
|
||||
* ADR 110: ein Menüeintrag als Button - dieselben Klassen wie der Button-Baustein, eigene
|
||||
* Farben als Regel im Stylesheet der Seite, und auf dem Telefon wahlweise neben dem Menü-Knopf.
|
||||
*/
|
||||
public function testAMenuEntryRendersAsAButtonWithItsOwnColoursAndStaysBesideTheBurger(): void
|
||||
{
|
||||
$this->menus->save('header', 'Hauptnavigation', [
|
||||
['label' => 'Start', 'type' => 'page', 'pageId' => 'page-1', 'anchor' => '', 'children' => []],
|
||||
['label' => 'Angebot anfragen', 'type' => 'url', 'url' => '#kontakt', 'icon' => 'arrow-right', 'iconPosition' => 'after', 'children' => [],
|
||||
'button' => ['id' => 'mabcdef0123', 'shape' => 'pill', 'appearance' => 'solid', 'fill' => '#b71c22', 'ink' => '#ffffff', 'hoverFill' => '@ink', 'iconColor' => '#ffd700', 'mobile' => 'bar', 'iconMotion' => 'slide']],
|
||||
['label' => 'Anrufen', 'type' => 'url', 'url' => 'tel:+4972296992826', 'icon' => 'phone', 'children' => [],
|
||||
'button' => ['id' => 'm0123456789', 'appearance' => 'outline', 'mobile' => 'menu', 'iconOnly' => true]],
|
||||
['label' => 'Hotline', 'type' => 'url', 'url' => 'tel:+497229', 'icon' => 'phone', 'children' => [],
|
||||
'button' => ['id' => 'm1111111111', 'mobile' => 'bar-icon']],
|
||||
], 0);
|
||||
$document = DemoContent::starter('Mit Menü');
|
||||
$document['schemaVersion'] = 22;
|
||||
$document['blocks'][0]['children'][] = ['id' => 'nav', 'type' => 'menu', 'props' => ['slot' => 'header', 'layout' => 'horizontal', 'collapse' => 'tablet']];
|
||||
$this->pages->publish('page-1', $this->pages->saveDraft('page-1', $document, 0), 1);
|
||||
$response = $this->request('/');
|
||||
$html = (string) $response->getContent();
|
||||
$crawler = new \Symfony\Component\DomCrawler\Crawler($html);
|
||||
|
||||
$offer = $crawler->filter('#nav > .menu-list a.menu-button-mabcdef0123');
|
||||
self::assertSame('button cms-button menu-button menu-button-mabcdef0123 b-desktop-shape-pill b-desktop-appearance-solid icon-motion-slide', $offer->attr('class'));
|
||||
self::assertSame('#kontakt', $offer->attr('href'));
|
||||
self::assertSame('Angebot anfragen', $offer->filter('.button-label')->text());
|
||||
self::assertCount(1, $offer->filter('.button-icon svg'));
|
||||
self::assertSame('menu-item-button menu-keep-visible', $offer->closest('li')->attr('class'));
|
||||
// Beim Menü-Knopf steht er ein zweites Mal - sichtbar nur, solange das Menü zusammengeklappt ist.
|
||||
self::assertCount(1, $crawler->filter('#nav > ul.menu-actions a.menu-button-mabcdef0123'));
|
||||
self::assertCount(0, $crawler->filter('#nav > ul.menu-actions a.menu-button-m0123456789'));
|
||||
// Nur Symbol: die Beschriftung bleibt als Name des Links.
|
||||
$call = $crawler->filter('#nav > .menu-list a.menu-button-m0123456789');
|
||||
self::assertStringContainsString('icon-start icon-only', (string) $call->attr('class'));
|
||||
self::assertSame('Anrufen', $call->filter('.button-label')->text());
|
||||
// Neben dem Menü-Knopf nur als Symbol, im Menü selbst mit Beschriftung.
|
||||
self::assertStringContainsString('icon-only', (string) $crawler->filter('#nav > ul.menu-actions a.menu-button-m1111111111')->attr('class'));
|
||||
self::assertStringNotContainsString('icon-only', (string) $crawler->filter('#nav > .menu-list a.menu-button-m1111111111')->attr('class'));
|
||||
self::assertSame('Hotline', $crawler->filter('#nav > ul.menu-actions a.menu-button-m1111111111 .button-label')->text());
|
||||
// Gewöhnliche Einträge bleiben gewöhnliche Links.
|
||||
self::assertCount(1, $crawler->filter('#nav > .menu-list a[href="/"] .nav-label'));
|
||||
|
||||
self::assertStringContainsString('.menu-button-mabcdef0123{--kb-fill:#b71c22;--kb-ink:#ffffff;--kb-hover-fill:var(--k-ink);--kb-bg:var(--kb-fill)', $html);
|
||||
self::assertStringContainsString('.menu-button-mabcdef0123 .button-icon{color:#ffd700}', $html);
|
||||
self::assertStringNotContainsString('.menu-button-m0123456789{', $html, 'Ohne eigene Farben keine Regel.');
|
||||
$css = (string) file_get_contents(__DIR__.'/../public/assets/site.css');
|
||||
self::assertStringContainsString('.menu-collapse-tablet>.menu-actions{display:flex}', $css);
|
||||
self::assertStringContainsString('.menu-collapse-tablet .menu-burger-body .menu-keep-visible{display:none}', $css);
|
||||
}
|
||||
|
||||
/** @return iterable<string, array{array}> */
|
||||
public static function rejectedMenuButtons(): iterable
|
||||
{
|
||||
$entry = ['label' => 'Angebot', 'type' => 'url', 'url' => '/kontakt', 'children' => []];
|
||||
$button = ['id' => 'mabcdef0123', 'mobile' => 'menu'];
|
||||
yield 'ohne Kennung' => [[...$entry, 'button' => ['mobile' => 'menu']]];
|
||||
yield 'unbekannte Form' => [[...$entry, 'button' => [...$button, 'shape' => 'round']]];
|
||||
yield 'Farbe als Wort' => [[...$entry, 'button' => [...$button, 'fill' => 'red']]];
|
||||
yield 'CSS in der Farbe' => [[...$entry, 'button' => [...$button, 'ink' => '#fff;}body{display:none']]];
|
||||
yield 'unbekannte Anzeige auf dem Telefon' => [[...$entry, 'button' => [...$button, 'mobile' => 'oben']]];
|
||||
yield 'unbekannte Einstellung' => [[...$entry, 'button' => [...$button, 'style' => 'x']]];
|
||||
yield 'neben dem Menü-Knopf nur als Symbol, ohne Symbol' => [[...$entry, 'button' => [...$button, 'mobile' => 'bar-icon']]];
|
||||
yield 'nur Symbol ohne Symbol' => [[...$entry, 'button' => [...$button, 'iconOnly' => true]]];
|
||||
yield 'Button mit Unterpunkten' => [[...$entry, 'button' => $button, 'children' => [['label' => 'Unter', 'type' => 'url', 'url' => '/a', 'children' => []]]]];
|
||||
}
|
||||
|
||||
#[DataProvider('rejectedMenuButtons')]
|
||||
public function testAMenuButtonRejectsUnknownOrUnsafeSettings(array $entry): void
|
||||
{
|
||||
$this->expectException(InvalidMenu::class);
|
||||
$this->menus->save('header', 'Hauptnavigation', [$entry], 0);
|
||||
}
|
||||
|
||||
public function testMenuSurvivesDeletedAndUnpublishedPagesWithoutLosingOtherEntries(): void
|
||||
{
|
||||
$doomed = $this->publishedPage('temporaer', 'Temporaer');
|
||||
|
||||
Reference in new issue
Block a user