- 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>
281 lines
9.4 KiB
PHP
281 lines
9.4 KiB
PHP
<?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;
|
|
}
|
|
}
|