Feedback Widget Layout
May 6, 2025 · AI Conversations
<style>
.feedback-slider-track {
position: relative;
height: auto;
}
.feedback-section .elementor-widget {
position: unset !important;
}
.feedback-slide-group {
display: flex;
gap: 30px;
position: absolute;
width: 100%;
top: 0;
left: 0;
opacity: 0;
transition: opacity 0.5s ease-in-out;
}
.feedback-box {
flex: 1;
background: #fff;
padding: 30px 30px 0 30px;
border-radius: 5px;
display: flex;
flex-direction: column;
justify-content: space-between;
box-sizing: border-box;
}
.feedback-user {
display: flex;
align-items: center;
justify-content: space-between;
}
.feedback-user-img {
width: 56px;
height: 56px !important;
border-radius: 50% !important;
margin-right: 10px;
border: 2px solid #E9204F !important;
}
.feedback-pagination {
position: absolute;
right: 105px;
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 32px;
}
.feedback-pagination::before,
.feedback-pagination::after {
content: '';
display: block;
width: 1px;
height: 85px;
background-color: #fff;
opacity: 0.5;
}
.feedback-pagination .dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: #fff;
cursor: pointer;
position: relative;
z-index: 1;
}
.feedback-pagination .dot.active {
background: #fff;
border: 2px solid #fff;
box-shadow: 0 0 0 2px #000, 0 0 0 4px #fff;
}
.feedback-slide-group.active {
opacity: 1;
position: relative;
}
.user-inner-box {
display: flex;
align-items: center;
}
.user-inner-text {
display: flex;
flex-direction: column;
}
.user-name {
font-size: 18px;
font-weight: 600;
color: #000;
}
.feedback-text,
span.user-title {
color: #444;
}
</style>
<script>
jQuery(document).ready(function ($) {
var $groups = $('.feedback-slide-group');
var current = 0;
function showSlide(index) {
$groups.removeClass('active').eq(index).addClass('active');
$('.feedback-pagination .dot').removeClass('active').eq(index).addClass('active');
}
$('.feedback-pagination .dot').click(function () {
var index = $(this).data('index');
current = index;
showSlide(index);
});
showSlide(0); // Initialize
// ✅ Equal height logic
function equalizeHeights() {
var maxGroupHeight = 0;
$groups.each(function () {
var $boxes = $(this).find('.feedback-box');
var maxBoxHeight = 0;
$boxes.css('height', 'auto'); // reset first
$boxes.each(function () {
var h = $(this).outerHeight();
if (h > maxBoxHeight) maxBoxHeight = h;
});
$boxes.css('height', maxBoxHeight + 'px');
$(this).css('height', maxBoxHeight + 40 + 'px'); // group height + padding
if (maxBoxHeight + 40 > maxGroupHeight) maxGroupHeight = maxBoxHeight + 40;
});
// Apply same height to all groups
$groups.css('height', maxGroupHeight + 'px');
}
equalizeHeights();
$(window).on('resize', function () {
//equalizeHeights();
});
});
</script>
Since `PHP` runs server-side and can't detect the screen width, we must render all items individually and let JavaScript group them for desktop if needed. Here's the **correct and complete solution**:
—
### ✅ Changes Summary:
1. Render all feedbacks as separate `.feedback-box` divs.
2. Let JavaScript group into slide sets (`.feedback-slide-group`) depending on screen width.
3. Dynamically reinitialize the groups on window resize.
—
### ✅ Updated `render()` Code (Just the relevant part):
Replace this PHP part:
$groups = array_chunk($settings['feedback_list'], 2);
foreach ($groups as $group) {
echo '<div class="feedback-slide-group">';
foreach ($group as $item) {
...
}
echo '</div>';
}
With this:
foreach ($settings['feedback_list'] as $item) {
?>
<div class="feedback-box" data-feedback-box>
<div class="feedback-text"><?php echo esc_html($item['feedback_text']); ?></div>
<div class="feedback-user">
<div class="user-inner-box">
<div><img class="feedback-user-img" src="<?php echo esc_url($item['user_image']['url']); ?>" /></div>
<div class="user-inner-text">
<span class="user-name"><?php echo esc_html($item['user_name']); ?></span>
<span class="user-title"><?php echo esc_html($item['user_title']); ?></span>
</div>
</div>
<div>
<img src="<?php echo esc_url($item['quote_icon']['url']); ?>" class="quote-icon" />
</div>
</div>
</div>
<?php
}
—
### ✅ JavaScript Update (Replace “ section):
<script>
jQuery(document).ready(function ($) {
var $track = $('.feedback-slider-track');
var $pagination = $('.feedback-pagination');
function groupSlides() {
var perSlide = $(window).width() <= 767 ? 1 : 2;
// Clear existing groups
$track.empty();
$pagination.empty();
var $boxes = $('[data-feedback-box]');
var groups = [];
for (var i = 0; i < $boxes.length; i += perSlide) {
var $group = $('<div class="feedback-slide-group"></div>');
$boxes.slice(i, i + perSlide).appendTo($group);
groups.push($group);
}
groups.forEach(function ($group, index) {
$track.append($group);
if (index === 0) $group.addClass('active');
var dot = $('<div class="dot" data-index="' + index + '"></div>');
if (index === 0) dot.addClass('active');
$pagination.append(dot);
});
bindDots();
equalizeHeights();
}
function bindDots() {
$('.feedback-pagination .dot').off('click').on('click', function () {
var index = $(this).data('index');
$('.feedback-slide-group').removeClass('active').eq(index).addClass('active');
$('.feedback-pagination .dot').removeClass('active').eq(index).addClass('active');
});
}
function equalizeHeights() {
var maxGroupHeight = 0;
$('.feedback-slide-group').each(function () {
var $boxes = $(this).find('.feedback-box');
var maxBoxHeight = 0;
$boxes.css('height', 'auto');
$boxes.each(function () {
var h = $(this).outerHeight();
if (h > maxBoxHeight) maxBoxHeight = h;
});
$boxes.css('height', maxBoxHeight + 'px');
$(this).css('height', maxBoxHeight + 40 + 'px');
if (maxBoxHeight + 40 > maxGroupHeight) maxGroupHeight = maxBoxHeight + 40;
});
$('.feedback-slide-group').css('height', maxGroupHeight + 'px');
}
// Initial grouping
groupSlides();
$(window).on('resize', function () {
groupSlides();
});
});
</script>
—
Would you like me to generate a downloadable PHP file with this full updated widget code?
—
### ✅ `FeedbackWidget.php` (Full)
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
class FeedbackWidget extends ElementorWidget_Base {
public function get_name() {
return 'feedbackWidget';
}
public function get_title() {
return __('Feedback Widget', 'gentium');
}
public function get_icon() {
return 'eicon-testimonial-carousel';
}
public function get_categories() {
return ['general'];
}
protected function _register_controls() {
$this->start_controls_section('content_section', [
'label' => __('Feedbacks', 'gentium'),
]);
$repeater = new ElementorRepeater();
$repeater->add_control('feedback_text', [
'label' => __('Feedback Text'),
'type' => ElementorControls_Manager::TEXTAREA,
'default' => 'Sample feedback text...',
]);
$repeater->add_control('user_image', [
'label' => __('User Image'),
'type' => ElementorControls_Manager::MEDIA,
]);
$repeater->add_control('user_name', [
'label' => __('User Name'),
'type' => ElementorControls_Manager::TEXT,
'default' => 'John Doe',
]);
$repeater->add_control('user_title', [
'label' => __('Job Title'),
'type' => ElementorControls_Manager::TEXT,
'default' => 'CEO, Company',
]);
$repeater->add_control('quote_icon', [
'label' => __('Quote Icon'),
'type' => ElementorControls_Manager::MEDIA,
]);
$this->add_control('feedback_list', [
'label' => __('Feedback List'),
'type' => ElementorControls_Manager::REPEATER,
'fields' => $repeater->get_controls(),
'default' => [],
]);
$this->end_controls_section();
}
protected function render() {
$settings = $this->get_settings_for_display();
if (empty($settings['feedback_list'])) return;
?>
<style>
.feedback-slider-track {
position: relative;
height: auto;
}
.feedback-section .elementor-widget {
position: unset !important;
}
.feedback-slide-group {
display: flex;
gap: 30px;
position: absolute;
width: 100%;
top: 0;
left: 0;
opacity: 0;
transition: opacity 0.5s ease-in-out;
}
.feedback-slide-group.active {
opacity: 1;
position: relative;
}
.feedback-box {
flex: 1;
background: #fff;
padding: 30px 30px 0 30px;
border-radius: 5px;
display: flex;
flex-direction: column;
justify-content: space-between;
box-sizing: border-box;
}
.feedback-user {
display: flex;
align-items: center;
justify-content: space-between;
}
.feedback-user-img {
width: 56px;
height: 56px !important;
border-radius: 50% !important;
margin-right: 10px;
border: 2px solid #E9204F !important;
}
.feedback-pagination {
position: absolute;
right: 105px;
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 32px;
}
.feedback-pagination::before,
.feedback-pagination::after {
content: '';
display: block;
width: 1px;
height: 85px;
background-color: #fff;
opacity: 0.5;
}
.feedback-pagination .dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: #fff;
cursor: pointer;
position: relative;
z-index: 1;
}
.feedback-pagination .dot.active {
background: #fff;
border: 2px solid #fff;
box-shadow: 0 0 0 2px #000, 0 0 0 4px #fff;
}
.user-inner-box {
display: flex;
align-items: center;
}
.user-inner-text {
display: flex;
flex-direction: column;
}
.user-name {
font-size: 18px;
font-weight: 600;
color: #000;
}
.feedback-text,
span.user-title {
color: #444;
}
</style>
<div class="feedback-slider-wrapper">
<div class="feedback-slider-track">
<?php foreach ($settings['feedback_list'] as $item): ?>
<div class="feedback-box" data-feedback-box>
<div class="feedback-text"><?php echo esc_html($item['feedback_text']); ?></div>
<div class="feedback-user">
<div class="user-inner-box">
<div><img class="feedback-user-img" src="<?php echo esc_url($item['user_image']['url']); ?>" /></div>
<div class="user-inner-text">
<span class="user-name"><?php echo esc_html($item['user_name']); ?></span>
<span class="user-title"><?php echo esc_html($item['user_title']); ?></span>
</div>
</div>
<div>
<img src="<?php echo esc_url($item['quote_icon']['url']); ?>" class="quote-icon" />
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="feedback-pagination"></div>
</div>
<script>
jQuery(document).ready(function ($) {
var $track = $('.feedback-slider-track');
var $pagination = $('.feedback-pagination');
function groupSlides() {
var perSlide = $(window).width() <= 767 ? 1 : 2;
// Reset
var $boxes = $('[data-feedback-box]');
$track.empty();
$pagination.empty();
var groups = [];
for (var i = 0; i < $boxes.length; i += perSlide) {
var $group = $('<div class="feedback-slide-group"></div>');
$boxes.slice(i, i + perSlide).appendTo($group);
groups.push($group);
}
groups.forEach(function ($group, index) {
$track.append($group);
if (index === 0) $group.addClass('active');
var dot = $('<div class="dot" data-index="' + index + '"></div>');
if (index === 0) dot.addClass('active');
$pagination.append(dot);
});
bindDots();
equalizeHeights();
}
function bindDots() {
$('.feedback-pagination .dot').off('click').on('click', function () {
var index = $(this).data('index');
$('.feedback-slide-group').removeClass('active').eq(index).addClass('active');
$('.feedback-pagination .dot').removeClass('active').eq(index).addClass('active');
});
}
function equalizeHeights() {
var maxGroupHeight = 0;
$('.feedback-slide-group').each(function () {
var $boxes = $(this).find('.feedback-box');
var maxBoxHeight = 0;
$boxes.css('height', 'auto');
$boxes.each(function () {
var h = $(this).outerHeight();
if (h > maxBoxHeight) maxBoxHeight = h;
});
$boxes.css('height', maxBoxHeight + 'px');
$(this).css('height', maxBoxHeight + 40 + 'px');
if (maxBoxHeight + 40 > maxGroupHeight) maxGroupHeight = maxBoxHeight + 40;
});
$('.feedback-slide-group').css('height', maxGroupHeight + 'px');
}
groupSlides();
$(window).on('resize', function () {
groupSlides();
});
});
</script>
<?php
}
}
—
Let me know if you want this in a ZIP with a manifest file for direct Elementor import.