feat(templates): add archive-header and post-grid components
- Add ArchiveHeader component (schema, renderer, formbuilder) - Add PostGrid component (schema, renderer, formbuilder) - Unify archive templates (home, archive, category, tag, author, date, search) - Add page visibility system with VisibilityDefaults - Register components in AdminDashboardRenderer - Fix boolean conversion in functions-addon.php - All 172 unit tests passed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
314
Public/ArchiveHeader/Infrastructure/Ui/ArchiveHeaderRenderer.php
Normal file
314
Public/ArchiveHeader/Infrastructure/Ui/ArchiveHeaderRenderer.php
Normal file
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Public\ArchiveHeader\Infrastructure\Ui;
|
||||
|
||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||
use ROITheme\Shared\Domain\Entities\Component;
|
||||
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
||||
|
||||
/**
|
||||
* ArchiveHeaderRenderer - Renderiza cabecera dinamica para paginas de archivo
|
||||
*
|
||||
* RESPONSABILIDAD: Generar HTML y CSS del componente Archive Header
|
||||
*
|
||||
* CARACTERISTICAS:
|
||||
* - Deteccion automatica del tipo de archivo (categoria, tag, autor, fecha, busqueda)
|
||||
* - Titulo y descripcion dinamicos
|
||||
* - Contador de posts opcional
|
||||
* - Estilos 100% desde BD via CSSGenerator
|
||||
*
|
||||
* @package ROITheme\Public\ArchiveHeader\Infrastructure\Ui
|
||||
*/
|
||||
final class ArchiveHeaderRenderer implements RendererInterface
|
||||
{
|
||||
private const COMPONENT_NAME = 'archive-header';
|
||||
|
||||
public function __construct(
|
||||
private CSSGeneratorInterface $cssGenerator
|
||||
) {}
|
||||
|
||||
public function render(Component $component): string
|
||||
{
|
||||
$data = $component->getData();
|
||||
|
||||
if (!$this->isEnabled($data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$visibilityClass = $this->getVisibilityClass($data);
|
||||
if ($visibilityClass === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$css = $this->generateCSS($data);
|
||||
$html = $this->buildHTML($data, $visibilityClass);
|
||||
|
||||
return sprintf("<style>%s</style>\n%s", $css, $html);
|
||||
}
|
||||
|
||||
public function supports(string $componentType): bool
|
||||
{
|
||||
return $componentType === self::COMPONENT_NAME;
|
||||
}
|
||||
|
||||
private function isEnabled(array $data): bool
|
||||
{
|
||||
$value = $data['visibility']['is_enabled'] ?? false;
|
||||
return $value === true || $value === '1' || $value === 1;
|
||||
}
|
||||
|
||||
private function getVisibilityClass(array $data): ?string
|
||||
{
|
||||
$showDesktop = $data['visibility']['show_on_desktop'] ?? true;
|
||||
$showDesktop = $showDesktop === true || $showDesktop === '1' || $showDesktop === 1;
|
||||
$showMobile = $data['visibility']['show_on_mobile'] ?? true;
|
||||
$showMobile = $showMobile === true || $showMobile === '1' || $showMobile === 1;
|
||||
|
||||
if (!$showDesktop && !$showMobile) {
|
||||
return null;
|
||||
}
|
||||
if (!$showDesktop && $showMobile) {
|
||||
return 'd-lg-none';
|
||||
}
|
||||
if ($showDesktop && !$showMobile) {
|
||||
return 'd-none d-lg-block';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private function generateCSS(array $data): string
|
||||
{
|
||||
$colors = $data['colors'] ?? [];
|
||||
$spacing = $data['spacing'] ?? [];
|
||||
$typography = $data['typography'] ?? [];
|
||||
$behavior = $data['behavior'] ?? [];
|
||||
|
||||
$cssRules = [];
|
||||
|
||||
// Container
|
||||
$marginTop = $spacing['margin_top'] ?? '2rem';
|
||||
$marginBottom = $spacing['margin_bottom'] ?? '2rem';
|
||||
$padding = $spacing['padding'] ?? '1.5rem';
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.archive-header', [
|
||||
'margin-top' => $marginTop,
|
||||
'margin-bottom' => $marginBottom,
|
||||
'padding' => $padding,
|
||||
]);
|
||||
|
||||
// Sticky behavior
|
||||
$isSticky = $behavior['is_sticky'] ?? false;
|
||||
$isSticky = $isSticky === true || $isSticky === '1' || $isSticky === 1;
|
||||
|
||||
if ($isSticky) {
|
||||
$stickyOffset = $behavior['sticky_offset'] ?? '0';
|
||||
$cssRules[] = $this->cssGenerator->generate('.archive-header', [
|
||||
'position' => 'sticky',
|
||||
'top' => $stickyOffset,
|
||||
'z-index' => '100',
|
||||
'background' => '#ffffff',
|
||||
]);
|
||||
}
|
||||
|
||||
// Title
|
||||
$titleColor = $colors['title_color'] ?? '#0E2337';
|
||||
$titleSize = $typography['title_size'] ?? '2rem';
|
||||
$titleWeight = $typography['title_weight'] ?? '700';
|
||||
$titleMarginBottom = $spacing['title_margin_bottom'] ?? '0.5rem';
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.archive-header__title', [
|
||||
'color' => $titleColor,
|
||||
'font-size' => $titleSize,
|
||||
'font-weight' => $titleWeight,
|
||||
'margin-bottom' => $titleMarginBottom,
|
||||
'line-height' => '1.2',
|
||||
]);
|
||||
|
||||
// Prefix
|
||||
$prefixColor = $colors['prefix_color'] ?? '#6b7280';
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.archive-header__prefix', [
|
||||
'color' => $prefixColor,
|
||||
'font-weight' => '400',
|
||||
]);
|
||||
|
||||
// Description
|
||||
$descColor = $colors['description_color'] ?? '#6b7280';
|
||||
$descSize = $typography['description_size'] ?? '1rem';
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.archive-header__description', [
|
||||
'color' => $descColor,
|
||||
'font-size' => $descSize,
|
||||
'margin-top' => '0.5rem',
|
||||
'line-height' => '1.6',
|
||||
]);
|
||||
|
||||
// Post count badge
|
||||
$countBgColor = $colors['count_bg_color'] ?? '#FF8600';
|
||||
$countTextColor = $colors['count_text_color'] ?? '#ffffff';
|
||||
$countSize = $typography['count_size'] ?? '0.875rem';
|
||||
$countPadding = $spacing['count_padding'] ?? '0.25rem 0.75rem';
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.archive-header__count', [
|
||||
'background-color' => $countBgColor,
|
||||
'color' => $countTextColor,
|
||||
'font-size' => $countSize,
|
||||
'padding' => $countPadding,
|
||||
'border-radius' => '9999px',
|
||||
'font-weight' => '500',
|
||||
'display' => 'inline-block',
|
||||
'margin-left' => '0.75rem',
|
||||
]);
|
||||
|
||||
return implode("\n", $cssRules);
|
||||
}
|
||||
|
||||
private function buildHTML(array $data, string $visibilityClass): string
|
||||
{
|
||||
$content = $data['content'] ?? [];
|
||||
$typography = $data['typography'] ?? [];
|
||||
|
||||
$headingLevel = $typography['heading_level'] ?? 'h1';
|
||||
$showPostCount = $content['show_post_count'] ?? true;
|
||||
$showPostCount = $showPostCount === true || $showPostCount === '1' || $showPostCount === 1;
|
||||
$showDescription = $content['show_description'] ?? true;
|
||||
$showDescription = $showDescription === true || $showDescription === '1' || $showDescription === 1;
|
||||
|
||||
// Get context-specific title and description
|
||||
$titleData = $this->getContextualTitle($content);
|
||||
$title = $titleData['title'];
|
||||
$prefix = $titleData['prefix'];
|
||||
$description = $showDescription ? $titleData['description'] : '';
|
||||
|
||||
// Get post count
|
||||
$postCount = $this->getPostCount();
|
||||
$countSingular = $content['posts_count_singular'] ?? 'publicacion';
|
||||
$countPlural = $content['posts_count_plural'] ?? 'publicaciones';
|
||||
$countText = $postCount === 1 ? $countSingular : $countPlural;
|
||||
|
||||
$containerClass = 'archive-header';
|
||||
if (!empty($visibilityClass)) {
|
||||
$containerClass .= ' ' . $visibilityClass;
|
||||
}
|
||||
|
||||
$html = sprintf('<div class="%s">', esc_attr($containerClass));
|
||||
|
||||
// Title with optional prefix
|
||||
$html .= sprintf('<%s class="archive-header__title">', esc_attr($headingLevel));
|
||||
|
||||
if (!empty($prefix)) {
|
||||
$html .= sprintf(
|
||||
'<span class="archive-header__prefix">%s</span> ',
|
||||
esc_html($prefix)
|
||||
);
|
||||
}
|
||||
|
||||
$html .= esc_html($title);
|
||||
|
||||
// Post count badge
|
||||
if ($showPostCount && $postCount > 0) {
|
||||
$html .= sprintf(
|
||||
'<span class="archive-header__count">%d %s</span>',
|
||||
$postCount,
|
||||
esc_html($countText)
|
||||
);
|
||||
}
|
||||
|
||||
$html .= sprintf('</%s>', esc_attr($headingLevel));
|
||||
|
||||
// Description
|
||||
if (!empty($description)) {
|
||||
$html .= sprintf(
|
||||
'<p class="archive-header__description">%s</p>',
|
||||
esc_html($description)
|
||||
);
|
||||
}
|
||||
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contextual title based on current page type
|
||||
*
|
||||
* @param array $content Content settings from schema
|
||||
* @return array{title: string, prefix: string, description: string}
|
||||
*/
|
||||
private function getContextualTitle(array $content): array
|
||||
{
|
||||
$title = '';
|
||||
$prefix = '';
|
||||
$description = '';
|
||||
|
||||
if (is_category()) {
|
||||
$prefix = $content['category_prefix'] ?? 'Categoria:';
|
||||
$title = single_cat_title('', false) ?: '';
|
||||
$description = category_description() ?: '';
|
||||
} elseif (is_tag()) {
|
||||
$prefix = $content['tag_prefix'] ?? 'Etiqueta:';
|
||||
$title = single_tag_title('', false) ?: '';
|
||||
$description = tag_description() ?: '';
|
||||
} elseif (is_author()) {
|
||||
$prefix = $content['author_prefix'] ?? 'Articulos de:';
|
||||
$title = get_the_author() ?: '';
|
||||
$description = get_the_author_meta('description') ?: '';
|
||||
} elseif (is_date()) {
|
||||
$prefix = $content['date_prefix'] ?? 'Archivo:';
|
||||
$title = $this->getDateArchiveTitle();
|
||||
$description = '';
|
||||
} elseif (is_search()) {
|
||||
$prefix = $content['search_prefix'] ?? 'Resultados para:';
|
||||
$title = get_search_query() ?: '';
|
||||
$description = '';
|
||||
} elseif (is_home()) {
|
||||
$prefix = '';
|
||||
$title = $content['blog_title'] ?? 'Blog';
|
||||
$description = '';
|
||||
} elseif (is_archive()) {
|
||||
$prefix = '';
|
||||
$title = get_the_archive_title() ?: 'Archivo';
|
||||
$description = get_the_archive_description() ?: '';
|
||||
} else {
|
||||
$prefix = '';
|
||||
$title = $content['blog_title'] ?? 'Blog';
|
||||
$description = '';
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => $title,
|
||||
'prefix' => $prefix,
|
||||
'description' => strip_tags($description),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted title for date archives
|
||||
*/
|
||||
private function getDateArchiveTitle(): string
|
||||
{
|
||||
if (is_day()) {
|
||||
return get_the_date();
|
||||
} elseif (is_month()) {
|
||||
return get_the_date('F Y');
|
||||
} elseif (is_year()) {
|
||||
return get_the_date('Y');
|
||||
}
|
||||
return get_the_archive_title() ?: '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total post count for current query
|
||||
*/
|
||||
private function getPostCount(): int
|
||||
{
|
||||
global $wp_query;
|
||||
return $wp_query->found_posts ?? 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user