getData(); // Validar visibilidad general if (!$this->isEnabled($data)) { return ''; } // Validar visibilidad por página if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) { return ''; } // Generar CSS usando CSSGeneratorService $css = $this->generateCSS($data); // Generar HTML $html = $this->buildHTML($data); // Combinar todo return sprintf( "\n%s", $css, $html ); } /** * {@inheritDoc} */ public function supports(string $componentType): bool { return $componentType === self::COMPONENT_NAME; } /** * Verificar si el componente está habilitado * * @param array $data Datos del componente * @return bool */ private function isEnabled(array $data): bool { return ($data['visibility']['is_enabled'] ?? false) === true; } /** * Calcular clases de visibilidad responsive * * @param bool $desktop Mostrar en desktop * @param bool $mobile Mostrar en mobile * @return string|null Clases CSS o null si no debe mostrarse */ 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 ''; } /** * Generar CSS usando CSSGeneratorService * * @param array $data Datos del componente * @return string CSS generado */ public function generateCSS(array $data): string { $css = ''; // Estilos base del botón $baseStyles = [ 'background_color' => $data['colors']['background_color'] ?? '#FF8600', 'color' => $data['colors']['text_color'] ?? '#FFFFFF', 'font_size' => $data['typography']['font_size'] ?? '1rem', 'font_weight' => $data['typography']['font_weight'] ?? '600', 'text_transform' => $data['typography']['text_transform'] ?? 'none', 'padding' => sprintf( '%s %s', $data['spacing']['padding_top_bottom'] ?? '0.5rem', $data['spacing']['padding_left_right'] ?? '1.5rem' ), 'border' => sprintf( '%s solid %s', $data['visual_effects']['border_width'] ?? '0', $data['colors']['border_color'] ?? 'transparent' ), 'border_radius' => $data['visual_effects']['border_radius'] ?? '6px', 'box_shadow' => $data['visual_effects']['box_shadow'] ?? 'none', 'transition' => sprintf( 'all %s ease', $data['visual_effects']['transition_duration'] ?? '0.3s' ), 'cursor' => 'pointer', ]; $css .= $this->cssGenerator->generate('.btn-lets-talk', $baseStyles); // Estilos hover del botón $hoverStyles = [ 'background_color' => $data['colors']['background_hover_color'] ?? '#FF6B35', 'color' => $data['colors']['text_hover_color'] ?? '#FFFFFF', ]; $css .= "\n" . $this->cssGenerator->generate('.btn-lets-talk:hover', $hoverStyles); // Estilos del ícono dentro del botón $iconStyles = [ 'color' => $data['colors']['text_color'] ?? '#FFFFFF', 'margin_right' => $data['spacing']['icon_spacing'] ?? '0.5rem', ]; $css .= "\n" . $this->cssGenerator->generate('.btn-lets-talk i', $iconStyles); // Estilos responsive - ocultar en móvil si show_on_mobile = false $showOnMobile = ($data['visibility']['show_on_mobile'] ?? false) === true; if (!$showOnMobile) { $responsiveStyles = [ 'display' => 'none !important', ]; $css .= "\n@media (max-width: 991px) {\n"; $css .= $this->cssGenerator->generate('.btn-lets-talk', $responsiveStyles); $css .= "\n}"; } // Estilos responsive - ocultar en desktop si show_on_desktop = false $showOnDesktop = ($data['visibility']['show_on_desktop'] ?? true) === true; if (!$showOnDesktop) { $responsiveStyles = [ 'display' => 'none !important', ]; $css .= "\n@media (min-width: 992px) {\n"; $css .= $this->cssGenerator->generate('.btn-lets-talk', $responsiveStyles); $css .= "\n}"; } // Margen izquierdo para separar del menú (solo desktop) $marginLeft = $data['spacing']['margin_left'] ?? '1rem'; if (!empty($marginLeft) && $marginLeft !== '0') { $css .= "\n@media (min-width: 992px) {\n"; $css .= $this->cssGenerator->generate('.btn-lets-talk', ['margin_left' => $marginLeft]); $css .= "\n}"; } return $css; } /** * Generar HTML del componente * * @param array $data Datos del componente * @return string HTML generado */ private function buildHTML(array $data): string { $classes = $this->buildClasses($data); $attributes = $this->buildAttributes($data); $content = $this->buildContent($data); $tag = $this->useModal($data) ? 'button' : 'a'; return sprintf( '<%s class="%s"%s>%s', $tag, esc_attr($classes), $attributes, $content, $tag ); } /** * Construir clases CSS del componente * * @param array $data Datos del componente * @return string Clases CSS */ private function buildClasses(array $data): string { $classes = ['btn', 'btn-lets-talk']; // Agregar clase ms-lg-3 para margen en desktop (Bootstrap) // Esto solo aplica en pantallas >= lg (992px) $classes[] = 'ms-lg-3'; return implode(' ', $classes); } /** * Determinar si debe usar modal o URL * * @param array $data Datos del componente * @return bool */ private function useModal(array $data): bool { return ($data['behavior']['enable_modal'] ?? true) === true; } /** * Construir atributos HTML del componente * * @param array $data Datos del componente * @return string Atributos HTML */ private function buildAttributes(array $data): string { $attributes = []; if ($this->useModal($data)) { // Atributos para modal de Bootstrap $attributes[] = 'type="button"'; $attributes[] = 'data-bs-toggle="modal"'; $modalTarget = $data['content']['modal_target'] ?? '#contactModal'; $attributes[] = sprintf('data-bs-target="%s"', esc_attr($modalTarget)); } else { // Atributos para enlace $customUrl = $data['behavior']['custom_url'] ?? ''; $attributes[] = sprintf('href="%s"', esc_url($customUrl ?: '#')); if (($data['behavior']['open_in_new_tab'] ?? false) === true) { $attributes[] = 'target="_blank"'; $attributes[] = 'rel="noopener noreferrer"'; } } // Atributo ARIA para accesibilidad $ariaLabel = $data['content']['aria_label'] ?? 'Abrir formulario de contacto'; if (!empty($ariaLabel)) { $attributes[] = sprintf('aria-label="%s"', esc_attr($ariaLabel)); } return !empty($attributes) ? ' ' . implode(' ', $attributes) : ''; } /** * Construir contenido del botón * * @param array $data Datos del componente * @return string HTML del contenido */ private function buildContent(array $data): string { $html = ''; // Ícono (si está habilitado) if ($this->shouldShowIcon($data)) { $html .= $this->buildIcon($data); } // Texto del botón $buttonText = $data['content']['button_text'] ?? "Let's Talk"; $html .= esc_html($buttonText); return $html; } /** * Verificar si debe mostrar el ícono * * @param array $data Datos del componente * @return bool */ private function shouldShowIcon(array $data): bool { return ($data['content']['show_icon'] ?? true) === true; } /** * Construir ícono del componente * * @param array $data Datos del componente * @return string HTML del ícono */ private function buildIcon(array $data): string { $iconClass = $data['content']['icon_class'] ?? 'bi-lightning-charge-fill'; // Asegurar prefijo 'bi-' if (strpos($iconClass, 'bi-') !== 0) { $iconClass = 'bi-' . $iconClass; } return sprintf( '', esc_attr($iconClass) ); } }