Migración completa a Clean Architecture con componentes funcionales
- Reorganización de estructura: Admin/, Public/, Shared/, Schemas/ - 12 componentes migrados: TopNotificationBar, Navbar, CtaLetsTalk, Hero, FeaturedImage, TableOfContents, CtaBoxSidebar, SocialShare, CtaPost, RelatedPost, ContactForm, Footer - Panel de administración con tabs Bootstrap 5 funcionales - Schemas JSON para configuración de componentes - Renderers dinámicos con CSSGeneratorService (cero CSS hardcodeado) - FormBuilders para UI admin con Design System consistente - Fix: Bootstrap JS cargado en header para tabs funcionales - Fix: buildTextInput maneja valores mixed (bool/string) - Eliminación de estructura legacy (src/, admin/, assets/css/componente-*) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
280
Public/CtaBoxSidebar/Infrastructure/Ui/CtaBoxSidebarRenderer.php
Normal file
280
Public/CtaBoxSidebar/Infrastructure/Ui/CtaBoxSidebarRenderer.php
Normal file
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Public\CtaBoxSidebar\Infrastructure\Ui;
|
||||
|
||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||
use ROITheme\Shared\Domain\Entities\Component;
|
||||
|
||||
/**
|
||||
* CtaBoxSidebarRenderer - Renderiza caja CTA en sidebar
|
||||
*
|
||||
* RESPONSABILIDAD: Generar HTML y CSS del CTA Box Sidebar
|
||||
*
|
||||
* CARACTERISTICAS:
|
||||
* - Titulo configurable
|
||||
* - Descripcion configurable
|
||||
* - Boton con icono y multiples acciones (modal, link, scroll)
|
||||
* - Estilos 100% desde BD via CSSGenerator
|
||||
*
|
||||
* Cumple con:
|
||||
* - DIP: Recibe CSSGeneratorInterface por constructor
|
||||
* - SRP: Una responsabilidad (renderizar CTA box)
|
||||
* - Clean Architecture: Infrastructure puede usar WordPress
|
||||
*
|
||||
* @package ROITheme\Public\CtaBoxSidebar\Infrastructure\Ui
|
||||
*/
|
||||
final class CtaBoxSidebarRenderer implements RendererInterface
|
||||
{
|
||||
public function __construct(
|
||||
private CSSGeneratorInterface $cssGenerator
|
||||
) {}
|
||||
|
||||
public function render(Component $component): string
|
||||
{
|
||||
$data = $component->getData();
|
||||
|
||||
if (!$this->isEnabled($data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!$this->shouldShowOnCurrentPage($data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$css = $this->generateCSS($data);
|
||||
$html = $this->buildHTML($data);
|
||||
$script = $this->buildScript();
|
||||
|
||||
return sprintf("<style>%s</style>\n%s\n%s", $css, $html, $script);
|
||||
}
|
||||
|
||||
public function supports(string $componentType): bool
|
||||
{
|
||||
return $componentType === 'cta-box-sidebar';
|
||||
}
|
||||
|
||||
private function isEnabled(array $data): bool
|
||||
{
|
||||
return ($data['visibility']['is_enabled'] ?? false) === true;
|
||||
}
|
||||
|
||||
private function shouldShowOnCurrentPage(array $data): bool
|
||||
{
|
||||
$showOn = $data['visibility']['show_on_pages'] ?? 'posts';
|
||||
|
||||
switch ($showOn) {
|
||||
case 'all':
|
||||
return true;
|
||||
case 'posts':
|
||||
return is_single();
|
||||
case 'pages':
|
||||
return is_page();
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private function generateCSS(array $data): string
|
||||
{
|
||||
$colors = $data['colors'] ?? [];
|
||||
$spacing = $data['spacing'] ?? [];
|
||||
$typography = $data['typography'] ?? [];
|
||||
$effects = $data['visual_effects'] ?? [];
|
||||
$behavior = $data['behavior'] ?? [];
|
||||
$visibility = $data['visibility'] ?? [];
|
||||
|
||||
$cssRules = [];
|
||||
$transitionDuration = $effects['transition_duration'] ?? '0.3s';
|
||||
|
||||
// Container styles - Match template exactly (height: 250px, flexbox centering)
|
||||
$cssRules[] = $this->cssGenerator->generate('.cta-box-sidebar', [
|
||||
'background' => $colors['background_color'] ?? '#FF8600',
|
||||
'border-radius' => $effects['border_radius'] ?? '8px',
|
||||
'padding' => $spacing['container_padding'] ?? '24px',
|
||||
'text-align' => $behavior['text_align'] ?? 'center',
|
||||
'box-shadow' => $effects['box_shadow'] ?? '0 4px 12px rgba(255, 133, 0, 0.2)',
|
||||
'margin-top' => '0',
|
||||
'margin-bottom' => '15px',
|
||||
'height' => '250px',
|
||||
'display' => 'flex',
|
||||
'flex-direction' => 'column',
|
||||
'justify-content' => 'center',
|
||||
]);
|
||||
|
||||
// Title styles
|
||||
$cssRules[] = $this->cssGenerator->generate('.cta-box-sidebar .cta-box-title', [
|
||||
'color' => $colors['title_color'] ?? '#ffffff',
|
||||
'font-weight' => $typography['title_font_weight'] ?? '700',
|
||||
'font-size' => $typography['title_font_size'] ?? '1.25rem',
|
||||
'margin-bottom' => $spacing['title_margin_bottom'] ?? '1rem',
|
||||
'margin-top' => '0',
|
||||
]);
|
||||
|
||||
// Description styles
|
||||
$cssRules[] = $this->cssGenerator->generate('.cta-box-sidebar .cta-box-text', [
|
||||
'color' => $colors['description_color'] ?? 'rgba(255, 255, 255, 0.95)',
|
||||
'font-size' => $typography['description_font_size'] ?? '0.9rem',
|
||||
'margin-bottom' => $spacing['description_margin_bottom'] ?? '1rem',
|
||||
]);
|
||||
|
||||
// Button styles
|
||||
$cssRules[] = $this->cssGenerator->generate('.cta-box-sidebar .btn-cta-box', [
|
||||
'background-color' => $colors['button_background_color'] ?? '#ffffff',
|
||||
'color' => $colors['button_text_color'] ?? '#FF8600',
|
||||
'font-weight' => $typography['button_font_weight'] ?? '700',
|
||||
'font-size' => $typography['button_font_size'] ?? '1rem',
|
||||
'border' => 'none',
|
||||
'padding' => $spacing['button_padding'] ?? '0.75rem 1.5rem',
|
||||
'border-radius' => $effects['button_border_radius'] ?? '8px',
|
||||
'transition' => "all {$transitionDuration} ease",
|
||||
'cursor' => 'pointer',
|
||||
'display' => 'inline-flex',
|
||||
'align-items' => 'center',
|
||||
'justify-content' => 'center',
|
||||
'width' => '100%',
|
||||
]);
|
||||
|
||||
// Button hover styles (template uses --color-navy-primary = #1e3a5f)
|
||||
$cssRules[] = $this->cssGenerator->generate('.cta-box-sidebar .btn-cta-box:hover', [
|
||||
'background-color' => $colors['button_hover_background'] ?? '#1e3a5f',
|
||||
'color' => $colors['button_hover_text_color'] ?? '#ffffff',
|
||||
]);
|
||||
|
||||
// Button icon spacing
|
||||
$cssRules[] = $this->cssGenerator->generate('.cta-box-sidebar .btn-cta-box i', [
|
||||
'margin-right' => $spacing['icon_margin_right'] ?? '0.5rem',
|
||||
]);
|
||||
|
||||
// Responsive visibility
|
||||
$showOnDesktop = $visibility['show_on_desktop'] ?? true;
|
||||
$showOnMobile = $visibility['show_on_mobile'] ?? false;
|
||||
|
||||
if (!$showOnMobile) {
|
||||
$cssRules[] = "@media (max-width: 991.98px) {
|
||||
.cta-box-sidebar { display: none !important; }
|
||||
}";
|
||||
}
|
||||
|
||||
if (!$showOnDesktop) {
|
||||
$cssRules[] = "@media (min-width: 992px) {
|
||||
.cta-box-sidebar { display: none !important; }
|
||||
}";
|
||||
}
|
||||
|
||||
return implode("\n", $cssRules);
|
||||
}
|
||||
|
||||
private function buildHTML(array $data): string
|
||||
{
|
||||
$content = $data['content'] ?? [];
|
||||
|
||||
$title = $content['title'] ?? '¿Listo para potenciar tus proyectos?';
|
||||
$description = $content['description'] ?? 'Accede a nuestra biblioteca completa de APUs y herramientas profesionales.';
|
||||
$buttonText = $content['button_text'] ?? 'Solicitar Demo';
|
||||
$buttonIcon = $content['button_icon'] ?? 'bi bi-calendar-check';
|
||||
$buttonAction = $content['button_action'] ?? 'modal';
|
||||
$buttonLink = $content['button_link'] ?? '#contactModal';
|
||||
|
||||
// Build button attributes based on action type
|
||||
$buttonAttributes = $this->getButtonAttributes($buttonAction, $buttonLink);
|
||||
|
||||
$html = '<div class="cta-box-sidebar">';
|
||||
|
||||
// Title
|
||||
$html .= sprintf(
|
||||
'<h5 class="cta-box-title">%s</h5>',
|
||||
esc_html($title)
|
||||
);
|
||||
|
||||
// Description
|
||||
$html .= sprintf(
|
||||
'<p class="cta-box-text">%s</p>',
|
||||
esc_html($description)
|
||||
);
|
||||
|
||||
// Button
|
||||
$iconHtml = !empty($buttonIcon)
|
||||
? sprintf('<i class="%s"></i>', esc_attr($buttonIcon))
|
||||
: '';
|
||||
|
||||
$html .= sprintf(
|
||||
'<button class="btn btn-cta-box" %s>%s%s</button>',
|
||||
$buttonAttributes,
|
||||
$iconHtml,
|
||||
esc_html($buttonText)
|
||||
);
|
||||
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function getButtonAttributes(string $action, string $link): string
|
||||
{
|
||||
switch ($action) {
|
||||
case 'modal':
|
||||
// Extract modal ID from link (e.g., #contactModal -> contactModal)
|
||||
$modalId = ltrim($link, '#');
|
||||
return sprintf(
|
||||
'type="button" data-bs-toggle="modal" data-bs-target="#%s"',
|
||||
esc_attr($modalId)
|
||||
);
|
||||
|
||||
case 'link':
|
||||
return sprintf(
|
||||
'type="button" data-cta-action="link" data-cta-href="%s"',
|
||||
esc_url($link)
|
||||
);
|
||||
|
||||
case 'scroll':
|
||||
$targetId = ltrim($link, '#');
|
||||
return sprintf(
|
||||
'type="button" data-cta-action="scroll" data-cta-target="%s"',
|
||||
esc_attr($targetId)
|
||||
);
|
||||
|
||||
default:
|
||||
return 'type="button"';
|
||||
}
|
||||
}
|
||||
|
||||
private function getVisibilityClasses(bool $desktop, bool $mobile): ?string
|
||||
{
|
||||
if (!$desktop && !$mobile) {
|
||||
return null;
|
||||
}
|
||||
if (!$desktop && $mobile) {
|
||||
return 'd-lg-none';
|
||||
}
|
||||
if ($desktop && !$mobile) {
|
||||
return 'd-none d-lg-block';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private function buildScript(): string
|
||||
{
|
||||
return <<<JS
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var ctaButtons = document.querySelectorAll('.btn-cta-box[data-cta-action]');
|
||||
ctaButtons.forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var action = this.getAttribute('data-cta-action');
|
||||
if (action === 'link') {
|
||||
var href = this.getAttribute('data-cta-href');
|
||||
if (href) window.location.href = href;
|
||||
} else if (action === 'scroll') {
|
||||
var target = this.getAttribute('data-cta-target');
|
||||
var el = document.getElementById(target);
|
||||
if (el) el.scrollIntoView({behavior: 'smooth'});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
JS;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user