` ins Stylesheet der Seite * geschrieben. * * Gespeichert am Eintrag als `button`: * {id, shape?, appearance?, size?, fill?, ink?, hoverFill?, hoverInk?, iconColor?, mobile, iconMotion?, iconOnly?} */ final class MenuButton { /** Wo der Button auf dem Telefon steht, wenn das Menü zum Menü-Knopf zusammenklappt. */ public const MOBILE = ['menu' => 'Im aufgeklappten Menü', 'bar' => 'Neben dem Menü-Knopf', 'bar-icon' => 'Neben dem Menü-Knopf, nur als Symbol']; public const MOTION = ['none', 'slide', 'reveal']; /** Formularfeld => gespeicherter Schlüssel. */ public const COLORS = ['buttonFill' => 'fill', 'buttonInk' => 'ink', 'buttonHoverFill' => 'hoverFill', 'buttonHoverInk' => 'hoverInk', 'buttonIconColor' => 'iconColor']; private const DESIGN = ['buttonShape' => 'shape', 'buttonAppearance' => 'appearance', 'buttonSize' => 'size']; /** * Aus den flachen Formularfeldern einer Zeile. Ohne Häkchen „Als Button“ kein Button. */ public static function fromForm(array $row): ?array { if ('1' !== (string) ($row['button'] ?? '')) { return null; } $id = (string) ($row['buttonId'] ?? ''); $button = ['id' => 1 === preg_match('/^m[a-f0-9]{10}$/D', $id) ? $id : 'm'.bin2hex(random_bytes(5))]; foreach ([...self::DESIGN, ...self::COLORS] as $field => $key) { $value = trim((string) ($row[$field] ?? '')); if ('' !== $value) { $button[$key] = $value; } } $button['mobile'] = (string) ($row['buttonMobile'] ?? 'menu'); $motion = (string) ($row['iconMotion'] ?? 'none'); if ('none' !== $motion) { $button['iconMotion'] = $motion; } if ('1' === (string) ($row['iconOnly'] ?? '')) { $button['iconOnly'] = true; } return $button; } public static function validate(mixed $button, array $item): void { if (!is_array($button) || 1 !== preg_match('/^m[a-f0-9]{10}$/D', (string) ($button['id'] ?? ''))) { throw new InvalidMenu('Ein Menü-Button braucht eine Kennung.'); } $known = ['id', 'mobile', 'iconMotion', 'iconOnly', ...array_values(self::DESIGN), ...array_values(self::COLORS)]; if ([] !== array_diff(array_keys($button), $known)) { throw new InvalidMenu('Unbekannte Einstellung am Menü-Button.'); } foreach (self::DESIGN as $key) { if (isset($button[$key]) && !isset(ButtonDesign::OPTIONS[$key][$button[$key]])) { throw new InvalidMenu('Unbekannte Form, Darstellung oder Größe am Menü-Button.'); } } foreach (self::COLORS as $key) { if (isset($button[$key]) && (!is_string($button[$key]) || 1 !== preg_match(StyleSchema::COLOR, $button[$key]))) { throw new InvalidMenu('Eine Farbe am Menü-Button ist ein Hexwert wie #b71c22 oder eine Farbe der Palette.'); } } if (!isset(self::MOBILE[$button['mobile'] ?? ''])) { throw new InvalidMenu('Unbekannte Anzeige auf dem Telefon.'); } // Auf dem Telefon nur als Symbol: ohne Symbol stünde dort ein leerer Knopf. if ('bar-icon' === $button['mobile'] && '' === ($item['icon'] ?? '')) { throw new InvalidMenu('Nur als Symbol neben dem Menü-Knopf braucht ein Symbol.'); } if (isset($button['iconMotion']) && !in_array($button['iconMotion'], self::MOTION, true)) { throw new InvalidMenu('Unbekannte Bewegung des Symbols.'); } if (isset($button['iconOnly']) && (true !== $button['iconOnly'] || '' === ($item['icon'] ?? ''))) { throw new InvalidMenu('Ein Menü-Button nur aus Symbol braucht ein Symbol.'); } // Ein Knopf öffnet kein Untermenü: mit Unterpunkten wäre er Knopf und Aufklapper zugleich. if ([] !== ($item['children'] ?? [])) { throw new InvalidMenu(sprintf('„%s“ hat Unterpunkte und kann deshalb kein Button sein.', $item['label'] ?? '')); } } /** * Die Farbregeln aller Menü-Buttons. Gesetzt werden die Variablen, aus denen die Button-Stile * ihre Flächen berechnen, plus die abgeleiteten Werte der gewählten oder der Standard- * Darstellung - sonst rechnete ein Button ohne eigene Darstellung weiter mit den Farben vom * Seitenanfang. * * @param array $menus */ public static function css(array $menus, array $themeButtons): string { $color = static fn (string $v): string => str_starts_with($v, '@') ? 'var(--k-'.substr($v, 1).')' : $v; $css = ''; $walk = function (array $items) use (&$walk, &$css, $color, $themeButtons): void { foreach ($items as $item) { $walk($item['children'] ?? []); $button = $item['button'] ?? null; if (!is_array($button) || !isset($button['id'])) { continue; } $vars = []; foreach (['fill' => '--kb-fill', 'ink' => '--kb-ink', 'hoverFill' => '--kb-hover-fill', 'hoverInk' => '--kb-hover-ink'] as $key => $var) { if (isset($button[$key])) { $vars[] = $var.':'.$color($button[$key]); } } if ([] !== $vars) { foreach (ButtonDesign::appearance($button['appearance'] ?? $themeButtons['appearance'] ?? 'solid') as $key => $value) { $vars[] = '--kb-'.$key.':'.$value; } $css .= sprintf('.menu-button-%s{%s}', $button['id'], implode(';', $vars)); } if (isset($button['iconColor'])) { $css .= sprintf('.menu-button-%s .button-icon{color:%s}', $button['id'], $color($button['iconColor'])); } } }; foreach ($menus as $menu) { $walk($menu['items'] ?? []); } return $css; } }